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 ;
2024-11-12 22:13:59 +01:00
using Flowframes.MiscUtils ;
2021-08-23 16:50:18 +02:00
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 )
{
2024-11-28 16:08:04 +01:00
string basePath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "Temp" , "Flowframes" ) ;
2023-12-28 15:45:36 +01:00
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
}
2024-11-28 16:08:04 +01:00
string folderName = Path . GetFileNameWithoutExtension ( inPath ) . StripBadChars ( ) . Remove ( " " ) . Trunc ( 35 , false ) + "_tmp" ;
2023-12-28 15:45:36 +01:00
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 ;
}
2024-11-28 16:08:04 +01:00
float fpsLimit = s . outFpsResampled . Float ;
int maxEncoderFps = s . outSettings . Encoder . GetInfo ( ) . MaxFramerate ;
2023-01-15 17:23:49 +01:00
2024-11-28 16:08:04 +01:00
if ( passes & & s . outFps . Float < 1f | | ( s . outFps . Float > maxEncoderFps & & ! ( fpsLimit > 0 & & fpsLimit < = maxEncoderFps ) ) )
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." ;
2024-11-28 16:08:04 +01:00
UiUtils . ShowMessageBox ( $"Invalid output frame rate ({s.outFps.Float}).\nMust be 1-{maxEncoderFps}. Either lower the interpolation factor or use the \" Maximum Output Frame Rate \ " option.{imgSeqNote}" ) ;
2021-08-23 16:50:18 +02:00
passes = false ;
}
2024-11-28 16:08:04 +01:00
I . InterpProgressMultiplier = s . FpsResampling ? s . outFps . Float / fpsLimit : 1f ;
2023-12-21 05:51:28 +01:00
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-11-21 01:22:31 +01:00
public static bool CheckAiAvailable ( AiInfo ai , ModelCollection . ModelInfo model , bool allowNullModel = false )
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 ;
}
2024-11-21 01:22:31 +01:00
if ( ! allowNullModel & & ( 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 ;
}
2024-11-21 01:22:31 +01:00
if ( I . currentSettings . ai . NameInternal . Upper ( ) . Contains ( "CUDA" ) & & NvApi . NvGpus . 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
2024-09-03 22:08:38 +02:00
if ( enc . Lower ( ) . 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 )
{
2024-11-12 22:13:59 +01:00
return videoPath ! = null & & IoUtils . IsFileValid ( videoPath ) ;
2021-08-23 16:50:18 +02:00
}
2024-11-12 22:13:59 +01:00
public static async Task < Size > GetOutputResolution ( FfmpegCommands . ModuloMode moduloMode , string inputPath , bool print = false )
2021-08-23 16:50:18 +02:00
{
Size resolution = await GetMediaResolutionCached . GetSizeAsync ( inputPath ) ;
2024-11-12 22:13:59 +01:00
return GetInterpolationResolution ( moduloMode , resolution , print ) ;
2021-08-23 16:50:18 +02:00
}
2024-11-12 22:13:59 +01:00
public static Size GetInterpolationResolution ( FfmpegCommands . ModuloMode moduloMode , Size inputRes , bool onlyRoundUp = true , 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 ) ;
2024-11-12 22:13:59 +01:00
int modulo = FfmpegCommands . GetModulo ( moduloMode ) ;
2023-01-08 14:17:11 +01:00
float factor = res . Height > maxHeight ? ( float ) maxHeight / res . Height : 1f ; // Calculate downscale factor if bigger than max, otherwise just use 1x
2024-11-12 22:13:59 +01:00
int width = RoundDivisibleBy ( ( res . Width * factor ) . RoundToInt ( ) , modulo , onlyRoundUp ) ;
int height = RoundDivisibleBy ( ( res . Height * factor ) . RoundToInt ( ) , modulo , onlyRoundUp ) ;
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}." ) ;
2024-11-12 22:13:59 +01:00
Logger . Log ( $"Scaled input res {inputRes.Width}x{inputRes.Height} to {res.Width}x{res.Height} ({moduloMode})" , true ) ;
2023-01-08 14:17:11 +01:00
return res ;
2021-08-23 16:50:18 +02:00
}
2024-11-12 22:13:59 +01:00
public static int RoundDivisibleBy ( float number , int divisibleBy , bool onlyRoundUp = false )
2021-08-23 16:50:18 +02:00
{
2024-11-12 22:13:59 +01:00
int numberInt = number . RoundToInt ( ) ;
if ( divisibleBy = = 0 )
return numberInt ;
return onlyRoundUp
? ( int ) Math . Ceiling ( ( double ) number / divisibleBy ) * divisibleBy
: ( int ) Math . Round ( ( double ) number / divisibleBy ) * divisibleBy ;
2021-08-23 16:50:18 +02:00
}
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 ;
}
2024-11-12 22:13:59 +01:00
public static bool UseUhd ( )
2021-08-23 16:50:18 +02:00
{
2024-11-12 22:13:59 +01:00
return UseUhd ( I . currentSettings . OutputResolution ) ;
2022-07-20 18:10:31 +02:00
}
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 ;
2024-11-12 22:13:59 +01:00
int sourceIndexForScnFrame = sourceFrames . IndexOf ( scnFrame ) ; // Get source index of scene frame
2021-08-23 16:50:18 +02:00
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
{
2024-11-08 12:07:43 +01:00
string text = $"Please enter the source frame rate for{(isImageSequence ? " the image sequence " : " ")} '{mediaName.Trunc(80)}'." ;
var form = new PromptForm ( "Enter Frame Rate" , text , "15" ) ;
2023-02-15 12:27:36 +01:00
form . ShowDialog ( ) ;
return new Fraction ( form . EnteredText ) ;
}
2021-08-23 16:50:18 +02:00
}
}