2021-08-23 16:50:18 +02:00
using Flowframes.Media ;
using Flowframes.Data ;
using Flowframes.Forms ;
using Flowframes.IO ;
using Flowframes.Os ;
using Flowframes.Ui ;
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
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
{
2023-02-20 16:02:51 +01:00
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 )
{
2023-12-28 15:45:36 +01:00
string basePath = Path . Combine ( Environment . GetEnvironmentVariable ( "LOCALAPPDATA" ) , "Temp" , "Flowframes" ) ;
int tempFolderLoc = Config . GetInt ( Config . Key . tempFolderLoc ) ;
2021-08-23 16:50:18 +02:00
2023-12-28 15:45:36 +01:00
switch ( tempFolderLoc )
2021-08-23 16:50:18 +02:00
{
2023-12-28 15:45:36 +01:00
case 1 :
basePath = inPath . GetParentDir ( ) ;
break ;
case 2 :
basePath = outPath ;
break ;
case 3 :
basePath = Paths . GetSessionDataPath ( ) ;
break ;
case 4 :
string custPath = Config . Get ( Config . Key . tempDirCustom ) ;
if ( IoUtils . IsDirValid ( custPath ) )
{
basePath = custPath ;
}
break ;
2021-08-23 16:50:18 +02:00
}
2023-12-28 15:45:36 +01:00
string folderName = Path . GetFileNameWithoutExtension ( inPath ) . StripBadChars ( ) . Remove ( " " ) . Trunc ( 30 , false ) + ".tmp" ;
return Path . Combine ( basePath , folderName ) ;
2021-08-23 16:50:18 +02:00
}
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
2024-09-03 22:01:32 +02:00
if ( passes & & IoUtils . IsPathOneDrive ( s . inPath ) | | IoUtils . IsPathOneDrive ( s . outPath ) )
{
UiUtils . ShowMessageBox ( "OneDrive paths are not supported. Please use a local path instead." ) ;
passes = false ;
}
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 ;
}
2024-09-03 22:01:32 +02:00
if ( passes & & s . tempFolder . StartsWith ( @"\\" ) | | IoUtils . IsPathOneDrive ( s . tempFolder ) )
2022-05-03 16:00:55 +02:00
{
2024-09-03 22:01:32 +02:00
UiUtils . ShowMessageBox ( "Flowframes does not support network paths as a temp folder!\nPlease use a local path instead." ) ;
2022-05-03 16:00:55 +02:00
passes = false ;
}
2023-01-15 17:23:49 +01:00
string fpsLimitValue = Config . Get ( Config . Key . maxFps ) ;
2023-12-21 05:51:28 +01:00
float fpsLimit = ( fpsLimitValue . Contains ( "/" ) ? new Fraction ( fpsLimitValue ) . GetFloat ( ) : fpsLimitValue . GetFloat ( ) ) ;
2023-01-15 17:23:49 +01:00
int maxFps = s . outSettings . Encoder . GetInfo ( ) . MaxFramerate ;
if ( passes & & s . outFps . GetFloat ( ) < 1f | | ( s . outFps . GetFloat ( ) > maxFps & & ! ( fpsLimit > 0 & & fpsLimit < = maxFps ) ) )
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." ;
2023-01-15 17:23:49 +01:00
UiUtils . ShowMessageBox ( $"Invalid output frame rate ({s.outFps.GetFloat()}).\nMust be 1-{maxFps}. Either lower the interpolation factor or use the \" Maximum Output Frame Rate \ " option.{imgSeqNote}" ) ;
2021-08-23 16:50:18 +02:00
passes = false ;
}
2023-12-21 05:51:28 +01:00
float fpsLimitFloat = fpsLimitValue . GetFloat ( ) ;
2023-12-22 05:34:32 +01:00
if ( fpsLimitFloat > 0 & & fpsLimitFloat < s . outFps . GetFloat ( ) )
2023-12-21 05:51:28 +01:00
Interpolate . InterpProgressMultiplier = s . outFps . GetFloat ( ) / fpsLimitFloat ;
else
Interpolate . InterpProgressMultiplier = 1f ;
2021-08-23 16:50:18 +02:00
if ( ! passes )
I . Cancel ( "Invalid settings detected." , true ) ;
return passes ;
}
2023-01-08 14:17:11 +01:00
catch ( Exception e )
2021-08-23 16:50:18 +02:00
{
Logger . Log ( $"Failed to run InputIsValid: {e.Message}\n{e.StackTrace}" , true ) ;
return false ;
}
}
2024-08-21 14:45:46 +02:00
public static bool CheckAiAvailable ( AiInfo 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 ;
}
2024-08-21 14:45:46 +02:00
public static void ShowWarnings ( float factor , AiInfo ai )
2021-08-30 16:58:19 +02:00
{
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 ;
}
2023-01-15 17:23:49 +01:00
public static async Task < bool > CheckEncoderValid ( )
2021-08-23 16:50:18 +02:00
{
2023-10-24 17:39:44 +02:00
string enc = I . currentSettings . outSettings . Encoder . GetInfo ( ) . Name ;
2022-07-19 21:54:25 +02:00
2022-10-14 09:00:47 +02:00
if ( enc . ToLowerInvariant ( ) . 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 ;
}
2023-01-08 14:17:11 +01:00
public static async Task < Size > GetOutputResolution ( string inputPath , bool pad , bool print = false )
2021-08-23 16:50:18 +02:00
{
Size resolution = await GetMediaResolutionCached . GetSizeAsync ( inputPath ) ;
2023-01-08 14:17:11 +01:00
return GetOutputResolution ( resolution , pad , print ) ;
2021-08-23 16:50:18 +02:00
}
2023-01-08 14:17:11 +01:00
public static Size GetOutputResolution ( Size inputRes , bool pad , bool print = false )
2021-08-23 16:50:18 +02:00
{
2023-01-08 14:17:11 +01:00
Size res = new Size ( inputRes . Width , inputRes . Height ) ;
int maxHeight = Config . GetInt ( Config . Key . maxVidHeight ) ;
int mod = pad ? FfmpegCommands . GetModulo ( ) : 1 ;
float factor = res . Height > maxHeight ? ( float ) maxHeight / res . Height : 1f ; // Calculate downscale factor if bigger than max, otherwise just use 1x
Logger . Log ( $"Un-rounded downscaled size: {(res.Width * factor).ToString(" 0. # # # ")}x{(res.Height * factor).ToString(" 0. # # # ")}" , true ) ;
int width = RoundDivisibleBy ( ( res . Width * factor ) . RoundToInt ( ) , mod ) ;
int height = RoundDivisibleBy ( ( res . Height * factor ) . RoundToInt ( ) , mod ) ;
2023-01-31 11:41:39 +01:00
res = new Size ( width , height ) ;
2021-08-23 16:50:18 +02:00
2023-01-08 14:17:11 +01:00
if ( print & & factor < 1f )
Logger . Log ( $"Video is bigger than the maximum - Downscaling to {width}x{height}." ) ;
if ( res ! = inputRes )
Logger . Log ( $"Scaled {inputRes.Width}x{inputRes.Height} to {res.Width}x{res.Height}" , true ) ;
return res ;
2021-08-23 16:50:18 +02:00
}
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 ;
}
2023-01-15 17:23:49 +01:00
if ( current . outSettings . Format = = Enums . Output . Format . Gif )
2021-12-27 22:58:19 +01:00
{
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 ( ) ;
}
2023-02-15 12:27:36 +01:00
2023-02-20 16:02:51 +01:00
public static Fraction AskForFramerate ( string mediaName , bool isImageSequence = true )
2023-02-15 12:27:36 +01:00
{
2023-02-20 16:00:27 +01:00
string text = $"Please enter an input frame rate to use for{(isImageSequence ? " the image sequence " : " ")} '{mediaName.Trunc(80)}'." ;
2023-02-15 12:27:36 +01:00
PromptForm form = new PromptForm ( "Enter Frame Rate" , text , "15" ) ;
form . ShowDialog ( ) ;
return new Fraction ( form . EnteredText ) ;
}
2021-08-23 16:50:18 +02:00
}
}