2021-08-23 16:50:18 +02:00
using Flowframes.IO ;
using Flowframes.Magick ;
using System ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using Padding = Flowframes . Data . Padding ;
using I = Flowframes . Interpolate ;
using System.Diagnostics ;
using Flowframes.Data ;
using Flowframes.Media ;
using Flowframes.MiscUtils ;
using Flowframes.Os ;
2021-09-28 20:44:08 +02:00
using System.Collections.Generic ;
using Newtonsoft.Json ;
2022-07-24 22:30:30 +02:00
using Flowframes.Ui ;
2021-08-23 16:50:18 +02:00
namespace Flowframes.Main
{
2021-09-12 14:23:35 +02:00
class Export
2021-08-23 16:50:18 +02:00
{
2024-11-28 16:08:04 +01:00
private static Fraction MaxFpsFrac = > I . currentSettings . outFpsResampled ;
2021-08-23 16:50:18 +02:00
2023-01-18 14:55:38 +01:00
public static async Task ExportFrames ( string path , string outFolder , OutputSettings exportSettings , bool stepByStep )
2021-08-23 16:50:18 +02:00
{
2023-02-20 19:30:23 +01:00
if ( Config . GetInt ( Config . Key . sceneChangeFillMode ) = = 1 )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
string frameFile = Path . Combine ( I . currentSettings . tempFolder , Paths . GetFrameOrderFilename ( I . currentSettings . interpFactor ) ) ;
2021-08-23 16:50:18 +02:00
await Blend . BlendSceneChanges ( frameFile ) ;
}
2023-02-20 19:30:23 +01:00
2023-01-15 17:23:49 +01:00
if ( exportSettings . Encoder . GetInfo ( ) . IsImageSequence ) // Copy interp frames out of temp folder and skip video export for image seq export
2021-08-23 16:50:18 +02:00
{
try
{
2021-09-12 14:23:35 +02:00
await ExportImageSequence ( path , stepByStep ) ;
2021-08-23 16:50:18 +02:00
}
catch ( Exception e )
{
Logger . Log ( "Failed to move interpolated frames: " + e . Message ) ;
Logger . Log ( "Stack Trace:\n " + e . StackTrace , true ) ;
}
return ;
}
2022-07-20 18:10:31 +02:00
if ( IoUtils . GetAmountOfFiles ( path , false , "*" + I . currentSettings . interpExt ) < = 1 )
2021-08-23 16:50:18 +02:00
{
I . Cancel ( "Output folder does not contain frames - An error must have occured during interpolation!" , AiProcess . hasShownError ) ;
return ;
}
Program . mainForm . SetStatus ( "Creating output video from frames..." ) ;
try
{
2024-11-08 11:54:26 +01:00
bool fpsLimit = MaxFpsFrac . Float > 0f & & I . currentSettings . outFps . Float > MaxFpsFrac . Float ;
2021-08-23 16:50:18 +02:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( Config . Key . maxFpsMode ) = = 0 ;
2024-11-08 11:54:26 +01:00
string exportPath = Path . Combine ( outFolder , await IoUtils . GetCurrentExportFilename ( fpsLimit ) ) ;
2021-08-23 16:50:18 +02:00
if ( ! dontEncodeFullFpsVid )
2024-11-08 11:54:26 +01:00
await Encode ( exportSettings , path , exportPath , I . currentSettings . outFps , new Fraction ( ) ) ;
2021-08-23 16:50:18 +02:00
if ( fpsLimit )
2024-11-08 11:54:26 +01:00
await Encode ( exportSettings , path , exportPath , I . currentSettings . outFps , MaxFpsFrac ) ;
2021-08-23 16:50:18 +02:00
}
catch ( Exception e )
{
2024-11-28 16:08:04 +01:00
Logger . Log ( $"{nameof(ExportFrames)} Error: {e.Message}" , false ) ;
2022-07-24 22:30:30 +02:00
UiUtils . ShowMessageBox ( "An error occured while trying to convert the interpolated frames to a video.\nCheck the log for details." , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
}
}
2024-11-29 10:27:18 +01:00
private const bool _useNutPipe = true ;
2022-08-04 23:02:52 +02:00
public static async Task < string > GetPipedFfmpegCmd ( bool ffplay = false )
2022-07-22 01:26:47 +02:00
{
2022-08-04 23:02:52 +02:00
InterpSettings s = I . currentSettings ;
2024-11-12 22:13:59 +01:00
string encArgs = FfmpegUtils . GetEncArgs ( s . outSettings , ( s . OutputResolution . IsEmpty ? s . InputResolution : s . OutputResolution ) , s . outFps . Float , true ) . FirstOrDefault ( ) ;
2024-11-08 11:54:26 +01:00
bool fpsLimit = MaxFpsFrac . Float > 0f & & s . outFps . Float > MaxFpsFrac . Float ;
2023-12-26 22:59:17 +01:00
bool gifInput = I . currentMediaFile . Format . Upper ( ) = = "GIF" ; // If input is GIF, we don't need to check the color space etc
VidExtraData extraData = gifInput ? new VidExtraData ( ) : await FfmpegCommands . GetVidExtraInfo ( s . inPath ) ;
2024-11-29 10:27:18 +01:00
string extraArgsIn = await FfmpegEncode . GetFfmpegExportArgsIn ( I . currentMediaFile . IsVfr ? s . outFpsResampled : s . outFps , s . outItsScale , extraData . Rotation ) ;
2024-11-08 11:54:26 +01:00
string extraArgsOut = await FfmpegEncode . GetFfmpegExportArgsOut ( fpsLimit ? MaxFpsFrac : new Fraction ( ) , extraData , s . outSettings ) ;
2022-07-22 01:26:47 +02:00
2024-11-08 11:54:26 +01:00
// For EXR, force bt709 input flags. Not sure if this really does anything, EXR
if ( s . outSettings . Encoder = = Enums . Encoding . Encoder . Exr )
2024-01-10 01:50:23 +01:00
{
extraArgsIn + = " -color_trc bt709 -color_primaries bt709 -colorspace bt709" ;
}
2022-08-04 23:02:52 +02:00
if ( ffplay )
{
2024-11-29 10:27:18 +01:00
encArgs = _useNutPipe ? "-c:v rawvideo -pix_fmt rgba" : $"-pix_fmt yuv444p16" ;
string format = _useNutPipe ? "nut" : "yuv4mpegpipe" ;
2022-07-22 01:26:47 +02:00
2023-02-20 19:30:23 +01:00
return
2023-12-26 23:28:21 +01:00
$"{extraArgsIn} -i pipe: {encArgs} {extraArgsOut} -f {format} - | ffplay - " +
2022-08-04 23:02:52 +02:00
$"-autoexit -seek_interval {VapourSynthUtils.GetSeekSeconds(Program.mainForm.currInDuration)} " +
$"-window_title \" Flowframes Realtime Interpolation ( { s . inFps . GetString ( ) } FPS x { s . interpFactor } = { s . outFps . GetString ( ) } FPS ) ( { s . model . Name } ) \ " " ;
}
else
{
2023-02-20 19:30:23 +01:00
bool imageSequence = s . outSettings . Encoder . GetInfo ( ) . IsImageSequence ;
2024-11-08 11:54:26 +01:00
s . FullOutPath = Path . Combine ( s . outPath , await IoUtils . GetCurrentExportFilename ( fpsLimit , isImgSeq : imageSequence ) ) ;
2023-02-20 19:30:23 +01:00
IoUtils . RenameExistingFileOrDir ( s . FullOutPath ) ;
if ( imageSequence )
{
Directory . CreateDirectory ( s . FullOutPath ) ;
s . FullOutPath + = $"/%{Padding.interpFrames}d.{s.outSettings.Encoder.GetInfo().OverideExtension}" ;
}
2023-01-20 20:58:32 +01:00
return $"{extraArgsIn} -i pipe: {extraArgsOut} {encArgs} {s.FullOutPath.Wrap()}" ;
2022-08-04 23:02:52 +02:00
}
2022-07-22 01:26:47 +02:00
}
2023-02-20 19:30:23 +01:00
static async Task ExportImageSequence ( string framesPath , bool stepByStep )
2021-08-23 16:50:18 +02:00
{
Program . mainForm . SetStatus ( "Copying output frames..." ) ;
2023-02-20 19:30:23 +01:00
Enums . Encoding . Encoder desiredFormat = I . currentSettings . outSettings . Encoder ;
2024-09-03 22:08:38 +02:00
string availableFormat = Path . GetExtension ( IoUtils . GetFilesSorted ( framesPath , "*.*" ) [ 0 ] ) . Remove ( "." ) . Upper ( ) ;
2024-11-08 11:54:26 +01:00
2024-11-28 16:08:04 +01:00
bool fpsLimit = MaxFpsFrac . Float > 0f & & I . currentSettings . outFps . Float > MaxFpsFrac . Float ;
2024-11-08 11:54:26 +01:00
bool encodeFullFpsSeq = ! ( fpsLimit & & Config . GetInt ( Config . Key . maxFpsMode ) = = 0 ) ;
2022-07-20 18:10:31 +02:00
string framesFile = Path . Combine ( framesPath . GetParentDir ( ) , Paths . GetFrameOrderFilename ( I . currentSettings . interpFactor ) ) ;
2021-08-23 16:50:18 +02:00
2024-11-08 11:54:26 +01:00
if ( encodeFullFpsSeq )
2021-08-23 16:50:18 +02:00
{
2024-11-08 11:54:26 +01:00
string outputFolderPath = Path . Combine ( I . currentSettings . outPath , await IoUtils . GetCurrentExportFilename ( fpsLimit : false , isImgSeq : true ) ) ;
2021-08-23 16:50:18 +02:00
IoUtils . RenameExistingFolder ( outputFolderPath ) ;
2023-02-20 19:30:23 +01:00
Logger . Log ( $"Exporting {desiredFormat.ToString().Upper()} frames to '{Path.GetFileName(outputFolderPath)}'..." ) ;
2021-08-23 16:50:18 +02:00
2024-09-03 22:08:38 +02:00
if ( desiredFormat . GetInfo ( ) . OverideExtension . Upper ( ) = = availableFormat . Upper ( ) ) // Move if frames are already in the desired format
2021-08-31 01:08:21 +02:00
await CopyOutputFrames ( framesPath , framesFile , outputFolderPath , 1 , fpsLimit , false ) ;
2021-08-31 00:43:22 +02:00
else // Encode if frames are not in desired format
2023-02-20 19:30:23 +01:00
await FfmpegEncode . FramesToFrames ( framesFile , outputFolderPath , 1 , I . currentSettings . outFps , new Fraction ( ) , desiredFormat , OutputUtils . GetImgSeqQ ( I . currentSettings . outSettings ) ) ;
2021-08-23 16:50:18 +02:00
}
2023-02-20 19:30:23 +01:00
2021-08-23 16:50:18 +02:00
if ( fpsLimit )
{
2024-11-08 11:54:26 +01:00
string outputFolderPath = Path . Combine ( I . currentSettings . outPath , await IoUtils . GetCurrentExportFilename ( fpsLimit : true , isImgSeq : true ) ) ;
2024-11-28 16:08:04 +01:00
Logger . Log ( $"Exporting {desiredFormat.ToString().Upper()} frames to '{Path.GetFileName(outputFolderPath)}' (Resampled to {MaxFpsFrac} FPS)..." ) ;
await FfmpegEncode . FramesToFrames ( framesFile , outputFolderPath , 1 , I . currentSettings . outFps , MaxFpsFrac , desiredFormat , OutputUtils . GetImgSeqQ ( I . currentSettings . outSettings ) ) ;
2021-08-23 16:50:18 +02:00
}
if ( ! stepByStep )
2022-07-20 18:10:31 +02:00
await IoUtils . DeleteContentsOfDirAsync ( I . currentSettings . interpFolder ) ;
2021-08-23 16:50:18 +02:00
}
2021-08-31 01:08:21 +02:00
static async Task CopyOutputFrames ( string framesPath , string framesFile , string outputFolderPath , int startNo , bool dontMove , bool hideLog )
2021-08-23 16:50:18 +02:00
{
IoUtils . CreateDir ( outputFolderPath ) ;
Stopwatch sw = new Stopwatch ( ) ;
sw . Restart ( ) ;
string [ ] framesLines = IoUtils . ReadLines ( framesFile ) ;
for ( int idx = 1 ; idx < = framesLines . Length ; idx + + )
{
string line = framesLines [ idx - 1 ] ;
string inFilename = line . RemoveComments ( ) . Split ( '/' ) . Last ( ) . Remove ( "'" ) . Trim ( ) ;
string framePath = Path . Combine ( framesPath , inFilename ) ;
2021-08-31 01:08:21 +02:00
string outFilename = Path . Combine ( outputFolderPath , startNo . ToString ( ) . PadLeft ( Padding . interpFrames , '0' ) ) + Path . GetExtension ( framePath ) ;
startNo + + ;
2021-08-23 16:50:18 +02:00
if ( dontMove | | ( ( idx < framesLines . Length ) & & framesLines [ idx ] . Contains ( inFilename ) ) ) // If file is re-used in the next line, copy instead of move
File . Copy ( framePath , outFilename ) ;
else
File . Move ( framePath , outFilename ) ;
if ( sw . ElapsedMilliseconds > = 500 | | idx = = framesLines . Length )
{
sw . Restart ( ) ;
2021-08-31 01:08:21 +02:00
Logger . Log ( $"Moving output frames... {idx}/{framesLines.Length}" , hideLog , true ) ;
2021-08-23 16:50:18 +02:00
await Task . Delay ( 1 ) ;
}
}
}
2023-01-18 14:55:38 +01:00
static async Task Encode ( OutputSettings settings , string framesPath , string outPath , Fraction fps , Fraction resampleFps )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
string framesFile = Path . Combine ( framesPath . GetParentDir ( ) , Paths . GetFrameOrderFilename ( I . currentSettings . interpFactor ) ) ;
2021-08-23 16:50:18 +02:00
if ( ! File . Exists ( framesFile ) )
{
bool sbs = Config . GetInt ( Config . Key . processingMode ) = = 1 ;
I . Cancel ( $"Frame order file for this interpolation factor not found!{(sbs ? " \ n \ nDid you run the interpolation step with the current factor ? " : " ")}" ) ;
return ;
}
2023-01-15 17:23:49 +01:00
if ( settings . Format = = Enums . Output . Format . Gif )
2021-08-23 16:50:18 +02:00
{
2023-01-18 14:55:38 +01:00
int paletteColors = OutputUtils . GetGifColors ( ParseUtils . GetEnum < Enums . Encoding . Quality . GifColors > ( settings . Quality , true , Strings . VideoQuality ) ) ;
await FfmpegEncode . FramesToGifConcat ( framesFile , outPath , fps , true , paletteColors , resampleFps , I . currentSettings . outItsScale ) ;
2021-08-23 16:50:18 +02:00
}
else
{
2022-07-20 18:10:31 +02:00
VidExtraData extraData = await FfmpegCommands . GetVidExtraInfo ( I . currentSettings . inPath ) ;
2023-01-15 17:23:49 +01:00
await FfmpegEncode . FramesToVideo ( framesFile , outPath , settings , fps , resampleFps , I . currentSettings . outItsScale , extraData ) ;
2022-07-20 18:10:31 +02:00
await MuxOutputVideo ( I . currentSettings . inPath , outPath ) ;
2024-11-29 10:27:18 +01:00
await Loop ( outPath , GetLoopTimes ( ) ) ;
2021-08-23 16:50:18 +02:00
}
}
2023-02-20 19:30:23 +01:00
public static async Task MuxPipedVideo ( string inputVideo , string outputPath )
2022-05-31 22:17:22 +02:00
{
await MuxOutputVideo ( inputVideo , Path . Combine ( outputPath , outputPath ) ) ;
2024-11-29 10:27:18 +01:00
await Loop ( outputPath , GetLoopTimes ( ) ) ;
2022-05-31 22:17:22 +02:00
}
2021-09-30 00:33:19 +02:00
public static async Task ChunksToVideo ( string tempFolder , string chunksFolder , string baseOutPath , bool isBackup = false )
2021-08-23 16:50:18 +02:00
{
2023-01-15 17:23:49 +01:00
if ( IoUtils . GetAmountOfFiles ( chunksFolder , true , "*" + FfmpegUtils . GetExt ( I . currentSettings . outSettings ) ) < 1 )
2021-08-23 16:50:18 +02:00
{
I . Cancel ( "No video chunks found - An error must have occured during chunk encoding!" , AiProcess . hasShownError ) ;
return ;
}
2023-02-20 19:30:23 +01:00
NmkdStopwatch sw = new NmkdStopwatch ( ) ;
2021-08-23 16:50:18 +02:00
2023-02-20 19:30:23 +01:00
if ( ! isBackup )
2021-08-23 16:50:18 +02:00
Program . mainForm . SetStatus ( "Merging video chunks..." ) ;
try
{
DirectoryInfo chunksDir = new DirectoryInfo ( chunksFolder ) ;
foreach ( DirectoryInfo dir in chunksDir . GetDirectories ( ) )
{
string suffix = dir . Name . Replace ( "chunks" , "" ) ;
string tempConcatFile = Path . Combine ( tempFolder , $"chunks-concat{suffix}.ini" ) ;
string concatFileContent = "" ;
foreach ( string vid in IoUtils . GetFilesSorted ( dir . FullName ) )
concatFileContent + = $"file '{Paths.chunksDir}/{dir.Name}/{Path.GetFileName(vid)}'\n" ;
File . WriteAllText ( tempConcatFile , concatFileContent ) ;
Logger . Log ( $"CreateVideo: Running MergeChunks() for frames file '{Path.GetFileName(tempConcatFile)}'" , true ) ;
bool fpsLimit = dir . Name . Contains ( Paths . fpsLimitSuffix ) ;
2024-11-08 11:54:26 +01:00
string outPath = Path . Combine ( baseOutPath , await IoUtils . GetCurrentExportFilename ( fpsLimit ) ) ;
2021-08-23 16:50:18 +02:00
await MergeChunks ( tempConcatFile , outPath , isBackup ) ;
if ( ! isBackup )
2024-08-25 21:18:38 +02:00
await IoUtils . TryDeleteIfExistsAsync ( IoUtils . FilenameSuffix ( outPath , Paths . backupSuffix ) ) ;
2021-08-23 16:50:18 +02:00
}
}
catch ( Exception e )
{
Logger . Log ( "ChunksToVideo Error: " + e . Message , isBackup ) ;
if ( ! isBackup )
2022-07-24 22:30:30 +02:00
UiUtils . ShowMessageBox ( "An error occured while trying to merge the video chunks.\nCheck the log for details." , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
}
2021-12-06 22:46:39 +01:00
Logger . Log ( $"Merged video chunks in {sw}" , true ) ;
2021-08-23 16:50:18 +02:00
}
static async Task MergeChunks ( string framesFile , string outPath , bool isBackup = false )
{
if ( isBackup )
{
outPath = IoUtils . FilenameSuffix ( outPath , Paths . backupSuffix ) ;
await IoUtils . TryDeleteIfExistsAsync ( outPath ) ;
2023-02-20 19:30:23 +01:00
}
2021-08-23 16:50:18 +02:00
await FfmpegCommands . ConcatVideos ( framesFile , outPath , - 1 , ! isBackup ) ;
2023-02-20 19:30:23 +01:00
if ( ! isBackup | | ( isBackup & & Config . GetInt ( Config . Key . autoEncBackupMode ) = = 2 ) ) // Mux if no backup, or if backup AND muxing is enabled for backups
2022-07-20 18:10:31 +02:00
await MuxOutputVideo ( I . currentSettings . inPath , outPath , isBackup , ! isBackup ) ;
2021-08-23 16:50:18 +02:00
2023-02-20 19:30:23 +01:00
if ( ! isBackup )
2024-11-29 10:27:18 +01:00
await Loop ( outPath , GetLoopTimes ( ) ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-18 14:55:38 +01:00
public static async Task EncodeChunk ( string outPath , string interpDir , int chunkNo , OutputSettings settings , int firstFrameNum , int framesAmount )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
string framesFileFull = Path . Combine ( I . currentSettings . tempFolder , Paths . GetFrameOrderFilename ( I . currentSettings . interpFactor ) ) ;
string concatFile = Path . Combine ( I . currentSettings . tempFolder , Paths . GetFrameOrderFilenameChunk ( firstFrameNum , firstFrameNum + framesAmount ) ) ;
2021-09-12 14:22:03 +02:00
File . WriteAllLines ( concatFile , IoUtils . ReadLines ( framesFileFull ) . Skip ( firstFrameNum ) . Take ( framesAmount ) ) ;
2021-08-23 16:50:18 +02:00
2021-09-28 20:44:08 +02:00
List < string > inputFrames = JsonConvert . DeserializeObject < List < string > > ( File . ReadAllText ( framesFileFull + ".inputframes.json" ) ) . Skip ( firstFrameNum ) . Take ( framesAmount ) . ToList ( ) ;
2021-08-23 16:50:18 +02:00
if ( Config . GetInt ( Config . Key . sceneChangeFillMode ) = = 1 )
2021-09-12 14:22:03 +02:00
await Blend . BlendSceneChanges ( concatFile , false ) ;
2021-08-23 16:50:18 +02:00
2024-11-08 11:54:26 +01:00
bool fpsLimit = MaxFpsFrac . Float ! = 0 & & I . currentSettings . outFps . Float > MaxFpsFrac . Float ;
2022-07-20 18:10:31 +02:00
VidExtraData extraData = await FfmpegCommands . GetVidExtraInfo ( I . currentSettings . inPath ) ;
2021-08-23 16:50:18 +02:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( Config . Key . maxFpsMode ) = = 0 ;
2023-01-15 17:23:49 +01:00
if ( settings . Encoder . GetInfo ( ) . IsImageSequence ) // Image Sequence output mode, not video
2021-08-31 00:43:22 +02:00
{
2023-02-20 19:30:23 +01:00
string desiredFormat = settings . Encoder . GetInfo ( ) . OverideExtension ;
2024-09-03 22:08:38 +02:00
string availableFormat = Path . GetExtension ( IoUtils . GetFilesSorted ( interpDir ) [ 0 ] ) . Remove ( "." ) . Upper ( ) ;
2021-08-31 00:43:22 +02:00
if ( ! dontEncodeFullFpsVid )
{
2024-11-08 11:54:26 +01:00
string outFolderPath = Path . Combine ( I . currentSettings . outPath , await IoUtils . GetCurrentExportFilename ( fpsLimit : false , isImgSeq : true ) ) ;
2021-09-12 14:22:03 +02:00
int startNo = IoUtils . GetAmountOfFiles ( outFolderPath , false ) + 1 ;
2021-08-31 00:43:22 +02:00
2023-02-20 19:30:23 +01:00
if ( chunkNo = = 1 ) // Only check for existing folder on first chunk, otherwise each chunk makes a new folder
2021-09-12 14:22:03 +02:00
IoUtils . RenameExistingFolder ( outFolderPath ) ;
2021-08-31 00:47:06 +02:00
2024-09-03 22:08:38 +02:00
if ( desiredFormat . Upper ( ) = = availableFormat . Upper ( ) ) // Move if frames are already in the desired format
2021-09-12 14:22:03 +02:00
await CopyOutputFrames ( interpDir , concatFile , outFolderPath , startNo , fpsLimit , true ) ;
2021-08-31 00:43:22 +02:00
else // Encode if frames are not in desired format
2023-02-20 19:30:23 +01:00
await FfmpegEncode . FramesToFrames ( concatFile , outFolderPath , startNo , I . currentSettings . outFps , new Fraction ( ) , settings . Encoder , OutputUtils . GetImgSeqQ ( settings ) , AvProcess . LogMode . Hidden ) ;
2021-08-31 00:43:22 +02:00
}
if ( fpsLimit )
{
2024-11-08 11:54:26 +01:00
string outputFolderPath = Path . Combine ( I . currentSettings . outPath , await IoUtils . GetCurrentExportFilename ( fpsLimit : true , isImgSeq : true ) ) ;
2021-08-31 00:43:22 +02:00
int startNumber = IoUtils . GetAmountOfFiles ( outputFolderPath , false ) + 1 ;
2024-11-08 11:54:26 +01:00
await FfmpegEncode . FramesToFrames ( concatFile , outputFolderPath , startNumber , I . currentSettings . outFps , MaxFpsFrac , settings . Encoder , OutputUtils . GetImgSeqQ ( settings ) , AvProcess . LogMode . Hidden ) ;
2021-08-31 00:43:22 +02:00
}
}
else
2021-08-23 16:50:18 +02:00
{
2021-08-31 00:43:22 +02:00
if ( ! dontEncodeFullFpsVid )
2023-01-15 17:23:49 +01:00
await FfmpegEncode . FramesToVideo ( concatFile , outPath , settings , I . currentSettings . outFps , new Fraction ( ) , I . currentSettings . outItsScale , extraData , AvProcess . LogMode . Hidden , true ) ; // Encode
2021-08-31 00:43:22 +02:00
if ( fpsLimit )
{
string filename = Path . GetFileName ( outPath ) ;
string newParentDir = outPath . GetParentDir ( ) + Paths . fpsLimitSuffix ;
outPath = Path . Combine ( newParentDir , filename ) ;
2024-11-08 11:54:26 +01:00
await FfmpegEncode . FramesToVideo ( concatFile , outPath , settings , I . currentSettings . outFps , MaxFpsFrac , I . currentSettings . outItsScale , extraData , AvProcess . LogMode . Hidden , true ) ; // Encode with limited fps
2021-08-31 00:43:22 +02:00
}
2021-08-23 16:50:18 +02:00
}
2021-09-28 20:44:08 +02:00
AutoEncodeResume . encodedChunks + = 1 ;
AutoEncodeResume . encodedFrames + = framesAmount ;
AutoEncodeResume . processedInputFrames . AddRange ( inputFrames ) ;
2021-08-23 16:50:18 +02:00
}
static async Task Loop ( string outPath , int looptimes )
{
2024-12-03 00:40:24 +01:00
if ( looptimes < 1 | | ! Config . GetBool ( Config . Key . enableLoop ) )
return ;
2021-08-23 16:50:18 +02:00
Logger . Log ( $"Looping {looptimes} {(looptimes == 1 ? " time " : " times ")} to reach target length of {Config.GetInt(Config.Key.minOutVidLength)}s..." ) ;
await FfmpegCommands . LoopVideo ( outPath , looptimes , Config . GetInt ( Config . Key . loopMode ) = = 0 ) ;
}
2024-11-29 10:27:18 +01:00
private static int GetLoopTimes ( )
2021-08-23 16:50:18 +02:00
{
int times = - 1 ;
int minLength = Config . GetInt ( Config . Key . minOutVidLength ) ;
2024-10-13 16:58:06 +02:00
int minFrameCount = ( minLength * I . currentSettings . outFps . Float ) . RoundToInt ( ) ;
2022-07-20 18:10:31 +02:00
int outFrames = ( I . currentMediaFile . FrameCount * I . currentSettings . interpFactor ) . RoundToInt ( ) ;
2024-10-13 16:58:06 +02:00
if ( outFrames / I . currentSettings . outFps . Float < minLength )
2024-12-03 00:40:24 +01:00
times = ( int ) Math . Ceiling ( ( double ) minFrameCount / outFrames ) ;
2021-08-23 16:50:18 +02:00
times - - ; // Not counting the 1st play (0 loops)
if ( times < = 0 ) return - 1 ; // Never try to loop 0 times, idk what would happen, probably nothing
return times ;
}
public static async Task MuxOutputVideo ( string inputPath , string outVideo , bool shortest = false , bool showLog = true )
{
if ( ! File . Exists ( outVideo ) )
{
I . Cancel ( $"No video was encoded!\n\nFFmpeg Output:\n{AvProcess.lastOutputFfmpeg}" ) ;
return ;
}
if ( ! Config . GetBool ( Config . Key . keepAudio ) & & ! Config . GetBool ( Config . Key . keepAudio ) )
return ;
2023-02-20 19:30:23 +01:00
if ( showLog )
2021-08-23 16:50:18 +02:00
Program . mainForm . SetStatus ( "Muxing audio/subtitles into video..." ) ;
2022-07-20 18:10:31 +02:00
if ( I . currentSettings . inputIsFrames )
2021-08-23 16:50:18 +02:00
{
2024-11-29 10:27:18 +01:00
Logger . Log ( "Skipping muxing additional streams from input as there is no input video, only frames." , true ) ;
return ;
}
if ( I . currentMediaFile . Format . Upper ( ) = = "GIF" )
{
Logger . Log ( "Skipping muxing additional streams from input as GIF can't have any audio or subtitles to copy" , true ) ;
2021-08-23 16:50:18 +02:00
return ;
}
try
{
2022-07-20 18:10:31 +02:00
await FfmpegAudioAndMetadata . MergeStreamsFromInput ( inputPath , outVideo , I . currentSettings . tempFolder , shortest ) ;
2021-08-23 16:50:18 +02:00
}
catch ( Exception e )
{
Logger . Log ( "Failed to merge audio/subtitles with output video!" , ! showLog ) ;
2024-11-29 10:27:18 +01:00
Logger . Log ( $"{nameof(MuxOutputVideo)} Exception: {e.Message}" , true ) ;
2021-08-23 16:50:18 +02:00
}
}
2024-11-07 15:10:36 +01:00
2024-11-08 11:54:26 +01:00
public static void MuxTimestamps ( string vidPath )
2024-11-07 15:10:36 +01:00
{
2024-11-26 13:45:44 +01:00
if ( I . currentSettings . dedupe )
{
2024-11-28 16:08:04 +01:00
Logger . Log ( $"{nameof(MuxTimestamps)}: Dedupe was used; won't mux timestamps." , hidden : true ) ;
2024-11-26 13:45:44 +01:00
return ;
}
2024-11-28 16:08:04 +01:00
if ( I . currentMediaFile . IsVfr & & I . currentMediaFile . OutputFrameIndexes ! = null & & I . currentMediaFile . OutputFrameIndexes . Count > 0 )
2024-11-07 15:10:36 +01:00
{
2024-11-28 16:08:04 +01:00
Logger . Log ( $"{nameof(MuxTimestamps)}: CFR conversion due to FPS limit was applied (picked {I.currentMediaFile.OutputFrameIndexes.Count} frames for {I.currentSettings.outFpsResampled} FPS); won't mux timestamps." , hidden : true ) ;
return ;
2024-11-07 15:10:36 +01:00
}
2024-11-28 16:08:04 +01:00
Logger . Log ( $"{nameof(MuxTimestamps)}: Muxing timestamps for '{vidPath}'" , hidden : true ) ;
string tsFile = Path . Combine ( Paths . GetSessionDataPath ( ) , "ts.txt" ) ;
TimestampUtils . WriteTsFile ( I . currentMediaFile . OutputTimestamps , tsFile ) ;
2024-11-07 15:10:36 +01:00
string outPath = Path . ChangeExtension ( vidPath , ".tmp.mkv" ) ;
string args = $"mkvmerge --output {outPath.Wrap()} --timestamps \" 0 : { tsFile } \ " {vidPath.Wrap()}" ;
var outputMux = NUtilsTemp . OsUtils . RunCommand ( $"cd /D {Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir).Wrap()} && {args}" ) ;
// Check if file exists and is not too small (min. 80% of input file)
if ( File . Exists ( outPath ) & & ( ( double ) new FileInfo ( outPath ) . Length / ( double ) new FileInfo ( vidPath ) . Length ) > 0.8d )
{
2024-11-28 16:08:04 +01:00
Logger . Log ( $"{nameof(MuxTimestamps)}: Deleting original '{vidPath}' and moving muxed '{outPath}' to '{vidPath}'" , hidden : true ) ;
2024-11-07 15:10:36 +01:00
File . Delete ( vidPath ) ;
File . Move ( outPath , vidPath ) ;
}
2024-11-26 13:45:44 +01:00
else
{
Logger . Log ( $"{nameof(MuxTimestamps)}: Timestamp muxing failed, keeping original video file" , hidden : true ) ;
2024-11-28 16:08:04 +01:00
Logger . Log ( outputMux , hidden : true ) ;
2024-11-26 13:45:44 +01:00
IoUtils . TryDeleteIfExists ( outPath ) ;
}
2024-11-07 15:10:36 +01:00
}
2021-08-23 16:50:18 +02:00
}
}