2021-08-23 16:50:18 +02:00
using Flowframes.Media ;
using Flowframes.Data ;
using Flowframes.Forms ;
using Flowframes.IO ;
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.Text.RegularExpressions ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
using I = Flowframes . Interpolate ;
using Padding = Flowframes . Data . Padding ;
namespace Flowframes.Main
{
class InterpolateUtils
{
public static async Task CopyLastFrame ( int lastFrameNum )
{
if ( I . canceled ) return ;
try
{
lastFrameNum - - ; // We have to do this as extracted frames start at 0, not 1
2022-07-20 18:10:31 +02:00
bool frameFolderInput = IoUtils . IsPathDirectory ( I . currentSettings . inPath ) ;
string targetPath = Path . Combine ( I . currentSettings . framesFolder , lastFrameNum . ToString ( ) . PadLeft ( Padding . inputFrames , '0' ) + I . currentSettings . framesExt ) ;
2021-08-23 16:50:18 +02:00
if ( File . Exists ( targetPath ) ) return ;
2022-07-20 18:10:31 +02:00
Size res = IoUtils . GetImage ( IoUtils . GetFilesSorted ( I . currentSettings . framesFolder , false ) . First ( ) ) . Size ;
2021-08-23 16:50:18 +02:00
if ( frameFolderInput )
{
2022-07-20 18:10:31 +02:00
string lastFramePath = IoUtils . GetFilesSorted ( I . currentSettings . inPath , false ) . Last ( ) ;
2021-08-23 16:50:18 +02:00
await FfmpegExtract . ExtractLastFrame ( lastFramePath , targetPath , res ) ;
}
else
{
2022-07-20 18:10:31 +02:00
await FfmpegExtract . ExtractLastFrame ( I . currentSettings . inPath , targetPath , res ) ;
2021-08-23 16:50:18 +02:00
}
}
catch ( Exception e )
{
Logger . Log ( "CopyLastFrame Error: " + e . Message ) ;
}
}
public static int GetProgressWaitTime ( int numFrames )
{
float hddMultiplier = ! Program . lastInputPathIsSsd ? 2f : 1f ;
int waitMs = 200 ;
if ( numFrames > 100 )
waitMs = 500 ;
if ( numFrames > 1000 )
waitMs = 1000 ;
if ( numFrames > 2500 )
waitMs = 1500 ;
if ( numFrames > 5000 )
waitMs = 2500 ;
return ( waitMs * hddMultiplier ) . RoundToInt ( ) ;
}
public static string GetTempFolderLoc ( string inPath , string outPath )
{
string basePath = inPath . GetParentDir ( ) ;
if ( Config . GetInt ( Config . Key . tempFolderLoc ) = = 1 )
basePath = outPath . GetParentDir ( ) ;
if ( Config . GetInt ( Config . Key . tempFolderLoc ) = = 2 )
basePath = outPath ;
if ( Config . GetInt ( Config . Key . tempFolderLoc ) = = 3 )
basePath = Paths . GetExeDir ( ) ;
if ( Config . GetInt ( Config . Key . tempFolderLoc ) = = 4 )
{
string custPath = Config . Get ( Config . Key . tempDirCustom ) ;
2022-05-03 16:00:55 +02:00
2021-08-23 16:50:18 +02:00
if ( IoUtils . IsDirValid ( custPath ) )
basePath = custPath ;
}
return Path . Combine ( basePath , Path . GetFileNameWithoutExtension ( inPath ) . StripBadChars ( ) . Remove ( " " ) . Trunc ( 30 , false ) + "-temp" ) ;
}
2022-07-20 18:10:31 +02:00
public static bool InputIsValid ( InterpSettings s )
2021-08-23 16:50:18 +02:00
{
try
{
bool passes = true ;
2022-07-20 18:10:31 +02:00
bool isFile = ! IoUtils . IsPathDirectory ( s . inPath ) ;
2021-08-23 16:50:18 +02:00
2022-07-20 18:10:31 +02:00
if ( ( passes & & isFile & & ! IoUtils . IsFileValid ( s . inPath ) ) | | ( ! isFile & & ! IoUtils . IsDirValid ( s . inPath ) ) )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Input path is not valid!" ) ;
2021-08-23 16:50:18 +02:00
passes = false ;
}
2022-07-20 18:10:31 +02:00
if ( passes & & ! IoUtils . IsDirValid ( s . outPath ) )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Output path is not valid!" ) ;
2021-08-23 16:50:18 +02:00
passes = false ;
}
2022-07-20 18:10:31 +02:00
if ( passes & & s . tempFolder . StartsWith ( @"\\" ) )
2022-05-03 16:00:55 +02:00
{
UiUtils . ShowMessageBox ( "Flowframes does not support UNC/Network paths as a temp folder!\nPlease use a local path instead." ) ;
passes = false ;
}
2022-07-20 18:10:31 +02:00
if ( passes & & s . outFps . GetFloat ( ) < 1f | | s . outFps . GetFloat ( ) > 1000f )
2021-08-23 16:50:18 +02:00
{
2021-09-09 19:56:45 +02:00
string imgSeqNote = isFile ? "" : "\n\nWhen using an image sequence as input, you always have to specify the frame rate manually." ;
2022-07-20 18:10:31 +02:00
UiUtils . ShowMessageBox ( $"Invalid output frame rate ({s.outFps.GetFloat()}).\nMust be 1-1000.{imgSeqNote}" ) ;
2021-08-23 16:50:18 +02:00
passes = false ;
}
string fpsLimitValue = Config . Get ( Config . Key . maxFps ) ;
float fpsLimit = ( fpsLimitValue . Contains ( "/" ) ? new Fraction ( Config . Get ( Config . Key . maxFps ) ) . GetFloat ( ) : fpsLimitValue . GetFloat ( ) ) ;
2022-07-20 18:10:31 +02:00
if ( s . outMode = = I . OutMode . VidGif & & s . outFps . GetFloat ( ) > 50 & & ! ( fpsLimit > 0 & & fpsLimit < = 50 ) )
Logger . Log ( $"Warning: GIF will be encoded at 50 FPS instead of {s.outFps.GetFloat()} as the format doesn't support frame rates that high." ) ;
2021-08-23 16:50:18 +02:00
if ( ! passes )
I . Cancel ( "Invalid settings detected." , true ) ;
return passes ;
}
catch ( Exception e )
{
Logger . Log ( $"Failed to run InputIsValid: {e.Message}\n{e.StackTrace}" , true ) ;
return false ;
}
}
2021-08-31 15:57:48 +02:00
public static bool CheckAiAvailable ( AI ai , ModelCollection . ModelInfo model )
2021-08-23 16:50:18 +02:00
{
2022-05-31 22:17:22 +02:00
if ( IoUtils . GetAmountOfFiles ( Path . Combine ( Paths . GetPkgPath ( ) , ai . PkgDir ) , true ) < 1 )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "The selected AI is not installed!" , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
I . Cancel ( "Selected AI not available." , true ) ;
return false ;
}
2022-07-27 15:18:37 +02:00
if ( model = = null | | model . Dir . Trim ( ) = = "" )
2021-08-31 15:57:48 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "No valid AI model has been selected!" , UiUtils . MessageType . Error ) ;
2021-08-31 15:57:48 +02:00
I . Cancel ( "No valid model selected." , true ) ;
return false ;
}
2022-07-21 10:08:53 +02:00
if ( I . currentSettings . ai . NameInternal . ToUpper ( ) . Contains ( "CUDA" ) & & NvApi . gpuList . Count < 1 )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Warning: No Nvidia GPU was detected. CUDA might fall back to CPU!\n\nTry an NCNN implementation instead if you don't have an Nvidia GPU." , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
if ( ! Config . GetBool ( "allowCudaWithoutDetectedGpu" , true ) )
{
I . Cancel ( "No CUDA-capable graphics card available." , true ) ;
return false ;
}
}
return true ;
}
public static bool CheckDeleteOldTempFolder ( )
{
2022-07-20 18:10:31 +02:00
if ( ! IoUtils . TryDeleteIfExists ( I . currentSettings . tempFolder ) )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Failed to remove an existing temp folder of this video!\nMake sure you didn't open any frames in an editor." , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
I . Cancel ( ) ;
return false ;
}
return true ;
}
2021-08-30 16:58:19 +02:00
public static void ShowWarnings ( float factor , AI ai )
{
if ( Config . GetInt ( Config . Key . cmdDebugMode ) > 0 )
Logger . Log ( $"Warning: The CMD window for interpolation is enabled. This will disable Auto-Encode and the progress bar!" ) ;
}
2021-08-23 16:50:18 +02:00
public static bool CheckPathValid ( string path )
{
2022-05-03 16:00:55 +02:00
if ( path . StartsWith ( @"\\" ) )
{
UiUtils . ShowMessageBox ( "Input path is not valid.\nFlowframes does not support UNC/Network paths." ) ;
I . Cancel ( ) ;
return false ;
}
2021-08-23 16:50:18 +02:00
if ( IoUtils . IsPathDirectory ( path ) )
{
if ( ! IoUtils . IsDirValid ( path ) )
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Input directory is not valid.\nMake sure it still exists and hasn't been renamed or moved!" ) ;
2021-08-23 16:50:18 +02:00
I . Cancel ( ) ;
return false ;
}
}
else
{
if ( ! IsVideoValid ( path ) )
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "Input video file is not valid.\nMake sure it still exists and hasn't been renamed or moved!" ) ;
2021-08-23 16:50:18 +02:00
return false ;
}
}
return true ;
}
2022-08-11 16:15:20 +02:00
public static async Task < bool > CheckEncoderValid ( float interpFps )
2021-08-23 16:50:18 +02:00
{
2022-07-20 18:10:31 +02:00
string enc = FfmpegUtils . GetEnc ( FfmpegUtils . GetCodec ( I . currentSettings . outMode ) ) ;
2021-08-23 16:50:18 +02:00
2022-08-11 16:15:20 +02:00
float maxAv1Fps = 240 ; // SVT-AV1 only supports up to 240 FPS as of 2022-08
float maxFps = Config . GetFloat ( Config . Key . maxFps ) ;
float encodeFps = maxFps > 0 ? interpFps . Clamp ( 0 , maxFps ) : interpFps ;
2021-08-23 16:50:18 +02:00
2022-07-19 21:54:25 +02:00
if ( enc . ToLower ( ) . Contains ( "av1" ) & & encodeFps > maxAv1Fps )
{
UiUtils . ShowMessageBox ( $"The selected encoder only supports up to {maxAv1Fps} FPS!\nPlease use a different encoder or reduce the interpolation factor." , UiUtils . MessageType . Error ) ;
I . Cancel ( ) ;
return false ;
}
if ( enc . ToLower ( ) . Contains ( "nvenc" ) & & ! ( await FfmpegCommands . IsEncoderCompatible ( enc ) ) )
2021-08-23 16:50:18 +02:00
{
2022-04-07 04:59:34 +02:00
UiUtils . ShowMessageBox ( "NVENC encoding is not available on your hardware!\nPlease use a different encoder." , UiUtils . MessageType . Error ) ;
2021-08-23 16:50:18 +02:00
I . Cancel ( ) ;
return false ;
}
return true ;
}
public static bool IsVideoValid ( string videoPath )
{
if ( videoPath = = null | | ! IoUtils . IsFileValid ( videoPath ) )
return false ;
return true ;
}
public static async Task < Size > GetOutputResolution ( string inputPath , bool print , bool returnZeroIfUnchanged = false )
{
Size resolution = await GetMediaResolutionCached . GetSizeAsync ( inputPath ) ;
return GetOutputResolution ( resolution , print , returnZeroIfUnchanged ) ;
}
public static Size GetOutputResolution ( Size inputRes , bool print = false , bool returnZeroIfUnchanged = false )
{
int maxHeightValue = Config . GetInt ( Config . Key . maxVidHeight ) ;
int maxHeight = RoundDivisibleBy ( maxHeightValue , FfmpegCommands . GetPadding ( ) ) ;
if ( inputRes . Height > maxHeight )
{
float factor = ( float ) maxHeight / inputRes . Height ;
Logger . Log ( $"Un-rounded downscaled size: {(inputRes.Width * factor).ToString(" 0.00 ")}x{maxHeightValue}" , true ) ;
int width = RoundDivisibleBy ( ( inputRes . Width * factor ) . RoundToInt ( ) , FfmpegCommands . GetPadding ( ) ) ;
if ( print )
Logger . Log ( $"Video is bigger than the maximum - Downscaling to {width}x{maxHeight}." ) ;
return new Size ( width , maxHeight ) ;
}
else
{
if ( returnZeroIfUnchanged )
return new Size ( ) ;
else
return inputRes ;
}
}
public static int RoundDivisibleBy ( int number , int divisibleBy ) // Round to a number that's divisible by 2 (for h264 etc)
{
int a = ( number / divisibleBy ) * divisibleBy ; // Smaller multiple
int b = a + divisibleBy ; // Larger multiple
return ( number - a > b - number ) ? b : a ; // Return of closest of two
}
public static bool CanUseAutoEnc ( bool stepByStep , InterpSettings current )
{
AutoEncode . UpdateChunkAndBufferSizes ( ) ;
2022-05-31 22:17:22 +02:00
if ( current . ai . Piped )
{
Logger . Log ( $"Not Using AutoEnc: Using piped encoding." , true ) ;
return false ;
}
2021-08-31 00:43:22 +02:00
if ( Config . GetInt ( Config . Key . cmdDebugMode ) > 0 )
2021-08-23 16:50:18 +02:00
{
2021-08-31 00:43:22 +02:00
Logger . Log ( $"Not Using AutoEnc: CMD window is shown (cmdDebugMode > 0)" , true ) ;
2021-08-23 16:50:18 +02:00
return false ;
}
2021-12-27 22:58:19 +01:00
if ( current . outMode = = I . OutMode . VidGif )
{
Logger . Log ( $"Not Using AutoEnc: Using GIF output" , true ) ;
return false ;
}
2021-08-23 16:50:18 +02:00
if ( stepByStep & & ! Config . GetBool ( Config . Key . sbsAllowAutoEnc ) )
{
2021-08-31 00:43:22 +02:00
Logger . Log ( $"Not Using AutoEnc: Using step-by-step mode, but 'sbsAllowAutoEnc' is false" , true ) ;
2021-08-23 16:50:18 +02:00
return false ;
}
if ( ! stepByStep & & Config . GetInt ( Config . Key . autoEncMode ) = = 0 )
{
2021-08-31 00:43:22 +02:00
Logger . Log ( $"Not Using AutoEnc: 'autoEncMode' is 0" , true ) ;
2021-08-23 16:50:18 +02:00
return false ;
}
int inFrames = IoUtils . GetAmountOfFiles ( current . framesFolder , false ) ;
if ( inFrames * current . interpFactor < ( AutoEncode . chunkSize + AutoEncode . safetyBufferFrames ) * 1.2f )
{
Logger . Log ( $"Not Using AutoEnc: Input frames ({inFrames}) * factor ({current.interpFactor}) is smaller than (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f)" , true ) ;
return false ;
}
return true ;
}
public static async Task < bool > UseUhd ( )
{
2022-07-20 18:10:31 +02:00
return UseUhd ( await GetOutputResolution ( I . currentSettings . inPath , false ) ) ;
}
public static bool UseUhd ( Size outputRes )
{
return outputRes . Height > = Config . GetInt ( Config . Key . uhdThresh ) ;
2021-08-23 16:50:18 +02:00
}
public static void FixConsecutiveSceneFrames ( string sceneFramesPath , string sourceFramesPath )
{
if ( ! Directory . Exists ( sceneFramesPath ) | | IoUtils . GetAmountOfFiles ( sceneFramesPath , false ) < 1 )
return ;
List < string > sceneFrames = IoUtils . GetFilesSorted ( sceneFramesPath ) . Select ( x = > Path . GetFileNameWithoutExtension ( x ) ) . ToList ( ) ;
List < string > sourceFrames = IoUtils . GetFilesSorted ( sourceFramesPath ) . Select ( x = > Path . GetFileNameWithoutExtension ( x ) ) . ToList ( ) ;
List < string > sceneFramesToDelete = new List < string > ( ) ;
foreach ( string scnFrame in sceneFrames )
{
if ( sceneFramesToDelete . Contains ( scnFrame ) )
continue ;
int sourceIndexForScnFrame = sourceFrames . IndexOf ( scnFrame ) ; // Get source index of scene frame
if ( ( sourceIndexForScnFrame + 1 ) = = sourceFrames . Count )
continue ;
string followingFrame = sourceFrames [ sourceIndexForScnFrame + 1 ] ; // Get filename/timestamp of the next source frame
if ( sceneFrames . Contains ( followingFrame ) ) // If next source frame is in scene folder, add to deletion list
sceneFramesToDelete . Add ( followingFrame ) ;
}
foreach ( string frame in sceneFramesToDelete )
2022-07-20 18:10:31 +02:00
IoUtils . TryDeleteIfExists ( Path . Combine ( sceneFramesPath , frame + I . currentSettings . framesExt ) ) ;
2021-08-23 16:50:18 +02:00
}
2022-04-07 14:20:28 +02:00
public static int GetRoundedInterpFramesPerInputFrame ( float factor , bool roundDown = true )
{
if ( roundDown )
return ( int ) Math . Floor ( factor ) - 1 ;
else
return factor . RoundToInt ( ) ;
}
2021-08-23 16:50:18 +02:00
}
}