2021-08-23 16:50:18 +02:00
using Flowframes.Media ;
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.MiscUtils ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using Flowframes.Ui ;
using Flowframes.Os ;
namespace Flowframes.Main
{
class AutoEncode
{
static string interpFramesFolder ;
static string videoChunksFolder ;
public static int chunkSize ; // Encode every n frames
public static int safetyBufferFrames ; // Ignore latest n frames to avoid using images that haven't been fully encoded yet
public static string [ ] interpFramesLines ;
public static List < int > encodedFrameLines = new List < int > ( ) ;
public static List < int > unencodedFrameLines = new List < int > ( ) ;
public static bool debug ;
public static bool busy ;
public static bool paused ;
public static Task currentMuxTask ;
public static void UpdateChunkAndBufferSizes ( )
{
2022-07-20 18:10:31 +02:00
chunkSize = GetChunkSize ( ( IoUtils . GetAmountOfFiles ( Interpolate . currentSettings . framesFolder , false , "*" + Interpolate . currentSettings . framesExt ) * Interpolate . currentSettings . interpFactor ) . RoundToInt ( ) ) ;
2021-08-23 16:50:18 +02:00
safetyBufferFrames = 90 ;
2022-07-20 18:10:31 +02:00
if ( Interpolate . currentSettings . ai . Backend = = AI . AiBackend . Ncnn )
2021-08-23 16:50:18 +02:00
safetyBufferFrames = Config . GetInt ( Config . Key . autoEncSafeBufferNcnn , 150 ) ;
2022-07-20 18:10:31 +02:00
if ( Interpolate . currentSettings . ai . Backend = = AI . AiBackend . Pytorch )
2021-08-31 00:43:22 +02:00
safetyBufferFrames = Config . GetInt ( Config . Key . autoEncSafeBufferCuda , 90 ) ;
2021-08-23 16:50:18 +02:00
}
public static async Task MainLoop ( string interpFramesPath )
{
2021-09-29 11:22:43 +02:00
if ( ! AutoEncodeResume . resumeNextRun )
AutoEncodeResume . Reset ( ) ;
2021-08-23 16:50:18 +02:00
debug = Config . GetBool ( "autoEncDebug" , false ) ;
try
{
UpdateChunkAndBufferSizes ( ) ;
2022-10-14 09:00:47 +02:00
bool imgSeq = Interpolate . currentSettings . outMode . ToString ( ) . ToLowerInvariant ( ) . StartsWith ( "img" ) ;
2021-08-23 16:50:18 +02:00
interpFramesFolder = interpFramesPath ;
videoChunksFolder = Path . Combine ( interpFramesPath . GetParentDir ( ) , Paths . chunksDir ) ;
2021-08-31 00:43:22 +02:00
2021-08-23 16:50:18 +02:00
if ( Interpolate . currentlyUsingAutoEnc )
Directory . CreateDirectory ( videoChunksFolder ) ;
encodedFrameLines . Clear ( ) ;
unencodedFrameLines . Clear ( ) ;
Logger . Log ( $"[AE] Starting AutoEncode MainLoop - Chunk Size: {chunkSize} Frames - Safety Buffer: {safetyBufferFrames} Frames" , true ) ;
2021-09-29 11:22:43 +02:00
int chunkNo = AutoEncodeResume . encodedChunks + 1 ;
2022-07-20 18:10:31 +02:00
string encFile = Path . Combine ( interpFramesPath . GetParentDir ( ) , Paths . GetFrameOrderFilename ( Interpolate . currentSettings . interpFactor ) ) ;
2021-08-23 16:50:18 +02:00
interpFramesLines = IoUtils . ReadLines ( encFile ) . Select ( x = > x . Split ( '/' ) . Last ( ) . Remove ( "'" ) . Split ( '#' ) . First ( ) ) . ToArray ( ) ; // Array with frame filenames
while ( ! Interpolate . canceled & & GetInterpFramesAmount ( ) < 2 )
2021-08-31 00:43:22 +02:00
await Task . Delay ( 1000 ) ;
2021-08-23 16:50:18 +02:00
int lastEncodedFrameNum = 0 ;
while ( HasWorkToDo ( ) ) // Loop while proc is running and not all frames have been encoded
{
if ( Interpolate . canceled ) return ;
if ( paused )
{
await Task . Delay ( 200 ) ;
continue ;
}
unencodedFrameLines . Clear ( ) ;
bool aiRunning = ! AiProcess . lastAiProcess . HasExited ;
for ( int frameLineNum = lastEncodedFrameNum ; frameLineNum < interpFramesLines . Length ; frameLineNum + + )
{
if ( aiRunning & & interpFramesLines [ frameLineNum ] . Contains ( InterpolationProgress . lastFrame . ToString ( ) . PadLeft ( Padding . interpFrames , '0' ) ) )
break ;
unencodedFrameLines . Add ( frameLineNum ) ;
}
if ( Config . GetBool ( Config . Key . alwaysWaitForAutoEnc ) )
{
int maxFrames = chunkSize + ( 0.5f * chunkSize ) . RoundToInt ( ) + safetyBufferFrames ;
bool overwhelmed = unencodedFrameLines . Count > maxFrames ;
2021-08-30 16:58:19 +02:00
if ( overwhelmed & & ! AiProcessSuspend . aiProcFrozen & & OsUtils . IsProcessHidden ( AiProcess . lastAiProcess ) )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
string dirSize = FormatUtils . Bytes ( IoUtils . GetDirSize ( Interpolate . currentSettings . interpFolder , true ) ) ;
2021-08-23 16:50:18 +02:00
Logger . Log ( $"AutoEnc is overwhelmed! ({unencodedFrameLines.Count} unencoded frames > {maxFrames}) - Pausing." , true ) ;
AiProcessSuspend . SuspendResumeAi ( true ) ;
}
else if ( ! overwhelmed & & AiProcessSuspend . aiProcFrozen )
{
AiProcessSuspend . SuspendResumeAi ( false ) ;
}
}
if ( unencodedFrameLines . Count > 0 & & ( unencodedFrameLines . Count > = ( chunkSize + safetyBufferFrames ) | | ! aiRunning ) ) // Encode every n frames, or after process has exited
{
try
{
List < int > frameLinesToEncode = aiRunning ? unencodedFrameLines . Take ( chunkSize ) . ToList ( ) : unencodedFrameLines ; // Take all remaining frames if process is done
string lastOfChunk = Path . Combine ( interpFramesPath , interpFramesLines [ frameLinesToEncode . Last ( ) ] ) ;
if ( ! File . Exists ( lastOfChunk ) )
{
if ( debug )
Logger . Log ( $"[AE] Last frame of chunk doesn't exist; skipping loop iteration ({lastOfChunk})" , true ) ;
await Task . Delay ( 500 ) ;
continue ;
}
busy = true ;
2022-07-20 18:10:31 +02:00
string outpath = Path . Combine ( videoChunksFolder , "chunks" , $"{chunkNo.ToString().PadLeft(4, '0')}{FfmpegUtils.GetExt(Interpolate.currentSettings.outMode)}" ) ;
2021-08-31 00:43:22 +02:00
string firstFile = Path . GetFileName ( interpFramesLines [ frameLinesToEncode . First ( ) ] . Trim ( ) ) ;
string lastFile = Path . GetFileName ( interpFramesLines [ frameLinesToEncode . Last ( ) ] . Trim ( ) ) ;
2021-08-31 01:08:21 +02:00
Logger . Log ( $"[AE] Encoding Chunk #{chunkNo} to using line {frameLinesToEncode.First()} ({firstFile}) through {frameLinesToEncode.Last()} ({lastFile}) - {unencodedFrameLines.Count} unencoded frames left in total" , true , false , "ffmpeg" ) ;
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
await Export . EncodeChunk ( outpath , Interpolate . currentSettings . interpFolder , chunkNo , Interpolate . currentSettings . outMode , frameLinesToEncode . First ( ) , frameLinesToEncode . Count ) ;
2021-08-23 16:50:18 +02:00
if ( Interpolate . canceled ) return ;
if ( aiRunning & & Config . GetInt ( Config . Key . autoEncMode ) = = 2 )
Task . Run ( ( ) = > DeleteOldFramesAsync ( interpFramesPath , frameLinesToEncode ) ) ;
if ( Interpolate . canceled ) return ;
encodedFrameLines . AddRange ( frameLinesToEncode ) ;
2021-08-31 00:47:06 +02:00
Logger . Log ( "[AE] Done Encoding Chunk #" + chunkNo , true , false , "ffmpeg" ) ;
2021-08-23 16:50:18 +02:00
lastEncodedFrameNum = ( frameLinesToEncode . Last ( ) + 1 ) ;
2021-08-31 00:47:06 +02:00
chunkNo + + ;
2021-09-28 20:44:08 +02:00
AutoEncodeResume . Save ( ) ;
2021-08-23 16:50:18 +02:00
2021-08-31 00:43:22 +02:00
if ( ! imgSeq & & Config . GetInt ( Config . Key . autoEncBackupMode ) > 0 )
2021-08-23 16:50:18 +02:00
{
if ( aiRunning & & ( currentMuxTask = = null | | ( currentMuxTask ! = null & & currentMuxTask . IsCompleted ) ) )
2022-07-20 18:10:31 +02:00
currentMuxTask = Task . Run ( ( ) = > Export . ChunksToVideo ( Interpolate . currentSettings . tempFolder , videoChunksFolder , Interpolate . currentSettings . outPath , true ) ) ;
2021-08-23 16:50:18 +02:00
else
Logger . Log ( $"[AE] Skipping backup because {(!aiRunning ? " this is the final chunk " : " previous mux task has not finished yet ")}!" , true , false , "ffmpeg" ) ;
}
busy = false ;
}
catch ( Exception e )
{
Logger . Log ( $"AutoEnc Chunk Encoding Error: {e.Message}. Stack Trace:\n{e.StackTrace}" ) ;
Interpolate . Cancel ( "Auto-Encode encountered an error." ) ;
}
}
await Task . Delay ( 50 ) ;
}
if ( Interpolate . canceled ) return ;
while ( currentMuxTask ! = null & & ! currentMuxTask . IsCompleted )
await Task . Delay ( 100 ) ;
2021-08-31 00:43:22 +02:00
if ( imgSeq )
return ;
2022-07-20 18:10:31 +02:00
await Export . ChunksToVideo ( Interpolate . currentSettings . tempFolder , videoChunksFolder , Interpolate . currentSettings . outPath ) ;
2021-08-23 16:50:18 +02:00
}
catch ( Exception e )
{
Logger . Log ( $"AutoEnc Error: {e.Message}. Stack Trace:\n{e.StackTrace}" ) ;
Interpolate . Cancel ( "Auto-Encode encountered an error." ) ;
}
}
static async Task DeleteOldFramesAsync ( string interpFramesPath , List < int > frameLinesToEncode )
{
if ( debug )
Logger . Log ( "[AE] Starting DeleteOldFramesAsync." , true , false , "ffmpeg" ) ;
Stopwatch sw = new Stopwatch ( ) ;
sw . Restart ( ) ;
foreach ( int frame in frameLinesToEncode )
{
if ( ! FrameIsStillNeeded ( interpFramesLines [ frame ] , frame ) ) // Make sure frames are no longer needed (for dupes) before deleting!
{
string framePath = Path . Combine ( interpFramesPath , interpFramesLines [ frame ] ) ;
//IOUtils.OverwriteFileWithText(framePath); // Overwrite to save space without breaking progress counter
IoUtils . TryDeleteIfExists ( framePath ) ;
InterpolationProgress . deletedFramesCount + + ;
}
}
if ( debug )
Logger . Log ( "[AE] DeleteOldFramesAsync finished in " + FormatUtils . TimeSw ( sw ) , true , false , "ffmpeg" ) ;
}
static bool FrameIsStillNeeded ( string frameName , int frameIndex )
{
if ( ( frameIndex + 1 ) < interpFramesLines . Length & & interpFramesLines [ frameIndex + 1 ] . Contains ( frameName ) )
return true ;
return false ;
}
public static bool HasWorkToDo ( )
{
if ( Interpolate . canceled | | interpFramesFolder = = null ) return false ;
if ( debug )
Logger . Log ( $"[AE] HasWorkToDo - Process Running: {(AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited)} - encodedFrameLines.Count: {encodedFrameLines.Count} - interpFramesLines.Length: {interpFramesLines.Length}" , true ) ;
return ( ( AiProcess . lastAiProcess ! = null & & ! AiProcess . lastAiProcess . HasExited ) | | encodedFrameLines . Count < interpFramesLines . Length ) ;
}
static int GetChunkSize ( int targetFramesAmount )
{
if ( targetFramesAmount > 100000 ) return 4800 ;
if ( targetFramesAmount > 50000 ) return 2400 ;
if ( targetFramesAmount > 20000 ) return 1200 ;
if ( targetFramesAmount > 5000 ) return 600 ;
if ( targetFramesAmount > 1000 ) return 300 ;
return 150 ;
}
static int GetInterpFramesAmount ( )
{
2022-07-20 18:10:31 +02:00
return IoUtils . GetAmountOfFiles ( interpFramesFolder , false , "*" + Interpolate . currentSettings . interpExt ) ;
2021-08-23 16:50:18 +02:00
}
}
}