2021-08-23 16:50:18 +02:00
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.Main ;
using Flowframes.MiscUtils ;
using Flowframes.Ui ;
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using static Flowframes . AvProcess ;
namespace Flowframes.Media
{
partial class FfmpegExtract : FfmpegCommands
{
public static async Task ExtractSceneChanges ( string inPath , string outDir , Fraction rate , bool inputIsFrames , string format )
{
Logger . Log ( "Extracting scene changes..." ) ;
Directory . CreateDirectory ( outDir ) ;
string inArg = $"-i {inPath.Wrap()}" ;
if ( inputIsFrames )
{
2022-07-27 14:10:29 +02:00
string concatFile = Path . Combine ( Paths . GetSessionDataPath ( ) , "png-scndetect-concat-temp.ini" ) ;
2022-07-20 18:10:31 +02:00
FfmpegUtils . CreateConcatFile ( inPath , concatFile , Filetypes . imagesInterpCompat . ToList ( ) ) ;
2021-08-23 16:50:18 +02:00
inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}" ;
}
string scnDetect = $"-vf \" select = ' gt ( scene , { Config . GetFloatString ( Config . Key . scnDetectValue ) } ) ' \ "" ;
2023-12-15 10:24:46 +01:00
string rateArg = ( rate . GetFloat ( ) > 0 ) ? $"-fps_mode cfr -r {rate}" : "-fps_mode passthrough" ;
string args = $"{GetTrimArg(true)} {inArg} {GetImgArgs(format)} {rateArg} {scnDetect} -frame_pts 1 -s 256x144 {GetTrimArg(false)} \" { outDir } / % { Padding . inputFrames } d { format } \ "" ;
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
LogMode logMode = Interpolate . currentMediaFile . FrameCount > 50 ? LogMode . OnlyLastLine : LogMode . Hidden ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , logMode , inputIsFrames ? "panic" : "warning" , true ) ;
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
bool hiddenLog = Interpolate . currentMediaFile . FrameCount < = 50 ;
2021-08-23 16:50:18 +02:00
int amount = IoUtils . GetAmountOfFiles ( outDir , false ) ;
Logger . Log ( $"Detected {amount} scene {(amount == 1 ? " change " : " changes ")}." . Replace ( " 0 " , " no " ) , false , ! hiddenLog ) ;
}
static string GetImgArgs ( string extension , bool includePixFmt = true , bool alpha = false )
{
2023-12-22 05:20:22 +01:00
extension = extension . Lower ( ) . Remove ( "." ) . Replace ( "jpeg" , "jpg" ) ;
string pixFmt = "yuv420p" ;
2021-08-23 16:50:18 +02:00
2023-12-22 05:20:22 +01:00
if ( Interpolate . currentMediaFile ! = null & & Interpolate . currentMediaFile . VideoStreams . Any ( ) )
2021-08-23 16:50:18 +02:00
{
2023-12-26 22:59:17 +01:00
pixFmt = Interpolate . currentMediaFile . VideoStreams . First ( ) . PixelFormat ;
2021-08-23 16:50:18 +02:00
}
2023-12-22 05:20:22 +01:00
bool inputHighBitDepth = pixFmt . Contains ( "p10" ) | | pixFmt . Contains ( "p16" ) ;
bool outputHighBitDepth = Interpolate . currentSettings . outSettings . PixelFormat . ToString ( ) . Lower ( ) . Contains ( "p10" ) ;
string args = "" ;
if ( extension = = "png" )
2021-08-23 16:50:18 +02:00
{
2023-12-22 05:20:22 +01:00
pixFmt = alpha ? "rgba" : "rgb24" ; // PNG can't use YUV so we overwrite it with RGB
args = pngCompr ;
2021-08-23 16:50:18 +02:00
}
2023-12-22 05:20:22 +01:00
else if ( extension = = "jpg" )
{
// Fallback to YUV420P if not in list of supported formats
2024-06-24 17:44:23 +02:00
if ( ! new [ ] { "yuv420p" , "yuv422p" , "yuv444p" } . Contains ( pixFmt . Replace ( "yuvj" , "yuv" ) ) )
2023-12-22 05:20:22 +01:00
{
pixFmt = "yuv420p" ;
}
2021-08-23 16:50:18 +02:00
2024-06-24 17:44:23 +02:00
args = $"-q:v 1 -qmin 1 -color_range pc" ;
2023-12-22 05:20:22 +01:00
}
else if ( extension = = "tiff" )
{
// Fallback to YUV420P if not in list of supported formats
if ( ! new [ ] { "rgb24" , "rgb48le" , "pal8" , "rgba" , "yuv420p" , "yuv422p" , "yuv440p" , "yuv444p" } . Contains ( pixFmt ) )
{
pixFmt = inputHighBitDepth & & outputHighBitDepth ? "rgb48le" : "yuv420p" ;
}
}
else if ( extension = = "webp" )
2021-08-23 16:50:18 +02:00
{
2023-12-22 05:20:22 +01:00
// Fallback to YUV420P if not in list of supported formats
if ( ! new [ ] { "bgra" , "yuv420p" , "yuva420p" } . Contains ( pixFmt ) )
{
pixFmt = "yuv420p" ;
}
2021-08-23 16:50:18 +02:00
args = $"-q:v 100" ;
}
if ( includePixFmt )
2023-12-22 05:20:22 +01:00
args + = $" -pix_fmt {pixFmt}" ;
2021-08-23 16:50:18 +02:00
return args ;
}
public static async Task VideoToFrames ( string inputFile , string framesDir , bool alpha , Fraction rate , bool deDupe , bool delSrc , Size size , string format )
{
Logger . Log ( "Extracting video frames from input video..." ) ;
Logger . Log ( $"VideoToFrames() - Alpha: {alpha} - Rate: {rate} - Size: {size} - Format: {format}" , true , false , "ffmpeg" ) ;
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
IoUtils . CreateDir ( framesDir ) ;
2023-12-28 04:47:58 +01:00
string mpStr = deDupe ? GetMpdecimate ( true ) : "" ;
2021-08-23 16:50:18 +02:00
string filters = FormatUtils . ConcatStrings ( new [ ] { GetPadFilter ( ) , mpStr } ) ;
string vf = filters . Length > 2 ? $"-vf {filters}" : "" ;
2024-08-11 14:26:23 +02:00
bool allowCfr = rate . GetFloat ( ) > 0 & & ! deDupe & & Path . GetExtension ( inputFile ) . Lower ( ) ! = ".gif" ; // Forcing CFR on GIFs causes issues // TODO: Maybe never use CFR???
string rateArg = allowCfr ? $" -fps_mode cfr -r {rate}" : "-fps_mode passthrough" ;
2023-12-28 02:59:29 +01:00
string args = $"{GetTrimArg(true)} -itsscale {Interpolate.currentMediaFile.VideoStreams.First().FpsInfo.VfrRatio} -i {inputFile.Wrap()} {GetImgArgs(format, true, alpha)} {rateArg} -frame_pts 1 {vf} {sizeStr} {GetTrimArg(false)} \" { framesDir } / % { Padding . inputFrames } d { format } \ "" ; LogMode logMode = Interpolate . currentMediaFile . FrameCount > 50 ? LogMode . OnlyLastLine : LogMode . Hidden ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , logMode , true ) ;
2021-08-23 16:50:18 +02:00
int amount = IoUtils . GetAmountOfFiles ( framesDir , false , "*" + format ) ;
Logger . Log ( $"Extracted {amount} {(amount == 1 ? " frame " : " frames ")} from input." , false , true ) ;
await Task . Delay ( 1 ) ;
if ( delSrc )
DeleteSource ( inputFile ) ;
}
public static async Task ImportImagesCheckCompat ( string inPath , string outPath , bool alpha , Size size , bool showLog , string format )
{
bool compatible = await Task . Run ( async ( ) = > { return AreImagesCompatible ( inPath , Config . GetInt ( Config . Key . maxVidHeight ) ) ; } ) ;
if ( ! alpha & & compatible )
{
await CopyImages ( inPath , outPath , showLog ) ;
}
else
{
await ImportImages ( inPath , outPath , alpha , size , showLog , format ) ;
}
}
2023-12-22 05:20:22 +01:00
public static async Task CopyImages ( string inpath , string outpath , bool showLog )
2021-08-23 16:50:18 +02:00
{
2021-08-31 11:33:18 +02:00
if ( showLog ) Logger . Log ( $"Loading images from {new DirectoryInfo(inpath).Name}..." ) ;
2021-08-23 16:50:18 +02:00
Directory . CreateDirectory ( outpath ) ;
Dictionary < string , string > moveFromTo = new Dictionary < string , string > ( ) ;
int counter = 0 ;
foreach ( FileInfo file in IoUtils . GetFileInfosSorted ( inpath ) )
{
string newFilename = counter . ToString ( ) . PadLeft ( Padding . inputFrames , '0' ) + file . Extension ;
moveFromTo . Add ( file . FullName , Path . Combine ( outpath , newFilename ) ) ;
counter + + ;
}
if ( Config . GetBool ( Config . Key . allowSymlinkEncoding ) & & Config . GetBool ( Config . Key . allowSymlinkImport , true ) )
{
2021-08-31 11:33:18 +02:00
Logger . Log ( $"Symlink Import enabled, creating symlinks for input frames..." , true ) ;
2021-08-23 16:50:18 +02:00
Dictionary < string , string > moveFromToSwapped = moveFromTo . ToDictionary ( x = > x . Value , x = > x . Key ) ; // From/To => To/From (Link/Target)
await Symlinks . CreateSymlinksParallel ( moveFromToSwapped ) ;
}
else
{
2021-08-31 11:33:18 +02:00
Logger . Log ( $"Symlink Import disabled, copying input frames..." , true ) ;
2023-12-22 05:20:22 +01:00
await Task . Run ( async ( ) = >
{
2021-08-23 16:50:18 +02:00
foreach ( KeyValuePair < string , string > moveFromToPair in moveFromTo )
File . Copy ( moveFromToPair . Key , moveFromToPair . Value ) ;
} ) ;
}
}
2023-12-22 05:20:22 +01:00
static bool AreImagesCompatible ( string inpath , int maxHeight )
2021-08-23 16:50:18 +02:00
{
NmkdStopwatch sw = new NmkdStopwatch ( ) ;
string [ ] validExtensions = Filetypes . imagesInterpCompat ; // = new string[] { ".jpg", ".jpeg", ".png" };
FileInfo [ ] files = IoUtils . GetFileInfosSorted ( inpath ) ;
if ( files . Length < 1 )
{
Logger . Log ( "[AreImagesCompatible] Sequence not compatible: No files found." , true ) ;
return false ;
}
bool allSameExtension = files . All ( x = > x . Extension = = files . First ( ) . Extension ) ;
if ( ! allSameExtension )
{
Logger . Log ( $"Sequence not compatible: Not all files have the same extension." , true ) ;
return false ;
}
bool allValidExtension = files . All ( x = > validExtensions . Contains ( x . Extension ) ) ;
if ( ! allValidExtension )
{
Logger . Log ( $"Sequence not compatible: Not all files have a valid extension ({string.Join(" , ", validExtensions)})." , true ) ;
return false ;
}
2021-08-31 11:42:58 +02:00
int sampleCount = Config . GetInt ( Config . Key . imgSeqSampleCount , 10 ) ;
Image [ ] randomSamples = files . OrderBy ( arg = > Guid . NewGuid ( ) ) . Take ( sampleCount ) . Select ( x = > IoUtils . GetImage ( x . FullName ) ) . ToArray ( ) ;
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
if ( files . All ( f = > f ! = null ) )
2021-08-23 16:50:18 +02:00
{
2024-01-16 19:14:26 +01:00
bool allSameSize = randomSamples . All ( i = > i . Size = = randomSamples . First ( ) . Size ) ;
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
if ( ! allSameSize )
{
Logger . Log ( $"Sequence not compatible: Not all images have the same dimensions." , true ) ;
return false ;
}
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
int div = GetModulo ( ) ;
bool allDivBy2 = randomSamples . All ( i = > ( i . Width % div = = 0 ) & & ( i . Height % div = = 0 ) ) ;
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
if ( ! allDivBy2 )
{
Logger . Log ( $"Sequence not compatible: Not all image dimensions are divisible by {div}." , true ) ;
return false ;
}
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
bool allSmallEnough = randomSamples . All ( i = > ( i . Height < = maxHeight ) ) ;
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
if ( ! allSmallEnough )
{
Logger . Log ( $"Sequence not compatible: Image dimensions above max size." , true ) ;
return false ;
}
2021-08-23 16:50:18 +02:00
2024-01-16 19:14:26 +01:00
// bool all24Bit = randomSamples.All(i => (i.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb));
//
// if (!all24Bit)
// {
// Logger.Log($"Sequence not compatible: Some images are not 24-bit (8bpp).", true);
// return false;
// }
2021-08-23 16:50:18 +02:00
}
2022-07-20 18:10:31 +02:00
Interpolate . currentSettings . framesExt = files . First ( ) . Extension ;
2021-12-06 22:46:39 +01:00
Logger . Log ( $"Sequence compatible!" , true ) ;
2021-08-23 16:50:18 +02:00
return true ;
}
public static async Task ImportImages ( string inPath , string outPath , bool alpha , Size size , bool showLog , string format )
{
if ( showLog ) Logger . Log ( $"Importing images from {new DirectoryInfo(inPath).Name}..." ) ;
Logger . Log ( $"ImportImages() - Alpha: {alpha} - Size: {size} - Format: {format}" , true , false , "ffmpeg" ) ;
IoUtils . CreateDir ( outPath ) ;
2022-07-27 14:10:29 +02:00
string concatFile = Path . Combine ( Paths . GetSessionDataPath ( ) , "import-concat-temp.ini" ) ;
2022-07-20 18:10:31 +02:00
FfmpegUtils . CreateConcatFile ( inPath , concatFile , Filetypes . imagesInterpCompat . ToList ( ) ) ;
2021-08-23 16:50:18 +02:00
string inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}" ;
string linksDir = Path . Combine ( concatFile + Paths . symlinksSuffix ) ;
if ( Config . GetBool ( Config . Key . allowSymlinkEncoding , true ) & & Symlinks . SymlinksAllowed ( ) )
{
if ( await Symlinks . MakeSymlinksForEncode ( concatFile , linksDir , Padding . interpFrames ) )
inArg = $"-i \" { linksDir } / % { Padding . interpFrames } d { FfmpegEncode . GetConcatFileExt ( concatFile ) } \ "" ;
}
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
string vf = $"-vf {GetPadFilter()}" ;
2023-01-08 15:44:01 +01:00
string args = $"-r 25 {inArg} {GetImgArgs(format, true, alpha)} {sizeStr} -fps_mode passthrough -start_number 0 {vf} \" { outPath } / % { Padding . inputFrames } d { format } \ "" ;
2021-08-23 16:50:18 +02:00
LogMode logMode = IoUtils . GetAmountOfFiles ( inPath , false ) > 50 ? LogMode . OnlyLastLine : LogMode . Hidden ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , logMode , "panic" ) ;
2021-08-23 16:50:18 +02:00
}
public static string [ ] GetTrimArgs ( )
{
return new string [ ] { GetTrimArg ( true ) , GetTrimArg ( false ) } ;
}
public static string GetTrimArg ( bool input )
{
if ( ! QuickSettingsTab . trimEnabled )
return "" ;
int fastSeekThresh = 180 ;
bool fastSeek = QuickSettingsTab . trimStartSecs > fastSeekThresh ;
string arg = "" ;
if ( input )
{
if ( fastSeek )
arg + = $"-ss {QuickSettingsTab.trimStartSecs - fastSeekThresh}" ;
else
return arg ;
}
else
{
if ( fastSeek )
{
arg + = $"-ss {fastSeekThresh}" ;
long trimTimeSecs = QuickSettingsTab . trimEndSecs - QuickSettingsTab . trimStartSecs ;
if ( QuickSettingsTab . doTrimEnd )
arg + = $" -to {fastSeekThresh + trimTimeSecs}" ;
}
else
{
arg + = $"-ss {QuickSettingsTab.trimStart}" ;
if ( QuickSettingsTab . doTrimEnd )
arg + = $" -to {QuickSettingsTab.trimEnd}" ;
}
}
return arg ;
}
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}" : "" ;
2024-09-03 22:08:38 +02:00
bool isPng = ( Path . GetExtension ( outPath ) . Lower ( ) = = ".png" ) ;
2021-08-23 16:50:18 +02:00
string comprArg = isPng ? pngCompr : "" ;
2024-08-25 21:18:38 +02:00
string pixFmt = isPng ? $"rgb24 {comprArg}" : "yuv420p -color_range full" ;
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} -pix_fmt {pixFmt} -vf {GetPadFilter()} {outPath.Wrap()}" ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , LogMode . Hidden ) ;
2021-08-23 16:50:18 +02:00
}
public static async Task ExtractSingleFrame ( string inputFile , string outputPath , int frameNum )
{
2024-09-03 22:08:38 +02:00
bool isPng = ( Path . GetExtension ( outputPath ) . Lower ( ) = = ".png" ) ;
2021-08-23 16:50:18 +02:00
string comprArg = isPng ? pngCompr : "" ;
2024-08-25 21:18:38 +02:00
string pixFmt = isPng ? $"rgb24 {comprArg}" : "yuv420p -color_range full" ;
string args = $"-i {inputFile.Wrap()} -vf \" select = eq ( n \ \ , { frameNum } ) \ " -vframes 1 -update 1 -pix_fmt {pixFmt} {outputPath.Wrap()}" ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , LogMode . Hidden ) ;
2021-08-23 16:50:18 +02:00
}
public static async Task ExtractLastFrame ( string inputFile , string outputPath , Size size )
{
if ( QuickSettingsTab . trimEnabled )
return ;
if ( IoUtils . IsPathDirectory ( outputPath ) )
outputPath = Path . Combine ( outputPath , "last.png" ) ;
2024-09-03 22:08:38 +02:00
bool isPng = ( Path . GetExtension ( outputPath ) . Lower ( ) = = ".png" ) ;
2021-08-23 16:50:18 +02:00
string comprArg = isPng ? pngCompr : "" ;
2024-08-25 21:18:38 +02:00
string pixFmt = isPng ? $"rgb24 {comprArg}" : "yuv420p -color_range full" ;
2021-08-23 16:50:18 +02:00
string sizeStr = ( size . Width > 1 & & size . Height > 1 ) ? $"-s {size.Width}x{size.Height}" : "" ;
string trim = QuickSettingsTab . trimEnabled ? $"-ss {QuickSettingsTab.GetTrimEndMinusOne()} -to {QuickSettingsTab.trimEnd}" : "" ;
string sseof = string . IsNullOrWhiteSpace ( trim ) ? "-sseof -1" : "" ;
2024-08-25 21:18:38 +02:00
string args = $"{sseof} -i {inputFile.Wrap()} -update 1 -pix_fmt {pixFmt} {sizeStr} {trim} {outputPath.Wrap()}" ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , LogMode . Hidden ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-20 20:58:32 +01:00
2023-12-22 05:20:22 +01:00
public static async Task GeneratePalette ( string inputFile , string outputPath , int colors = 256 )
2023-01-20 20:58:32 +01:00
{
string args = $"-i {inputFile.Wrap()} -vf palettegen={colors} {outputPath.Wrap()}" ;
await Task . Run ( ( ) = > AvProcess . RunFfmpegSync ( args ) ) ;
}
2021-08-23 16:50:18 +02:00
}
}