2021-08-23 16:50:18 +02:00
using Flowframes ;
using Flowframes.Media ;
using Flowframes.Data ;
using Flowframes.IO ;
using Flowframes.Magick ;
using Flowframes.Main ;
using Flowframes.MiscUtils ;
using Flowframes.Os ;
using Flowframes.Ui ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
using Padding = Flowframes . Data . Padding ;
using Utils = Flowframes . Main . InterpolateUtils ;
namespace Flowframes
{
public class Interpolate
{
public enum OutMode { VidMp4 , VidMkv , VidWebm , VidProRes , VidAvi , VidGif , ImgPng }
public static int currentInputFrameCount ;
public static bool currentlyUsingAutoEnc ;
public static InterpSettings current ;
public static bool canceled = false ;
static Stopwatch sw = new Stopwatch ( ) ;
public static async Task Start ( )
{
if ( ! BatchProcessing . busy & & Program . busy ) return ;
canceled = false ;
Program . initialRun = false ;
Program . mainForm . SetWorking ( true ) ;
2022-05-03 16:00:55 +02:00
if ( ! Utils . InputIsValid ( current . inPath , current . outPath , current . inFps , current . interpFactor , current . outMode , current . tempFolder ) ) return ; // General input checks
2021-08-31 15:57:48 +02:00
if ( ! Utils . CheckAiAvailable ( current . ai , current . model ) ) return ; // Check if selected AI pkg is installed
2021-09-29 11:22:43 +02:00
if ( ! AutoEncodeResume . resumeNextRun & & ! Utils . CheckDeleteOldTempFolder ( ) ) return ; // Try to delete temp folder if an old one exists
2021-08-23 16:50:18 +02:00
if ( ! Utils . CheckPathValid ( current . inPath ) ) return ; // Check if input path/file is valid
if ( ! ( await Utils . CheckEncoderValid ( ) ) ) return ; // Check NVENC compat
2021-08-30 16:58:19 +02:00
Utils . ShowWarnings ( current . interpFactor , current . ai ) ;
2021-08-23 16:50:18 +02:00
currentInputFrameCount = await GetFrameCountCached . GetFrameCountAsync ( current . inPath ) ;
current . stepByStep = false ;
Program . mainForm . SetStatus ( "Starting..." ) ;
2021-09-30 19:57:59 +02:00
sw . Restart ( ) ;
2021-08-23 16:50:18 +02:00
2022-06-01 15:46:34 +02:00
if ( ! AutoEncodeResume . resumeNextRun & & ! ( current . ai . Piped & & ! current . inputIsFrames ) )
2021-08-23 16:50:18 +02:00
{
await GetFrames ( ) ;
if ( canceled ) return ;
await PostProcessFrames ( false ) ;
}
if ( canceled ) return ;
2021-09-29 11:22:43 +02:00
bool skip = await AutoEncodeResume . PrepareResumedRun ( ) ;
if ( skip | | canceled ) return ;
2021-08-23 16:50:18 +02:00
await RunAi ( current . interpFolder , current . ai ) ;
if ( canceled ) return ;
Program . mainForm . SetProgress ( 100 ) ;
2022-05-31 22:17:22 +02:00
if ( ! currentlyUsingAutoEnc )
{
if ( current . ai . Piped )
await Export . MuxPipedVideo ( current . inPath , current . FullOutPath ) ;
else
await Export . ExportFrames ( current . interpFolder , current . outPath , current . outMode , false ) ;
}
2021-08-23 16:50:18 +02:00
2022-06-01 16:23:00 +02:00
if ( ! AutoEncodeResume . resumeNextRun & & Config . GetBool ( Config . Key . keepTempFolder ) & & IoUtils . GetAmountOfFiles ( current . framesFolder , false ) > 0 )
2021-08-23 16:50:18 +02:00
await Task . Run ( async ( ) = > { await FrameRename . Unrename ( ) ; } ) ;
2021-09-29 11:22:43 +02:00
await Done ( ) ;
}
2022-05-31 22:17:22 +02:00
public static async Task Done ( )
2021-09-29 11:22:43 +02:00
{
2021-08-23 16:50:18 +02:00
await Cleanup ( ) ;
Program . mainForm . SetWorking ( false ) ;
Logger . Log ( "Total processing time: " + FormatUtils . Time ( sw . Elapsed ) ) ;
sw . Stop ( ) ;
2021-09-29 11:22:43 +02:00
if ( ! BatchProcessing . busy )
2021-08-23 16:50:18 +02:00
OsUtils . ShowNotificationIfInBackground ( "Flowframes" , $"Finished interpolation after {FormatUtils.Time(sw.Elapsed)}." ) ;
2021-09-29 11:22:43 +02:00
2021-08-23 16:50:18 +02:00
Program . mainForm . InterpolationDone ( ) ;
}
2022-06-04 12:43:48 +02:00
public static async Task Realtime ( )
{
await AiProcess . RunRifeNcnnVs ( current . framesFolder , "" , current . interpFactor , current . model . dir , true ) ;
}
2022-05-31 22:17:22 +02:00
public static async Task < int > GetCurrentInputFrameCount ( )
2021-08-23 16:50:18 +02:00
{
if ( currentInputFrameCount < 2 )
currentInputFrameCount = await GetFrameCountCached . GetFrameCountAsync ( current . inPath ) ;
return currentInputFrameCount ;
}
2022-05-31 22:17:22 +02:00
public static async Task GetFrames ( )
2021-08-23 16:50:18 +02:00
{
current . RefreshAlpha ( ) ;
current . RefreshExtensions ( InterpSettings . FrameType . Import ) ;
if ( Config . GetBool ( Config . Key . scnDetect ) )
{
Program . mainForm . SetStatus ( "Extracting scenes from video..." ) ;
await FfmpegExtract . ExtractSceneChanges ( current . inPath , Path . Combine ( current . tempFolder , Paths . scenesDir ) , current . inFpsDetected , current . inputIsFrames , current . framesExt ) ;
}
if ( ! current . inputIsFrames ) // Extract if input is video, import if image sequence
await ExtractFrames ( current . inPath , current . framesFolder , current . alpha ) ;
else
2021-11-24 21:08:29 +01:00
await FfmpegExtract . ImportImagesCheckCompat ( current . inPath , current . framesFolder , current . alpha , current . ScaledResolution , true , current . framesExt ) ;
2021-08-23 16:50:18 +02:00
}
public static async Task ExtractFrames ( string inPath , string outPath , bool alpha )
{
if ( canceled ) return ;
Program . mainForm . SetStatus ( "Extracting frames from video..." ) ;
current . RefreshExtensions ( InterpSettings . FrameType . Import ) ;
bool mpdecimate = Config . GetInt ( Config . Key . dedupMode ) = = 2 ;
Size res = await Utils . GetOutputResolution ( inPath , true , true ) ;
await FfmpegExtract . VideoToFrames ( inPath , outPath , alpha , current . inFpsDetected , mpdecimate , false , res , current . framesExt ) ;
if ( mpdecimate )
{
int framesLeft = IoUtils . GetAmountOfFiles ( outPath , false , "*" + current . framesExt ) ;
int framesDeleted = currentInputFrameCount - framesLeft ;
float percentDeleted = ( ( float ) framesDeleted / currentInputFrameCount ) * 100f ;
string keptPercent = $"{(100f - percentDeleted).ToString(" 0.0 ")}%" ;
2022-05-31 22:17:22 +02:00
if ( QuickSettingsTab . trimEnabled )
2021-08-23 16:50:18 +02:00
Logger . Log ( $"Deduplication: Kept {framesLeft} frames." ) ;
else
Logger . Log ( $"Deduplication: Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames." ) ;
}
2022-05-31 22:17:22 +02:00
if ( ! Config . GetBool ( "allowConsecutiveSceneChanges" , true ) )
2021-08-23 16:50:18 +02:00
Utils . FixConsecutiveSceneFrames ( Path . Combine ( current . tempFolder , Paths . scenesDir ) , current . framesFolder ) ;
}
2022-05-31 22:17:22 +02:00
public static async Task PostProcessFrames ( bool stepByStep )
2021-08-23 16:50:18 +02:00
{
if ( canceled ) return ;
Program . mainForm . SetStatus ( "Processing frames..." ) ;
int extractedFrames = IoUtils . GetAmountOfFiles ( current . framesFolder , false , "*" + current . framesExt ) ;
2021-12-09 10:47:28 +01:00
2021-08-23 16:50:18 +02:00
if ( ! Directory . Exists ( current . framesFolder ) | | currentInputFrameCount < = 0 | | extractedFrames < 2 )
{
2022-05-31 22:17:22 +02:00
if ( extractedFrames = = 1 )
2021-08-23 16:50:18 +02:00
Cancel ( "Only a single frame was extracted from your input file!\n\nPossibly your input is an image, not a video?" ) ;
else
2022-04-05 18:07:05 +02:00
Cancel ( $"Frame extraction failed!\nExtracted {extractedFrames} frames - current.framesFolder exists: {Directory.Exists(current.framesFolder)} - currentInputFrameCount = {currentInputFrameCount} - extractedFrames = {extractedFrames}.\n\nYour input file might be incompatible." ) ;
2022-05-31 22:17:22 +02:00
}
2021-08-23 16:50:18 +02:00
if ( Config . GetInt ( Config . Key . dedupMode ) = = 1 )
await Dedupe . Run ( current . framesFolder ) ;
else
Dedupe . ClearCache ( ) ;
if ( ! Config . GetBool ( Config . Key . enableLoop ) )
{
await Utils . CopyLastFrame ( currentInputFrameCount ) ;
}
else
{
FileInfo [ ] frameFiles = IoUtils . GetFileInfosSorted ( current . framesFolder ) ;
string ext = frameFiles . First ( ) . Extension ;
int lastNum = frameFiles . Last ( ) . Name . GetInt ( ) + 1 ;
string loopFrameTargetPath = Path . Combine ( current . framesFolder , lastNum . ToString ( ) . PadLeft ( Padding . inputFrames , '0' ) + ext ) ;
File . Copy ( frameFiles . First ( ) . FullName , loopFrameTargetPath , true ) ;
Logger . Log ( $"Copied loop frame to {loopFrameTargetPath}." , true ) ;
}
}
public static async Task RunAi ( string outpath , AI ai , bool stepByStep = false )
{
if ( canceled ) return ;
2022-06-01 16:23:00 +02:00
if ( ! ai . Piped | | ai . Piped & & current . inputIsFrames )
{
await Task . Run ( async ( ) = > { await Dedupe . CreateDupesFile ( current . framesFolder , currentInputFrameCount , current . framesExt ) ; } ) ;
await Task . Run ( async ( ) = > { await FrameRename . Rename ( ) ; } ) ;
}
2022-06-01 15:46:34 +02:00
2022-05-31 22:17:22 +02:00
if ( ! ai . Piped )
await Task . Run ( async ( ) = > { await FrameOrder . CreateFrameOrderFile ( current . framesFolder , Config . GetBool ( Config . Key . enableLoop ) , current . interpFactor ) ; } ) ;
2021-08-23 16:50:18 +02:00
Program . mainForm . SetStatus ( "Downloading models..." ) ;
2022-06-01 15:46:34 +02:00
await ModelDownloader . DownloadModelFiles ( ai , current . model . dir ) ;
2021-08-23 16:50:18 +02:00
if ( canceled ) return ;
currentlyUsingAutoEnc = Utils . CanUseAutoEnc ( stepByStep , current ) ;
IoUtils . CreateDir ( outpath ) ;
List < Task > tasks = new List < Task > ( ) ;
2022-05-31 22:17:22 +02:00
if ( ai . AiName = = Implementations . rifeCuda . AiName )
2021-08-23 16:50:18 +02:00
tasks . Add ( AiProcess . RunRifeCuda ( current . framesFolder , current . interpFactor , current . model . dir ) ) ;
2022-05-31 22:17:22 +02:00
if ( ai . AiName = = Implementations . rifeNcnn . AiName )
2022-04-05 23:13:48 +02:00
tasks . Add ( AiProcess . RunRifeNcnn ( current . framesFolder , outpath , current . interpFactor , current . model . dir ) ) ;
2021-08-23 16:50:18 +02:00
2022-05-31 22:17:22 +02:00
if ( ai . AiName = = Implementations . rifeNcnnVs . AiName )
tasks . Add ( AiProcess . RunRifeNcnnVs ( current . framesFolder , outpath , current . interpFactor , current . model . dir ) ) ;
if ( ai . AiName = = Implementations . flavrCuda . AiName )
2021-08-23 16:50:18 +02:00
tasks . Add ( AiProcess . RunFlavrCuda ( current . framesFolder , current . interpFactor , current . model . dir ) ) ;
2022-05-31 22:17:22 +02:00
if ( ai . AiName = = Implementations . dainNcnn . AiName )
2021-08-23 16:50:18 +02:00
tasks . Add ( AiProcess . RunDainNcnn ( current . framesFolder , outpath , current . interpFactor , current . model . dir , Config . GetInt ( Config . Key . dainNcnnTilesize , 512 ) ) ) ;
2022-05-31 22:17:22 +02:00
if ( ai . AiName = = Implementations . xvfiCuda . AiName )
2021-08-23 16:50:18 +02:00
tasks . Add ( AiProcess . RunXvfiCuda ( current . framesFolder , current . interpFactor , current . model . dir ) ) ;
if ( currentlyUsingAutoEnc )
{
Logger . Log ( $"{Logger.GetLastLine()} (Using Auto-Encode)" , true ) ;
tasks . Add ( AutoEncode . MainLoop ( outpath ) ) ;
}
Program . mainForm . SetStatus ( "Running AI..." ) ;
await Task . WhenAll ( tasks ) ;
}
public static void Cancel ( string reason = "" , bool noMsgBox = false )
{
if ( current = = null )
return ;
canceled = true ;
Program . mainForm . SetStatus ( "Canceled." ) ;
Program . mainForm . SetProgress ( 0 ) ;
AiProcess . Kill ( ) ;
AvProcess . Kill ( ) ;
if ( ! current . stepByStep & & ! Config . GetBool ( Config . Key . keepTempFolder ) )
{
2022-05-31 22:17:22 +02:00
if ( ! BatchProcessing . busy & & IoUtils . GetAmountOfFiles ( Path . Combine ( current . tempFolder , Paths . resumeDir ) , true ) > 0 )
2021-08-23 16:50:18 +02:00
{
DialogResult dialogResult = MessageBox . Show ( $"Delete the temp folder (Yes) or keep it for resuming later (No)?" , "Delete temporary files?" , MessageBoxButtons . YesNo ) ;
2022-05-31 22:17:22 +02:00
2021-08-23 16:50:18 +02:00
if ( dialogResult = = DialogResult . Yes )
Task . Run ( async ( ) = > { await IoUtils . TryDeleteIfExistsAsync ( current . tempFolder ) ; } ) ;
}
else
{
Task . Run ( async ( ) = > { await IoUtils . TryDeleteIfExistsAsync ( current . tempFolder ) ; } ) ;
}
}
AutoEncode . busy = false ;
Program . mainForm . SetWorking ( false ) ;
Program . mainForm . SetTab ( "interpolation" ) ;
Logger . LogIfLastLineDoesNotContainMsg ( "Canceled interpolation." ) ;
if ( ! string . IsNullOrWhiteSpace ( reason ) & & ! noMsgBox )
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( $"Canceled:\n\n{reason}" ) ;
2021-08-23 16:50:18 +02:00
}
public static async Task Cleanup ( bool ignoreKeepSetting = false , int retriesLeft = 3 , bool isRetry = false )
{
if ( ( ! ignoreKeepSetting & & Config . GetBool ( Config . Key . keepTempFolder ) ) | | ! Program . busy ) return ;
if ( ! isRetry )
Logger . Log ( "Deleting temporary files..." ) ;
try
{
await Task . Run ( async ( ) = > { Directory . Delete ( current . tempFolder , true ) ; } ) ;
}
catch ( Exception e )
{
Logger . Log ( "Cleanup Error: " + e . Message , true ) ;
2022-05-31 22:17:22 +02:00
if ( retriesLeft > 0 )
2021-08-23 16:50:18 +02:00
{
await Task . Delay ( 1000 ) ;
await Cleanup ( ignoreKeepSetting , retriesLeft - 1 , true ) ;
}
}
}
}
}