2020-12-23 18:21:31 +01:00
using Flowframes.AudioVideo ;
using Flowframes.Data ;
2020-12-02 14:27:41 +01:00
using Flowframes.IO ;
2020-12-23 00:07:06 +01:00
using Flowframes.Main ;
2020-12-29 16:01:24 +01:00
using Flowframes.MiscUtils ;
2020-12-07 12:34:12 +01:00
using System ;
2021-01-16 02:30:46 +01:00
using System.Collections.Generic ;
2020-11-23 16:51:05 +01:00
using System.Drawing ;
using System.Globalization ;
using System.IO ;
2020-12-22 19:52:37 +01:00
using System.Linq ;
2020-11-23 16:51:05 +01:00
using System.Threading.Tasks ;
2020-12-23 16:13:04 +01:00
using Utils = Flowframes . AudioVideo . FFmpegUtils ;
2020-11-23 16:51:05 +01:00
namespace Flowframes
{
class FFmpegCommands
{
2020-11-25 17:48:27 +01:00
static string hdrFilter = @"-vf select=gte(n\,%frNum%),zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p" ;
2021-01-11 17:43:35 +01:00
static string divisionFilter = "\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"" ;
2020-11-24 12:28:47 +01:00
static string pngComprArg = "-compression_level 3" ;
2020-11-25 12:40:17 +01:00
static string mpDecDef = "\"mpdecimate\"" ;
static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"" ;
2021-01-06 23:33:00 +01:00
public static async Task ExtractSceneChanges ( string inputFile , string frameFolderPath , float rate )
2020-11-26 20:17:18 +01:00
{
2020-12-07 12:34:12 +01:00
Logger . Log ( "Extracting scene changes..." ) ;
2021-01-06 23:33:00 +01:00
await VideoToFrames ( inputFile , frameFolderPath , rate , false , false , new Size ( 320 , 180 ) , false , true ) ;
2020-12-07 12:34:12 +01:00
bool hiddenLog = Interpolate . currentInputFrameCount < = 50 ;
2021-01-07 11:02:43 +01:00
int amount = IOUtils . GetAmountOfFiles ( frameFolderPath , false ) ;
Logger . Log ( $"Detected {amount} scene {(amount == 1 ? " change " : " changes ")}." . Replace ( " 0 " , " no " ) , false , ! hiddenLog ) ;
2020-11-26 20:17:18 +01:00
}
2021-01-06 23:33:00 +01:00
public static async Task VideoToFrames ( string inputFile , string frameFolderPath , float rate , bool deDupe , bool delSrc , bool timecodes = true )
2020-11-23 16:51:05 +01:00
{
2021-01-06 23:33:00 +01:00
await VideoToFrames ( inputFile , frameFolderPath , rate , deDupe , delSrc , new Size ( ) , timecodes ) ;
2020-11-23 16:51:05 +01:00
}
2021-01-06 23:33:00 +01:00
public static async Task VideoToFrames ( string inputFile , string frameFolderPath , float rate , bool deDupe , bool delSrc , Size size , bool timecodes , bool sceneDetect = false )
2020-11-23 16:51:05 +01:00
{
2020-12-15 14:46:33 +01:00
if ( ! sceneDetect ) Logger . Log ( "Extracting video frames from input video..." ) ;
2020-11-25 20:31:21 +01:00
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
2020-12-02 14:27:41 +01:00
IOUtils . CreateDir ( frameFolderPath ) ;
2021-01-14 20:32:42 +01:00
string timecodeStr = timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : "-copyts -frame_pts true" ;
2020-12-17 23:26:25 +01:00
string scnDetect = sceneDetect ? $"\" select = ' gt ( scene , { Config . GetFloatString ( "scnDetectValue" ) } ) ' \ "" : "" ;
2020-12-15 14:46:33 +01:00
string mpStr = deDupe ? ( ( Config . GetInt ( "mpdecimateMode" ) = = 0 ) ? mpDecDef : mpDecAggr ) : "" ;
2021-01-07 11:02:43 +01:00
string filters = FormatUtils . ConcatStrings ( new string [ ] { scnDetect , mpStr } ) ;
2020-12-29 16:01:24 +01:00
string vf = filters . Length > 2 ? $"-vf {filters}" : "" ;
2021-01-06 23:33:00 +01:00
string rateArg = ( rate > 0 ) ? $" -r {rate.ToStringDot()}" : "" ;
2020-12-02 17:23:24 +01:00
string pad = Padding . inputFrames . ToString ( ) ;
2021-01-06 23:33:00 +01:00
string args = $"{rateArg} -i {inputFile.Wrap()} {pngComprArg} -vsync 0 -pix_fmt rgb24 {timecodeStr} {vf} {sizeStr} \" { frameFolderPath } / % { pad } d . png \ "" ;
2020-12-07 12:34:12 +01:00
AvProcess . LogMode logMode = Interpolate . currentInputFrameCount > 50 ? AvProcess . LogMode . OnlyLastLine : AvProcess . LogMode . Hidden ;
2021-01-07 11:02:43 +01:00
await AvProcess . RunFfmpeg ( args , logMode , AvProcess . TaskType . ExtractFrames ) ;
int amount = IOUtils . GetAmountOfFiles ( frameFolderPath , false , "*.png" ) ;
if ( ! sceneDetect ) Logger . Log ( $"Extracted {amount} {(amount == 1 ? " frame " : " frames ")} from input." , false , true ) ;
2020-11-23 16:51:05 +01:00
await Task . Delay ( 1 ) ;
if ( delSrc )
DeleteSource ( inputFile ) ;
}
2021-01-15 15:07:40 +01:00
public static async Task ImportImages ( string inpath , string outpath , Size size , bool delSrc = false , bool showLog = true )
2020-12-02 14:27:41 +01:00
{
if ( showLog ) Logger . Log ( "Importing images..." ) ;
2020-12-18 00:19:08 +01:00
Logger . Log ( $"Importing images from {inpath} to {outpath}." ) ;
2020-12-02 14:27:41 +01:00
IOUtils . CreateDir ( outpath ) ;
string concatFile = Path . Combine ( Paths . GetDataPath ( ) , "png-concat-temp.ini" ) ;
string concatFileContent = "" ;
2020-12-22 19:52:37 +01:00
foreach ( string img in IOUtils . GetFilesSorted ( inpath ) )
2020-12-02 14:27:41 +01:00
concatFileContent + = $"file '{img.Replace(@" \ ", " / ")}'\n" ;
File . WriteAllText ( concatFile , concatFileContent ) ;
2021-01-15 15:07:40 +01:00
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
string args = $" -loglevel panic -f concat -safe 0 -i {concatFile.Wrap()} {pngComprArg} {sizeStr} -pix_fmt rgb24 -vsync 0 -vf {divisionFilter} \" { outpath } / % { Padding . inputFrames } d . png \ "" ;
2020-12-02 14:27:41 +01:00
AvProcess . LogMode logMode = IOUtils . GetAmountOfFiles ( inpath , false ) > 50 ? AvProcess . LogMode . OnlyLastLine : AvProcess . LogMode . Hidden ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , logMode , AvProcess . TaskType . ExtractFrames ) ;
2020-12-02 14:27:41 +01:00
if ( delSrc )
DeleteSource ( inpath ) ;
}
2021-01-15 22:43:13 +01:00
public static async Task ImportSingleImage ( string inputFile , string outPath , Size size )
{
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
bool isPng = ( Path . GetExtension ( outPath ) . ToLower ( ) = = ".png" ) ;
string comprArg = isPng ? pngComprArg : "" ;
string pixFmt = "-pix_fmt " + ( isPng ? $"rgb24 {comprArg}" : "yuvj420p" ) ;
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} {pixFmt} -vf {divisionFilter} {outPath.Wrap()}" ;
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden , AvProcess . TaskType . ExtractFrames ) ;
}
2021-01-07 12:15:13 +01:00
public static async Task ExtractSingleFrame ( string inputFile , string outputPath , int frameNum )
2020-11-27 14:35:32 +01:00
{
2021-01-07 12:15:13 +01:00
bool isPng = ( Path . GetExtension ( outputPath ) . ToLower ( ) = = ".png" ) ;
string comprArg = isPng ? pngComprArg : "" ;
2021-01-15 22:43:13 +01:00
string pixFmt = "-pix_fmt " + ( isPng ? $"rgb24 {comprArg}" : "yuvj420p" ) ;
string args = $"-i {inputFile.Wrap()} -vf \" select = eq ( n \ \ , { frameNum } ) \ " -vframes 1 {pixFmt} {outputPath.Wrap()}" ;
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden , AvProcess . TaskType . ExtractFrames ) ;
}
public static async Task ExtractLastFrame ( string inputFile , string outputPath , Size size )
{
if ( IOUtils . IsPathDirectory ( outputPath ) )
outputPath = Path . Combine ( outputPath , "last.png" ) ;
bool isPng = ( Path . GetExtension ( outputPath ) . ToLower ( ) = = ".png" ) ;
string comprArg = isPng ? pngComprArg : "" ;
string pixFmt = "-pix_fmt " + ( isPng ? $"rgb24 {comprArg}" : "yuvj420p" ) ;
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
string args = $"-sseof -1 -i {inputFile.Wrap()} -update 1 {pixFmt} {sizeStr} {outputPath.Wrap()}" ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden , AvProcess . TaskType . ExtractFrames ) ;
2020-11-23 16:51:05 +01:00
}
2021-01-02 16:20:21 +01:00
public static async Task FramesToVideoConcat ( string framesFile , string outPath , Interpolate . OutMode outMode , float fps , AvProcess . LogMode logMode = AvProcess . LogMode . OnlyLastLine , bool isChunk = false )
2021-01-06 17:41:18 +01:00
{
await FramesToVideoConcat ( framesFile , outPath , outMode , fps , 0 , logMode , isChunk ) ;
}
public static async Task FramesToVideoConcat ( string framesFile , string outPath , Interpolate . OutMode outMode , float fps , float resampleFps , AvProcess . LogMode logMode = AvProcess . LogMode . OnlyLastLine , bool isChunk = false )
2020-12-23 16:13:04 +01:00
{
if ( logMode ! = AvProcess . LogMode . Hidden )
2021-01-06 17:41:18 +01:00
Logger . Log ( ( resampleFps < = 0 ) ? $"Encoding video..." : $"Encoding video resampled to {resampleFps.ToString().Replace(" , ", " . ")} FPS..." ) ;
Directory . CreateDirectory ( outPath . GetParentDir ( ) ) ;
string encArgs = Utils . GetEncArgs ( Utils . GetCodec ( outMode ) ) ;
if ( ! isChunk ) encArgs + = $" -movflags +faststart" ;
2020-12-23 16:13:04 +01:00
string vfrFilename = Path . GetFileName ( framesFile ) ;
string rate = fps . ToString ( ) . Replace ( "," , "." ) ;
2021-01-08 20:16:40 +01:00
string vf = ( resampleFps < = 0 ) ? "" : $"-vf fps=fps={resampleFps.ToStringDot()}" ;
2020-12-23 16:13:04 +01:00
string extraArgs = Config . Get ( "ffEncArgs" ) ;
2021-01-06 17:41:18 +01:00
string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {vf} {extraArgs} -threads {Config.GetInt(" ffEncThreads ")} {outPath.Wrap()}" ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , framesFile . GetParentDir ( ) , logMode , AvProcess . TaskType . Encode ) ;
2020-11-25 12:40:17 +01:00
}
2021-01-06 17:41:18 +01:00
public static async Task ConcatVideos ( string concatFile , string outPath , int looptimes = - 1 )
2020-11-30 02:14:04 +01:00
{
2021-01-06 21:44:09 +01:00
Logger . Log ( $"Merging videos..." , false , Logger . GetLastLine ( ) . Contains ( "frame" ) ) ;
2020-11-30 02:14:04 +01:00
string loopStr = ( looptimes > 0 ) ? $"-stream_loop {looptimes}" : "" ;
2020-11-30 20:32:33 +01:00
string vfrFilename = Path . GetFileName ( concatFile ) ;
2021-01-06 21:47:25 +01:00
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart {outPath.Wrap()}" ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , concatFile . GetParentDir ( ) , AvProcess . LogMode . Hidden , AvProcess . TaskType . Merge ) ;
2020-11-30 02:14:04 +01:00
}
2021-01-06 17:41:18 +01:00
public static async Task FramesToGifConcat ( string framesFile , string outPath , float fps , bool palette , int colors = 64 , float resampleFps = - 1 , AvProcess . LogMode logMode = AvProcess . LogMode . OnlyLastLine )
2020-12-08 14:43:03 +01:00
{
2021-01-06 17:41:18 +01:00
if ( logMode ! = AvProcess . LogMode . Hidden )
Logger . Log ( ( resampleFps < = 0 ) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(" , ", " . ")} FPS..." ) ;
2020-12-08 14:43:03 +01:00
string vfrFilename = Path . GetFileName ( framesFile ) ;
2021-01-06 17:41:18 +01:00
string paletteFilter = palette ? $"-vf \" split [ s0 ] [ s1 ] ; [ s0 ] palettegen = { colors } [ p ] ; [ s1 ] [ p ] paletteuse = dither = floyd_steinberg : diff_mode = rectangle \ "" : "" ;
string fpsFilter = ( resampleFps < = 0 ) ? "" : $"fps=fps={resampleFps.ToStringDot()}" ;
string vf = FormatUtils . ConcatStrings ( new string [ ] { paletteFilter , fpsFilter } ) ;
string rate = fps . ToStringDot ( ) ;
string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}" ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , framesFile . GetParentDir ( ) , AvProcess . LogMode . OnlyLastLine , AvProcess . TaskType . Encode ) ;
2020-12-08 14:43:03 +01:00
}
2020-12-15 14:46:33 +01:00
public static async Task LoopVideo ( string inputFile , int times , bool delSrc = false )
2020-11-23 16:51:05 +01:00
{
string pathNoExt = Path . ChangeExtension ( inputFile , null ) ;
string ext = Path . GetExtension ( inputFile ) ;
2020-11-26 21:18:31 +01:00
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy \" { pathNoExt } - Loop { times } { ext } \ "" ;
2020-12-03 00:04:46 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden ) ;
2020-11-23 16:51:05 +01:00
if ( delSrc )
DeleteSource ( inputFile ) ;
}
2020-12-15 14:46:33 +01:00
public static async Task LoopVideoEnc ( string inputFile , int times , bool useH265 , int crf , bool delSrc = false )
2020-11-23 16:51:05 +01:00
{
string pathNoExt = Path . ChangeExtension ( inputFile , null ) ;
string ext = Path . GetExtension ( inputFile ) ;
string enc = "libx264" ;
if ( useH265 ) enc = "libx265" ;
2020-12-15 14:46:33 +01:00
string args = " -stream_loop " + times + " -i \"" + inputFile + "\" -c:v " + enc + " -crf " + crf + " -c:a copy \"" + pathNoExt + "-" + times + "xLoop" + ext + "\"" ;
2020-11-23 16:51:05 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . OnlyLastLine ) ;
if ( delSrc )
DeleteSource ( inputFile ) ;
}
2020-12-15 14:46:33 +01:00
public static async Task ChangeSpeed ( string inputFile , float newSpeedPercent , bool delSrc = false )
2020-11-23 16:51:05 +01:00
{
string pathNoExt = Path . ChangeExtension ( inputFile , null ) ;
string ext = Path . GetExtension ( inputFile ) ;
float val = newSpeedPercent / 100f ;
string speedVal = ( 1f / val ) . ToString ( "0.0000" ) . Replace ( "," , "." ) ;
string args = " -itsscale " + speedVal + " -i \"" + inputFile + "\" -c copy \"" + pathNoExt + "-" + newSpeedPercent + "pcSpeed" + ext + "\"" ;
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . OnlyLastLine ) ;
if ( delSrc )
DeleteSource ( inputFile ) ;
}
2020-12-15 14:46:33 +01:00
public static async Task Encode ( string inputFile , string vcodec , string acodec , int crf , int audioKbps = 0 , bool delSrc = false )
2020-11-23 16:51:05 +01:00
{
string outPath = Path . ChangeExtension ( inputFile , null ) + "-convert.mp4" ;
2021-01-02 16:20:21 +01:00
string args = $" -i {inputFile.Wrap()} -c:v {vcodec} -crf {crf} -pix_fmt yuv420p -c:a {acodec} -b:a {audioKbps}k -vf {divisionFilter} {outPath.Wrap()}" ;
2020-11-23 16:51:05 +01:00
if ( string . IsNullOrWhiteSpace ( acodec ) )
args = args . Replace ( "-c:a" , "-an" ) ;
2020-12-15 14:46:33 +01:00
if ( audioKbps < 0 )
2020-11-23 16:51:05 +01:00
args = args . Replace ( $" -b:a {audioKbps}" , "" ) ;
2021-01-06 22:51:04 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . OnlyLastLine , AvProcess . TaskType . Encode ) ;
2020-11-23 16:51:05 +01:00
if ( delSrc )
DeleteSource ( inputFile ) ;
}
2020-12-15 14:46:33 +01:00
public static async Task ExtractAudio ( string inputFile , string outFile ) // https://stackoverflow.com/a/27413824/14274419
2020-11-23 16:51:05 +01:00
{
2021-01-15 22:43:13 +01:00
string audioExt = Utils . GetAudioExt ( inputFile ) ;
outFile = Path . ChangeExtension ( outFile , audioExt ) ;
2020-11-23 16:51:05 +01:00
Logger . Log ( $"[FFCmds] Extracting audio from {inputFile} to {outFile}" , true ) ;
2021-01-15 22:43:13 +01:00
string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}" ;
2020-11-23 16:51:05 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden ) ;
2021-01-15 22:43:13 +01:00
if ( File . Exists ( outFile ) & & IOUtils . GetFilesize ( outFile ) < 512 )
{
Logger . Log ( "Failed to extract audio losslessly! Trying to re-encode." ) ;
2020-11-23 16:51:05 +01:00
File . Delete ( outFile ) ;
2021-01-15 22:43:13 +01:00
outFile = Path . ChangeExtension ( outFile , Utils . GetAudioExtForContainer ( Path . GetExtension ( inputFile ) ) ) ;
args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outFile.Wrap()}" ;
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden ) ;
if ( ( File . Exists ( outFile ) & & IOUtils . GetFilesize ( outFile ) < 512 ) | | AvProcess . lastOutputFfmpeg . Contains ( "Invalid data" ) )
{
Logger . Log ( "Failed to extract audio, even with re-encoding. Output will not have audio." ) ;
IOUtils . TryDeleteIfExists ( outFile ) ;
return ;
}
Logger . Log ( $"Source audio has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly." , false , true ) ;
}
2020-11-23 16:51:05 +01:00
}
2021-01-16 14:42:47 +01:00
public static async Task ExtractSubtitles ( string inputFile , string outFolder , Interpolate . OutMode outMode )
2021-01-16 01:49:10 +01:00
{
2021-01-16 02:30:46 +01:00
Dictionary < int , string > subDict = await GetSubtitleTracks ( inputFile ) ;
foreach ( KeyValuePair < int , string > subTrack in subDict )
2021-01-16 01:49:10 +01:00
{
2021-01-16 02:30:46 +01:00
string trackName = subTrack . Value . Length > 4 ? CultureInfo . CurrentCulture . TextInfo . ToTitleCase ( subTrack . Value . ToLower ( ) ) : subTrack . Value . ToUpper ( ) ;
string outPath = Path . Combine ( outFolder , $"{subTrack.Key}-{trackName}.srt" ) ;
string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}" ;
2021-01-16 01:49:10 +01:00
await AvProcess . RunFfmpeg ( args , AvProcess . LogMode . Hidden ) ;
if ( AvProcess . lastOutputFfmpeg . Contains ( "matches no streams" ) ) // Break if there are no more subtitle tracks
break ;
2021-01-16 14:42:47 +01:00
Logger . Log ( $"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})" , true , false , "ffmpeg" ) ;
2021-01-16 01:49:10 +01:00
}
2021-01-16 02:30:46 +01:00
if ( subDict . Count > 0 )
2021-01-16 14:42:47 +01:00
{
2021-01-16 02:30:46 +01:00
Logger . Log ( $"Extracted {subDict.Count} subtitle tracks from the input video." ) ;
2021-01-16 14:42:47 +01:00
Utils . ContainerSupportsSubs ( Utils . GetExt ( outMode ) , true ) ;
}
2021-01-16 02:30:46 +01:00
}
public static async Task < Dictionary < int , string > > GetSubtitleTracks ( string inputFile )
{
Dictionary < int , string > subDict = new Dictionary < int , string > ( ) ;
string args = $"-i {inputFile.Wrap()}" ;
string [ ] outputLines = ( await AvProcess . GetFfmpegOutputAsync ( args ) ) . SplitIntoLines ( ) ;
string [ ] filteredLines = outputLines . Where ( l = > l . Contains ( " Subtitle: " ) ) . ToArray ( ) ;
int idx = 0 ;
foreach ( string line in filteredLines )
{
string lang = "unknown" ;
bool hasLangInfo = line . Contains ( "(" ) & & line . Contains ( "): Subtitle: " ) ;
if ( hasLangInfo )
lang = line . Split ( '(' ) [ 1 ] . Split ( ')' ) [ 0 ] ;
subDict . Add ( idx , lang ) ;
idx + + ;
}
return subDict ;
2021-01-16 01:49:10 +01:00
}
2021-01-16 14:42:47 +01:00
public static async Task MergeAudioAndSubs ( string inputFile , string audioPath , string tempFolder , int looptimes = - 1 ) // https://superuser.com/a/277667
2020-11-23 16:51:05 +01:00
{
Logger . Log ( $"[FFCmds] Merging audio from {audioPath} into {inputFile}" , true ) ;
2021-01-16 14:42:47 +01:00
string containerExt = Path . GetExtension ( inputFile ) ;
string tempPath = Path . Combine ( tempFolder , $"vid{containerExt}" ) ; // inputFile + "-temp" + Path.GetExtension(inputFile);
string outPath = Path . Combine ( tempFolder , $"muxed{containerExt}" ) ; // inputFile + "-temp" + Path.GetExtension(inputFile);
File . Move ( inputFile , tempPath ) ;
string inName = Path . GetFileName ( tempPath ) ;
string audioName = Path . GetFileName ( audioPath ) ;
string outName = Path . GetFileName ( outPath ) ;
bool subs = Utils . ContainerSupportsSubs ( containerExt , false ) & & Config . GetBool ( "keepSubs" ) ;
string subInputArgs = "" ;
string subMapArgs = "" ;
string subMetaArgs = "" ;
string [ ] subTracks = subs ? IOUtils . GetFilesSorted ( tempFolder , false , "*.srt" ) : new string [ 0 ] ;
for ( int subTrack = 0 ; subTrack < subTracks . Length ; subTrack + + )
2020-11-23 16:51:05 +01:00
{
2021-01-16 14:42:47 +01:00
subInputArgs + = $" -i {Path.GetFileName(subTracks[subTrack])}" ;
subMapArgs + = $" -map {subTrack+2}" ;
subMetaArgs + = $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}" ;
}
2021-01-15 22:43:13 +01:00
2021-01-16 14:42:47 +01:00
string subCodec = Utils . GetSubCodecForContainer ( containerExt ) ;
string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}" ;
await AvProcess . RunFfmpeg ( args , tempFolder , AvProcess . LogMode . Hidden ) ;
2021-01-15 22:43:13 +01:00
2021-01-16 14:42:47 +01:00
if ( ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) < 1024 ) | | AvProcess . lastOutputFfmpeg . Contains ( "Invalid data" ) | | AvProcess . lastOutputFfmpeg . Contains ( "Error initializing output stream" ) )
{
Logger . Log ( "Failed to merge audio losslessly! Trying to re-encode." , false , false , "ffmpeg" ) ;
args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}" ;
await AvProcess . RunFfmpeg ( args , tempFolder , AvProcess . LogMode . Hidden ) ;
if ( ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) < 1024 ) | | AvProcess . lastOutputFfmpeg . Contains ( "Invalid data" ) | | AvProcess . lastOutputFfmpeg . Contains ( "Error initializing output stream" ) )
2021-01-15 22:43:13 +01:00
{
2021-01-16 14:42:47 +01:00
Logger . Log ( "Failed to merge audio, even with re-encoding. Output will not have audio." , false , false , "ffmpeg" ) ;
2021-01-15 22:43:13 +01:00
IOUtils . TryDeleteIfExists ( tempPath ) ;
return ;
}
string audioExt = Path . GetExtension ( audioPath ) . Remove ( "." ) . ToUpper ( ) ;
2021-01-16 14:42:47 +01:00
Logger . Log ( $"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(" . ").ToUpper()}). This may decrease the quality slightly." , false , true , "ffmpeg" ) ;
}
//string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
//File.Delete(movePath);
if ( File . Exists ( outPath ) & & IOUtils . GetFilesize ( outPath ) > 512 )
{
File . Delete ( tempPath ) ;
File . Move ( outPath , inputFile ) ;
}
else
{
File . Move ( tempPath , inputFile ) ;
2020-11-23 16:51:05 +01:00
}
}
2020-12-15 14:46:33 +01:00
public static float GetFramerate ( string inputFile )
2020-11-23 16:51:05 +01:00
{
2020-12-27 22:52:14 +01:00
Logger . Log ( "Reading FPS using ffmpeg." , true , false , "ffmpeg" ) ;
2020-11-23 16:51:05 +01:00
string args = $" -i {inputFile.Wrap()}" ;
2020-11-23 18:08:17 +01:00
string output = AvProcess . GetFfmpegOutput ( args ) ;
string [ ] entries = output . Split ( ',' ) ;
2020-12-15 14:46:33 +01:00
foreach ( string entry in entries )
2020-11-23 16:51:05 +01:00
{
2020-11-23 18:08:17 +01:00
if ( entry . Contains ( " fps" ) & & ! entry . Contains ( "Input " ) ) // Avoid reading FPS from the filename, in case filename contains "fps"
2020-11-23 16:51:05 +01:00
{
Logger . Log ( "[FFCmds] FPS Entry: " + entry , true ) ;
string num = entry . Replace ( " fps" , "" ) . Trim ( ) . Replace ( "," , "." ) ;
float value ;
float . TryParse ( num , NumberStyles . Any , CultureInfo . InvariantCulture , out value ) ;
return value ;
}
}
return 0f ;
}
2020-12-15 14:46:33 +01:00
public static Size GetSize ( string inputFile )
2020-11-23 16:51:05 +01:00
{
2021-01-08 20:16:40 +01:00
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}" ;
2020-11-23 16:51:05 +01:00
string output = AvProcess . GetFfprobeOutput ( args ) ;
if ( output . Length > 4 & & output . Contains ( "x" ) )
{
string [ ] numbers = output . Split ( 'x' ) ;
return new Size ( numbers [ 0 ] . GetInt ( ) , numbers [ 1 ] . GetInt ( ) ) ;
}
return new Size ( 0 , 0 ) ;
}
public static int GetFrameCount ( string inputFile )
{
int frames = 0 ;
2020-12-27 22:52:14 +01:00
Logger . Log ( "Reading frame count using ffprobe." , true , false , "ffmpeg" ) ;
2020-11-23 16:51:05 +01:00
frames = ReadFrameCountFfprobe ( inputFile , Config . GetBool ( "ffprobeCountFrames" ) ) ; // Try reading frame count with ffprobe
if ( frames > 0 )
return frames ;
2020-12-27 22:52:14 +01:00
Logger . Log ( $"Failed to get frame count using ffprobe (frames = {frames}). Reading frame count using ffmpeg." , true , false , "ffmpeg" ) ;
2020-11-23 16:51:05 +01:00
frames = ReadFrameCountFfmpeg ( inputFile ) ; // Try reading frame count with ffmpeg
if ( frames > 0 )
return frames ;
Logger . Log ( "Failed to get total frame count of video." ) ;
return 0 ;
}
2020-12-27 22:52:14 +01:00
public static async Task < int > GetFrameCountAsync ( string inputFile )
{
int frames = 0 ;
Logger . Log ( "Reading frame count using ffprobe." , true , false , "ffmpeg" ) ;
frames = await ReadFrameCountFfprobeAsync ( inputFile , Config . GetBool ( "ffprobeCountFrames" ) ) ; // Try reading frame count with ffprobe
if ( frames > 0 )
return frames ;
Logger . Log ( $"Failed to get frame count using ffprobe (frames = {frames}). Reading frame count using ffmpeg." , true , false , "ffmpeg" ) ;
frames = await ReadFrameCountFfmpegAsync ( inputFile ) ; // Try reading frame count with ffmpeg
if ( frames > 0 )
return frames ;
Logger . Log ( "Failed to get total frame count of video." ) ;
return 0 ;
}
2020-12-15 14:46:33 +01:00
static int ReadFrameCountFfprobe ( string inputFile , bool readFramesSlow )
2020-11-23 16:51:05 +01:00
{
string args = $" -v panic -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}" ;
if ( readFramesSlow )
{
Logger . Log ( "Counting total frames using FFprobe. This can take a moment..." ) ;
args = $" -v panic -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}" ;
}
string info = AvProcess . GetFfprobeOutput ( args ) ;
string [ ] entries = info . SplitIntoLines ( ) ;
try
{
if ( readFramesSlow )
return info . GetInt ( ) ;
foreach ( string entry in entries )
{
if ( entry . Contains ( "nb_frames=" ) )
return entry . GetInt ( ) ;
2020-12-27 22:52:14 +01:00
}
}
catch { }
return - 1 ;
}
static async Task < int > ReadFrameCountFfprobeAsync ( string inputFile , bool readFramesSlow )
{
string args = $" -v panic -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}" ;
if ( readFramesSlow )
{
Logger . Log ( "Counting total frames using FFprobe. This can take a moment..." ) ;
await Task . Delay ( 10 ) ;
args = $" -v panic -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}" ;
}
string info = AvProcess . GetFfprobeOutput ( args ) ;
string [ ] entries = info . SplitIntoLines ( ) ;
try
{
if ( readFramesSlow )
return info . GetInt ( ) ;
foreach ( string entry in entries )
{
if ( entry . Contains ( "nb_frames=" ) )
return entry . GetInt ( ) ;
2020-11-23 16:51:05 +01:00
}
}
catch { }
return - 1 ;
}
static int ReadFrameCountFfmpeg ( string inputFile )
{
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - " ;
string info = AvProcess . GetFfmpegOutput ( args ) ;
string [ ] entries = info . SplitIntoLines ( ) ;
foreach ( string entry in entries )
{
if ( entry . Contains ( "frame=" ) )
return entry . Substring ( 0 , entry . IndexOf ( "fps" ) ) . GetInt ( ) ;
}
return - 1 ;
}
2020-12-27 22:52:14 +01:00
static async Task < int > ReadFrameCountFfmpegAsync ( string inputFile )
{
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - " ;
string info = await AvProcess . GetFfmpegOutputAsync ( args , true ) ;
try
{
string [ ] lines = info . SplitIntoLines ( ) ;
string lastLine = lines . Last ( ) ;
return lastLine . Substring ( 0 , lastLine . IndexOf ( "fps" ) ) . GetInt ( ) ;
}
catch
{
return - 1 ;
}
}
2020-12-25 00:39:14 +01:00
public static string GetAudioCodec ( string path )
2020-11-23 16:51:05 +01:00
{
string args = $" -v panic -show_streams -select_streams a -show_entries stream=codec_name {path.Wrap()}" ;
string info = AvProcess . GetFfprobeOutput ( args ) ;
string [ ] entries = info . SplitIntoLines ( ) ;
foreach ( string entry in entries )
{
if ( entry . Contains ( "codec_name=" ) )
return entry . Split ( '=' ) [ 1 ] ;
}
return "" ;
}
2020-12-15 14:46:33 +01:00
static void DeleteSource ( string path )
2020-11-23 16:51:05 +01:00
{
2020-11-30 02:14:04 +01:00
Logger . Log ( "[FFCmds] Deleting input file/dir: " + path , true ) ;
2020-11-23 16:51:05 +01:00
if ( IOUtils . IsPathDirectory ( path ) & & Directory . Exists ( path ) )
Directory . Delete ( path , true ) ;
if ( ! IOUtils . IsPathDirectory ( path ) & & File . Exists ( path ) )
File . Delete ( path ) ;
}
}
}