2021-08-23 16:50:18 +02:00
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.MiscUtils ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
2024-11-08 11:54:26 +01:00
using Win32Interop.Enums ;
2021-08-23 16:50:18 +02:00
using static Flowframes . AvProcess ;
using Utils = Flowframes . Media . FfmpegUtils ;
namespace Flowframes.Media
{
partial class FfmpegEncode : FfmpegCommands
{
2023-01-18 14:55:38 +01:00
public static async Task FramesToVideo ( string framesFile , string outPath , OutputSettings settings , Fraction fps , Fraction resampleFps , float itsScale , VidExtraData extraData , LogMode logMode = LogMode . OnlyLastLine , bool isChunk = false )
2021-08-23 16:50:18 +02:00
{
if ( logMode ! = LogMode . Hidden )
2024-10-13 16:58:06 +02:00
Logger . Log ( ( resampleFps . Float < = 0 ) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS..." ) ;
2021-08-23 16:50:18 +02:00
2023-02-20 19:30:23 +01:00
IoUtils . RenameExistingFileOrDir ( outPath ) ;
2021-08-23 16:50:18 +02:00
Directory . CreateDirectory ( outPath . GetParentDir ( ) ) ;
2024-11-12 22:13:59 +01:00
string [ ] encArgs = Utils . GetEncArgs ( settings , ( Interpolate . currentSettings . OutputResolution . IsEmpty ? Interpolate . currentSettings . InputResolution : Interpolate . currentSettings . OutputResolution ) , Interpolate . currentSettings . outFps . Float ) ;
2021-11-24 13:48:23 +01:00
2021-08-23 16:50:18 +02:00
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}" ;
string linksDir = Path . Combine ( framesFile + Paths . symlinksSuffix ) ;
if ( Config . GetBool ( Config . Key . allowSymlinkEncoding , true ) & & Symlinks . SymlinksAllowed ( ) )
{
if ( await Symlinks . MakeSymlinksForEncode ( framesFile , linksDir , Padding . interpFrames ) )
inArg = $"-i \" { linksDir } / % { Padding . interpFrames } d { GetConcatFileExt ( framesFile ) } \ "" ;
}
2021-11-24 13:48:23 +01:00
string args = "" ;
2023-01-15 17:23:49 +01:00
for ( int i = 0 ; i < encArgs . Length ; i + + )
2021-11-24 13:48:23 +01:00
{
2021-11-24 21:08:29 +01:00
string pre = i = = 0 ? "" : $" && ffmpeg {AvProcess.GetFfmpegDefaultArgs()}" ;
2021-11-25 22:22:45 +01:00
string post = ( i = = 0 & & encArgs . Length > 1 ) ? $"-f null -" : outPath . Wrap ( ) ;
2023-01-31 12:03:31 +01:00
args + = $"{pre} {await GetFfmpegExportArgsIn(fps, itsScale)} {inArg} {encArgs[i]} {await GetFfmpegExportArgsOut(resampleFps, extraData, settings, isChunk)} {post} " ;
2021-11-24 13:48:23 +01:00
}
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , framesFile . GetParentDir ( ) , logMode , ! isChunk ) ;
2021-08-23 16:50:18 +02:00
IoUtils . TryDeleteIfExists ( linksDir ) ;
}
2024-11-07 15:10:36 +01:00
public static async Task < string > GetFfmpegExportArgsIn ( Fraction fps , float itsScale , int rotation = 0 )
2022-08-04 15:30:19 +02:00
{
2023-01-20 20:58:32 +01:00
var args = new List < string > ( ) ;
2022-08-04 15:30:19 +02:00
fps = fps / new Fraction ( itsScale ) ;
2025-03-10 11:58:28 +01:00
if ( fps > 0.1f )
{
args . Add ( $"-r {fps}" ) ;
}
2024-11-07 15:10:36 +01:00
if ( rotation ! = 0 )
{
args . Add ( $"-display_rotation {rotation}" ) ;
}
2023-01-20 20:58:32 +01:00
return string . Join ( " " , args ) ;
2022-08-04 15:30:19 +02:00
}
2023-01-20 20:58:32 +01:00
public static async Task < string > GetFfmpegExportArgsOut ( Fraction resampleFps , VidExtraData extraData , OutputSettings settings , bool isChunk = false )
2021-08-23 16:50:18 +02:00
{
2023-01-20 20:58:32 +01:00
var beforeArgs = new List < string > ( ) ;
var filters = new List < string > ( ) ;
var extraArgs = new List < string > { Config . Get ( Config . Key . ffEncArgs ) } ;
2024-11-07 22:39:48 +01:00
var mf = Interpolate . currentMediaFile ;
2022-07-22 01:26:47 +02:00
2024-10-13 16:58:06 +02:00
if ( resampleFps . Float > = 0.1f )
2024-11-14 20:38:41 +01:00
{
2024-11-26 13:45:44 +01:00
if ( Interpolate . currentMediaFile . IsVfr & & ! Interpolate . currentSettings . dedupe )
2024-11-14 20:38:41 +01:00
{
2024-12-03 00:40:24 +01:00
Logger . Log ( $"Won't add fps filter as VFR handling already outputs at desired frame rate ({resampleFps.Float} FPS)" , true ) ;
2024-11-14 20:38:41 +01:00
}
else
{
filters . Add ( $"fps={resampleFps}" ) ;
}
}
2022-07-22 01:26:47 +02:00
2024-11-07 15:10:36 +01:00
if ( Config . GetBool ( Config . Key . keepColorSpace ) & & extraData . HasAllColorValues ( ) )
2022-07-22 01:26:47 +02:00
{
2023-12-22 03:51:55 +01:00
Logger . Log ( $"Using color data: Space {extraData.colorSpace}; Primaries {extraData.colorPrimaries}; Transfer {extraData.colorTransfer}; Range {extraData.colorRange}" , true , false , "ffmpeg" ) ;
2023-01-20 20:58:32 +01:00
extraArgs . Add ( $"-colorspace {extraData.colorSpace} -color_primaries {extraData.colorPrimaries} -color_trc {extraData.colorTransfer} -color_range:v {extraData.colorRange.Wrap()}" ) ;
2022-07-22 01:26:47 +02:00
}
if ( ! string . IsNullOrWhiteSpace ( extraData . displayRatio ) & & ! extraData . displayRatio . MatchesWildcard ( "*N/A*" ) )
2023-01-20 20:58:32 +01:00
extraArgs . Add ( $"-aspect {extraData.displayRatio}" ) ;
2022-08-05 11:58:12 +02:00
2023-01-20 20:58:32 +01:00
if ( ! isChunk & & settings . Format = = Enums . Output . Format . Mp4 | | settings . Format = = Enums . Output . Format . Mov )
extraArgs . Add ( $"-movflags +faststart" ) ;
2022-07-22 01:26:47 +02:00
2023-01-20 20:58:32 +01:00
if ( settings . Format = = Enums . Output . Format . Gif )
2023-01-18 14:55:38 +01:00
{
string dither = Config . Get ( Config . Key . gifDitherType ) . Split ( ' ' ) . First ( ) ;
2023-01-20 20:58:32 +01:00
string palettePath = Path . Combine ( Paths . GetSessionDataPath ( ) , "palette.png" ) ;
string paletteFilter = $"[1:v]paletteuse=dither={dither}" ;
2023-01-18 14:55:38 +01:00
int colors = OutputUtils . GetGifColors ( ParseUtils . GetEnum < Enums . Encoding . Quality . GifColors > ( settings . Quality , true , Strings . VideoQuality ) ) ;
2024-11-07 22:39:48 +01:00
await FfmpegExtract . GeneratePalette ( mf . ImportPath , palettePath , colors ) ;
2023-01-20 20:58:32 +01:00
if ( File . Exists ( palettePath ) )
{
beforeArgs . Add ( $"-i {palettePath.Wrap()}" ) ;
filters . Add ( paletteFilter ) ;
}
2023-01-18 14:55:38 +01:00
}
2024-01-10 01:50:23 +01:00
else if ( settings . Encoder = = Enums . Encoding . Encoder . Exr )
{
2024-11-07 22:39:48 +01:00
if ( mf . Format . Upper ( ) ! = "EXR" )
2024-06-24 11:36:43 +02:00
filters . Add ( $"zscale=transfer=linear,format={settings.PixelFormat.ToString().Lower()}" . Wrap ( ) ) ;
2024-01-10 01:50:23 +01:00
}
2023-01-18 14:55:38 +01:00
2024-11-12 22:13:59 +01:00
filters . Add ( GetPadFilter ( Interpolate . currentSettings . ScaledResolution . Width , Interpolate . currentSettings . ScaledResolution . Height ) ) ;
2023-03-30 14:13:39 +02:00
filters = filters . Where ( f = > f . IsNotEmpty ( ) ) . ToList ( ) ;
2023-01-20 20:58:32 +01:00
return filters . Count > 0 ?
2024-11-08 10:04:39 +01:00
$"{string.Join(" ", beforeArgs)} -filter_complex [0:v]{string.Join(" [ vf ] , [ vf ] ", filters)}[vf] -map [vf] {string.Join(" ", extraArgs)}" :
2023-01-20 20:58:32 +01:00
$"{string.Join(" ", beforeArgs)} {string.Join(" ", extraArgs)}" ;
2021-08-23 16:50:18 +02:00
}
2023-01-15 17:23:49 +01:00
public static string GetConcatFileExt ( string concatFilePath )
2021-08-23 16:50:18 +02:00
{
2022-07-22 01:26:47 +02:00
return Path . GetExtension ( File . ReadAllLines ( concatFilePath ) . FirstOrDefault ( ) . Split ( '\'' ) [ 1 ] ) ;
2021-08-23 16:50:18 +02:00
}
2023-02-20 19:30:23 +01:00
public static async Task FramesToFrames ( string framesFile , string outDir , int startNo , Fraction fps , Fraction resampleFps , Enums . Encoding . Encoder format = Enums . Encoding . Encoder . Png , int lossyQ = 1 , LogMode logMode = LogMode . OnlyLastLine )
2021-08-23 16:50:18 +02:00
{
Directory . CreateDirectory ( outDir ) ;
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}" ;
string linksDir = Path . Combine ( framesFile + Paths . symlinksSuffix ) ;
if ( Config . GetBool ( Config . Key . allowSymlinkEncoding , true ) & & Symlinks . SymlinksAllowed ( ) )
{
if ( await Symlinks . MakeSymlinksForEncode ( framesFile , linksDir , Padding . interpFrames ) )
inArg = $"-i {Path.GetFileName(framesFile) + Paths.symlinksSuffix}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}" ;
}
2024-11-08 11:54:26 +01:00
var ffArgs = new List < string > ( )
{
$"-r {fps.ToString().Replace(" , ", " . ")}" , // Rate
inArg ,
format = = Enums . Encoding . Encoder . Webp ? "-c:v libwebp" : "" , // Codec - Specify libwebp to avoid putting all frames into single animated WEBP
format = = Enums . Encoding . Encoder . Png ? pngCompr : $"-q:v {lossyQ}" , // Compression
$"-start_number {startNo}" ,
resampleFps . Float < 0.1f ? "" : $"-vf fps=fps={resampleFps}" , // FPS Resample
"-fps_mode passthrough" ,
$"{outDir}/%{Padding.interpFrames}d.{format.GetInfo().OverideExtension}" . Wrap ( ) ,
} ;
await RunFfmpeg ( string . Join ( " " , ffArgs . Where ( s = > s . IsNotEmpty ( ) ) ) , framesFile . GetParentDir ( ) , logMode , "error" , true ) ;
2021-08-23 16:50:18 +02:00
IoUtils . TryDeleteIfExists ( linksDir ) ;
}
2021-08-28 10:21:52 +02:00
public static async Task FramesToGifConcat ( string framesFile , string outPath , Fraction rate , bool palette , int colors , Fraction resampleFps , float itsScale , LogMode logMode = LogMode . OnlyLastLine )
2021-08-23 16:50:18 +02:00
{
2024-10-13 16:58:06 +02:00
if ( rate . Float > 50f & & ( resampleFps . Float > 50f | | resampleFps . Float < 1 ) )
2021-08-23 16:50:18 +02:00
resampleFps = new Fraction ( 50 , 1 ) ; // Force limit framerate as encoding above 50 will cause problems
if ( logMode ! = LogMode . Hidden )
2024-10-13 16:58:06 +02:00
Logger . Log ( ( resampleFps . Float < = 0 ) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.Float.ToString().Replace(" , ", " . ")} FPS..." ) ;
2023-01-15 17:23:49 +01:00
2021-08-23 16:50:18 +02:00
string framesFilename = Path . GetFileName ( framesFile ) ;
string dither = Config . Get ( Config . Key . gifDitherType ) . Split ( ' ' ) . First ( ) ;
string paletteFilter = palette ? $"-vf \" split [ s0 ] [ s1 ] ; [ s0 ] palettegen = { colors } [ p ] ; [ s1 ] [ p ] paletteuse = dither = { dither } \ "" : "" ;
2024-10-13 16:58:06 +02:00
string fpsFilter = ( resampleFps . Float < = 0 ) ? "" : $"fps=fps={resampleFps}" ;
2021-08-23 16:50:18 +02:00
string vf = FormatUtils . ConcatStrings ( new string [ ] { paletteFilter , fpsFilter } ) ;
string extraArgs = Config . Get ( Config . Key . ffEncArgs ) ;
2021-08-28 10:21:52 +02:00
rate = rate / new Fraction ( itsScale ) ;
2021-08-23 16:50:18 +02:00
string args = $"-f concat -r {rate} -i {framesFilename.Wrap()} -gifflags -offsetting {vf} {extraArgs} {outPath.Wrap()}" ;
2021-12-06 22:46:39 +01:00
await RunFfmpeg ( args , framesFile . GetParentDir ( ) , LogMode . OnlyLastLine , "error" ) ;
2021-08-23 16:50:18 +02:00
}
}
}