2020-11-23 16:51:05 +01:00
using Flowframes.Data ;
using Flowframes.Forms ;
using Flowframes.IO ;
2020-12-27 22:52:14 +01:00
using Flowframes.MiscUtils ;
2020-11-23 16:51:05 +01:00
using Flowframes.OS ;
using Flowframes.UI ;
using System ;
using System.Collections.Generic ;
2020-12-23 00:07:06 +01:00
using System.Diagnostics ;
2020-11-23 16:51:05 +01:00
using System.Drawing ;
using System.IO ;
using System.Linq ;
using System.Text.RegularExpressions ;
2020-12-20 21:25:34 +01:00
using System.Threading.Tasks ;
2020-11-23 16:51:05 +01:00
using System.Windows.Forms ;
using i = Flowframes . Interpolate ;
2021-01-15 22:43:13 +01:00
using Padding = Flowframes . Data . Padding ;
2020-11-23 16:51:05 +01:00
namespace Flowframes.Main
{
class InterpolateUtils
{
public static PictureBox preview ;
public static BigPreviewForm bigPreviewForm ;
2021-01-15 22:43:13 +01:00
public static async Task CopyLastFrame ( int lastFrameNum )
{
try
{
lastFrameNum - - ; // We have to do this as extracted frames start at 0, not 1
bool frameFolderInput = IOUtils . IsPathDirectory ( i . current . inPath ) ;
string targetPath = Path . Combine ( i . current . framesFolder , lastFrameNum . ToString ( ) . PadLeft ( Padding . inputFrames , '0' ) + ".png" ) ;
if ( File . Exists ( targetPath ) ) return ;
Size res = IOUtils . GetImage ( IOUtils . GetFilesSorted ( i . current . framesFolder , false ) . First ( ) ) . Size ;
if ( frameFolderInput )
{
string lastFramePath = IOUtils . GetFilesSorted ( i . current . inPath , false ) . Last ( ) ;
await FFmpegCommands . ExtractLastFrame ( lastFramePath , targetPath , res ) ;
}
else
{
await FFmpegCommands . ExtractLastFrame ( i . current . inPath , targetPath , res ) ;
}
}
catch ( Exception e )
{
Logger . Log ( "CopyLastFrame Error: " + e . Message ) ;
}
}
2020-12-20 21:25:34 +01:00
public static string GetOutExt ( bool withDot = false )
2020-12-10 16:37:33 +01:00
{
2020-12-20 21:25:34 +01:00
string dotStr = withDot ? "." : "" ;
2021-01-16 00:19:23 +01:00
if ( Config . GetBool ( "jpegInterp" ) )
2020-12-20 21:25:34 +01:00
return dotStr + "jpg" ;
return dotStr + "png" ;
}
public static int targetFrames ;
2021-01-18 15:32:45 +01:00
public static string currentOutdir ;
2020-12-20 21:25:34 +01:00
public static int currentFactor ;
2021-01-18 15:32:45 +01:00
public static bool progressPaused = false ;
public static bool progCheckRunning = false ;
2020-12-20 21:25:34 +01:00
public static async void GetProgressByFrameAmount ( string outdir , int target )
{
2021-01-18 15:32:45 +01:00
progCheckRunning = true ;
targetFrames = target ;
currentOutdir = outdir ;
Logger . Log ( $"Starting GetProgressByFrameAmount() loop for outdir '{currentOutdir}', target is {target} frames" , true ) ;
2020-12-20 21:25:34 +01:00
bool firstProgUpd = true ;
Program . mainForm . SetProgress ( 0 ) ;
while ( Program . busy )
{
2021-01-22 01:43:47 +01:00
if ( ! progressPaused & & AiProcess . processTime . IsRunning & & ! i . canceled & & Directory . Exists ( currentOutdir ) )
2020-12-20 21:25:34 +01:00
{
if ( firstProgUpd & & Program . mainForm . IsInFocus ( ) )
Program . mainForm . SetTab ( "preview" ) ;
firstProgUpd = false ;
2021-01-18 15:32:45 +01:00
string [ ] frames = IOUtils . GetFilesSorted ( currentOutdir , $"*.{GetOutExt()}" ) ;
2020-12-20 21:25:34 +01:00
if ( frames . Length > 1 )
UpdateInterpProgress ( frames . Length , targetFrames , frames [ frames . Length - 1 ] ) ;
2021-01-06 21:44:09 +01:00
if ( frames . Length > = targetFrames )
break ;
2020-12-20 21:25:34 +01:00
await Task . Delay ( GetProgressWaitTime ( frames . Length ) ) ;
}
else
{
await Task . Delay ( 200 ) ;
}
}
2021-01-18 15:32:45 +01:00
progCheckRunning = false ;
2021-01-22 01:43:47 +01:00
if ( i . canceled )
Program . mainForm . SetProgress ( 0 ) ;
2020-12-10 16:37:33 +01:00
}
2020-11-23 16:51:05 +01:00
public static void UpdateInterpProgress ( int frames , int target , string latestFramePath = "" )
{
2020-12-20 21:25:34 +01:00
frames = frames . Clamp ( 0 , target ) ;
2020-11-23 16:51:05 +01:00
int percent = ( int ) Math . Round ( ( ( float ) frames / target ) * 100f ) ;
Program . mainForm . SetProgress ( percent ) ;
2020-11-25 14:04:31 +01:00
float generousTime = ( ( AiProcess . processTime . ElapsedMilliseconds - AiProcess . lastStartupTimeMs ) / 1000f ) ;
2020-11-23 16:51:05 +01:00
float fps = ( float ) frames / generousTime ;
2020-12-20 21:25:34 +01:00
string fpsIn = ( fps / currentFactor ) . ToString ( "0.00" ) ;
2020-11-23 16:51:05 +01:00
string fpsOut = fps . ToString ( "0.00" ) ;
float secondsPerFrame = generousTime / ( float ) frames ;
int framesLeft = target - frames ;
float eta = framesLeft * secondsPerFrame ;
2020-12-20 21:25:34 +01:00
string etaStr = FormatUtils . Time ( new TimeSpan ( 0 , 0 , eta . RoundToInt ( ) ) , false ) ;
2020-11-23 16:51:05 +01:00
bool replaceLine = Regex . Split ( Logger . textbox . Text , "\r\n|\r|\n" ) . Last ( ) . Contains ( "Average Speed: " ) ;
2020-11-30 02:14:04 +01:00
string logStr = $"Interpolated {frames}/{target} frames ({percent}%) - Average Speed: {fpsIn} FPS In / {fpsOut} FPS Out - " ;
logStr + = $"Time: {FormatUtils.Time(AiProcess.processTime.Elapsed)} - ETA: {etaStr}" ;
if ( AutoEncode . busy ) logStr + = " - Encoding..." ;
Logger . Log ( logStr , false , replaceLine ) ;
2020-11-23 16:51:05 +01:00
try
{
2020-12-20 21:25:34 +01:00
if ( ! string . IsNullOrWhiteSpace ( latestFramePath ) & & frames > currentFactor )
2020-11-23 16:51:05 +01:00
{
if ( bigPreviewForm = = null & & ! preview . Visible /* ||Program.mainForm.WindowState != FormWindowState.Minimized */ /* || !Program.mainForm.IsInFocus()*/ ) return ; // Skip if the preview is not visible or the form is not in focus
Image img = IOUtils . GetImage ( latestFramePath ) ;
2020-11-27 14:35:32 +01:00
SetPreviewImg ( img ) ;
2020-11-23 16:51:05 +01:00
}
}
catch { }
}
2020-11-27 14:35:32 +01:00
public static void SetPreviewImg ( Image img )
{
if ( img = = null )
return ;
preview . Image = img ;
if ( bigPreviewForm ! = null )
bigPreviewForm . SetImage ( img ) ;
}
2020-12-27 22:52:14 +01:00
public static Dictionary < string , int > frameCountCache = new Dictionary < string , int > ( ) ;
public static async Task < int > GetInputFrameCountAsync ( string path )
2020-12-07 00:41:07 +01:00
{
2020-12-27 22:52:14 +01:00
string hash = await IOUtils . GetHashAsync ( path , IOUtils . Hash . xxHash ) ; // Get checksum for caching
if ( hash . Length > 1 & & frameCountCache . ContainsKey ( hash ) )
{
Logger . Log ( $"FrameCountCache contains this hash ({hash}), using cached frame count." , true ) ;
return frameCountCache [ hash ] ;
}
else
{
Logger . Log ( $"Hash ({hash}) not cached, reading frame count." , true ) ;
}
int frameCount = 0 ;
2020-12-07 00:41:07 +01:00
if ( IOUtils . IsPathDirectory ( path ) )
2020-12-27 22:52:14 +01:00
frameCount = IOUtils . GetAmountOfFiles ( path , false ) ;
2020-12-07 00:41:07 +01:00
else
2020-12-27 22:52:14 +01:00
frameCount = await FFmpegCommands . GetFrameCountAsync ( path ) ;
if ( hash . Length > 1 & & frameCount > 5000 ) // Cache if >5k frames to avoid re-reading it every single time
{
Logger . Log ( $"Adding hash ({hash}) with frame count {frameCount} to cache." , true ) ;
frameCountCache [ hash ] = frameCount ; // Use CRC32 instead of path to avoid using cached value if file was changed
}
return frameCount ;
2020-12-07 00:41:07 +01:00
}
2020-11-23 16:51:05 +01:00
public static int GetProgressWaitTime ( int numFrames )
{
2021-01-07 11:02:43 +01:00
float hddMultiplier = ! Program . lastInputPathIsSsd ? 2f : 1f ;
2020-11-23 16:51:05 +01:00
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 ( ) ;
2021-01-07 11:02:43 +01:00
2020-11-23 16:51:05 +01:00
if ( Config . GetInt ( "tempFolderLoc" ) = = 1 )
basePath = outPath . GetParentDir ( ) ;
2021-01-07 11:02:43 +01:00
2020-11-23 16:51:05 +01:00
if ( Config . GetInt ( "tempFolderLoc" ) = = 2 )
basePath = outPath ;
2021-01-07 11:02:43 +01:00
2020-11-23 16:51:05 +01:00
if ( Config . GetInt ( "tempFolderLoc" ) = = 3 )
basePath = IOUtils . GetExeDir ( ) ;
2021-01-07 11:02:43 +01:00
2020-11-23 16:51:05 +01:00
if ( Config . GetInt ( "tempFolderLoc" ) = = 4 )
{
string custPath = Config . Get ( "tempDirCustom" ) ;
if ( IOUtils . IsDirValid ( custPath ) )
basePath = custPath ;
}
2020-12-04 16:53:39 +01:00
return Path . Combine ( basePath , Path . GetFileNameWithoutExtension ( inPath ) . StripBadChars ( ) . Remove ( " " ) . Trunc ( 30 , false ) + "-temp" ) ;
2020-11-23 16:51:05 +01:00
}
2021-01-13 23:05:23 +01:00
public static bool InputIsValid ( string inDir , string outDir , float fpsOut , int interp , Interpolate . OutMode outMode )
2020-11-23 16:51:05 +01:00
{
2020-12-04 16:53:39 +01:00
bool passes = true ;
2020-11-23 16:51:05 +01:00
bool isFile = ! IOUtils . IsPathDirectory ( inDir ) ;
2020-12-06 18:49:53 +01:00
if ( ( passes & & isFile & & ! IOUtils . IsFileValid ( inDir ) ) | | ( ! isFile & & ! IOUtils . IsDirValid ( inDir ) ) )
2020-11-23 16:51:05 +01:00
{
ShowMessage ( "Input path is not valid!" ) ;
passes = false ;
}
2020-12-06 18:49:53 +01:00
if ( passes & & ! IOUtils . IsDirValid ( outDir ) )
2020-11-23 16:51:05 +01:00
{
ShowMessage ( "Output path is not valid!" ) ;
passes = false ;
}
2020-12-06 18:49:53 +01:00
if ( passes & & interp ! = 2 & & interp ! = 4 & & interp ! = 8 )
2020-11-23 16:51:05 +01:00
{
ShowMessage ( "Interpolation factor is not valid!" ) ;
passes = false ;
}
2021-01-16 01:19:07 +01:00
if ( passes & & outMode = = i . OutMode . VidGif & & fpsOut > 50 )
2021-01-07 12:15:13 +01:00
{
ShowMessage ( "Invalid output frame rate!\nGIF does not properly support frame rates above 40 FPS.\nPlease use MP4, WEBM or another video format." ) ;
passes = false ;
}
2020-12-06 18:49:53 +01:00
if ( passes & & fpsOut < 1 | | fpsOut > 500 )
2020-11-23 16:51:05 +01:00
{
2021-01-07 12:15:13 +01:00
ShowMessage ( "Invalid output frame rate - Must be 1-500." ) ;
2020-11-23 16:51:05 +01:00
passes = false ;
}
if ( ! passes )
2020-12-06 20:29:47 +01:00
i . Cancel ( "Invalid settings detected." , true ) ;
2020-11-23 16:51:05 +01:00
return passes ;
}
public static void PathAsciiCheck ( string inpath , string outpath )
{
2020-11-26 23:47:09 +01:00
bool shownMsg = false ;
2020-11-23 16:51:05 +01:00
if ( OSUtils . HasNonAsciiChars ( inpath ) )
2020-11-26 23:47:09 +01:00
{
ShowMessage ( "Warning: Input path includes non-ASCII characters. This might cause problems." ) ;
shownMsg = true ;
}
2020-11-23 16:51:05 +01:00
2020-11-26 23:47:09 +01:00
if ( ! shownMsg & & OSUtils . HasNonAsciiChars ( outpath ) )
ShowMessage ( "Warning: Output path includes non-ASCII characters. This might cause problems." ) ;
2020-11-23 16:51:05 +01:00
}
public static void GifCompatCheck ( Interpolate . OutMode outMode , float fpsOut , int targetFrameCount )
{
if ( outMode ! = Interpolate . OutMode . VidGif )
return ;
if ( fpsOut > = 50f )
Logger . Log ( "Warning: GIFs above 50 FPS might play slower on certain software/hardware! MP4 is recommended for higher frame rates." ) ;
int maxGifFrames = 200 ;
if ( targetFrameCount > maxGifFrames )
{
ShowMessage ( $"You can't use GIF with more than {maxGifFrames} output frames!\nPlease use MP4 for this." , "Error" ) ;
i . Cancel ( $"Can't use GIF encoding with more than {maxGifFrames} frames!" ) ;
}
}
public static bool CheckAiAvailable ( AI ai )
{
2020-11-25 14:04:31 +01:00
if ( ! PkgUtils . IsAiAvailable ( ai ) )
2020-11-23 16:51:05 +01:00
{
2020-11-25 14:04:31 +01:00
ShowMessage ( "The selected AI is not installed!\nYou can download it from the Package Installer." , "Error" ) ;
i . Cancel ( "Selected AI not available." , true ) ;
2020-11-23 16:51:05 +01:00
return false ;
}
return true ;
}
public static bool CheckDeleteOldTempFolder ( )
{
2020-12-17 11:32:45 +01:00
if ( ! IOUtils . TryDeleteIfExists ( i . current . tempFolder ) )
2020-11-23 16:51:05 +01:00
{
ShowMessage ( "Failed to remove an existing temp folder of this video!\nMake sure you didn't open any frames in an editor." , "Error" ) ;
i . Cancel ( ) ;
return false ;
}
return true ;
}
public static bool CheckPathValid ( string path )
{
if ( IOUtils . IsPathDirectory ( path ) )
{
if ( ! IOUtils . IsDirValid ( path ) )
{
ShowMessage ( "Input directory is not valid." ) ;
i . Cancel ( ) ;
return false ;
}
}
else
{
if ( ! IsVideoValid ( path ) )
{
ShowMessage ( "Input video file is not valid." ) ;
return false ;
}
}
return true ;
}
public static bool IsVideoValid ( string videoPath )
{
if ( videoPath = = null | | ! IOUtils . IsFileValid ( videoPath ) )
return false ;
2020-12-13 23:44:23 +01:00
// string ext = Path.GetExtension(videoPath).ToLower();
// if (!Formats.supported.Contains(ext))
// return false;
return true ;
2020-11-23 16:51:05 +01:00
}
public static void ShowMessage ( string msg , string title = "Message" )
{
if ( ! BatchProcessing . busy )
MessageBox . Show ( msg , title ) ;
Logger . Log ( "Message: " + msg , true ) ;
}
2020-12-15 14:46:33 +01:00
2021-01-15 15:07:40 +01:00
public static async Task < Size > GetOutputResolution ( string inputPath , bool print )
2020-12-15 14:46:33 +01:00
{
2021-01-15 15:07:40 +01:00
Size resolution = await IOUtils . GetVideoOrFramesRes ( inputPath ) ;
2020-12-22 23:45:07 +01:00
return GetOutputResolution ( resolution , print ) ;
}
public static Size GetOutputResolution ( Size inputRes , bool print = false )
{
2020-12-15 14:46:33 +01:00
int maxHeight = RoundDiv2 ( Config . GetInt ( "maxVidHeight" ) ) ;
2020-12-22 23:45:07 +01:00
if ( inputRes . Height > maxHeight )
2020-12-15 14:46:33 +01:00
{
2020-12-22 23:45:07 +01:00
float factor = ( float ) maxHeight / inputRes . Height ;
Logger . Log ( $"Un-rounded downscaled size: {(inputRes.Width * factor).ToString(" 0.00 ")}x{Config.GetInt(" maxVidHeight ")}" , true ) ;
int width = RoundDiv2 ( ( inputRes . Width * factor ) . RoundToInt ( ) ) ;
if ( print )
2020-12-21 15:03:31 +01:00
Logger . Log ( $"Video is bigger than the maximum - Downscaling to {width}x{maxHeight}." ) ;
2020-12-15 14:46:33 +01:00
return new Size ( width , maxHeight ) ;
}
else
{
2020-12-22 23:45:07 +01:00
return new Size ( RoundDiv2 ( inputRes . Width ) , RoundDiv2 ( inputRes . Height ) ) ;
2020-12-15 14:46:33 +01:00
}
}
2021-01-17 20:05:39 +01:00
public static int RoundDiv2 ( int n ) // Round to a number that's divisible by 2 (for h264 etc)
2020-12-15 14:46:33 +01:00
{
int a = ( n / 2 ) * 2 ; // Smaller multiple
int b = a + 2 ; // Larger multiple
return ( n - a > b - n ) ? b : a ; // Return of closest of two
}
2020-12-23 00:07:06 +01:00
2021-01-18 15:32:45 +01:00
public static bool CanUseAutoEnc ( bool stepByStep , InterpSettings current )
2021-01-05 17:23:37 +01:00
{
2021-01-06 17:41:18 +01:00
AutoEncode . UpdateChunkAndBufferSizes ( ) ;
2021-01-05 17:23:37 +01:00
2021-01-18 15:32:45 +01:00
if ( current . alpha )
{
Logger . Log ( $"Not Using AutoEnc: Alpha mode is enabled." , true ) ;
return false ;
}
if ( ! current . outMode . ToString ( ) . ToLower ( ) . Contains ( "vid" ) | | current . outMode . ToString ( ) . ToLower ( ) . Contains ( "gif" ) )
2021-01-05 17:23:37 +01:00
{
Logger . Log ( $"Not Using AutoEnc: Out Mode is not video ({current.outMode.ToString()})" , true ) ;
return false ;
}
if ( stepByStep & & ! Config . GetBool ( "sbsAllowAutoEnc" ) )
{
Logger . Log ( $"Not Using AutoEnc: Using step-by-step mode, but 'sbsAllowAutoEnc' is false." , true ) ;
return false ;
}
if ( ! stepByStep & & Config . GetInt ( "autoEncMode" ) = = 0 )
{
Logger . Log ( $"Not Using AutoEnc: 'autoEncMode' is 0." , true ) ;
return false ;
}
int inFrames = IOUtils . GetAmountOfFiles ( current . framesFolder , false ) ;
if ( inFrames * current . interpFactor < ( AutoEncode . chunkSize + AutoEncode . safetyBufferFrames ) * 1.2f )
{
2021-01-06 17:41:18 +01:00
Logger . Log ( $"Not Using AutoEnc: Input frames ({inFrames}) * factor ({current.interpFactor}) is smaller than (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f)" , true ) ;
2021-01-05 17:23:37 +01:00
return false ;
}
return true ;
}
2021-01-15 15:07:40 +01:00
public static async Task < bool > UseUHD ( )
2021-01-05 17:23:37 +01:00
{
2021-01-15 15:07:40 +01:00
return ( await GetOutputResolution ( i . current . inPath , false ) ) . Height > = Config . GetInt ( "uhdThresh" ) ;
2021-01-05 17:23:37 +01:00
}
2020-12-23 00:07:06 +01:00
public static void FixConsecutiveSceneFrames ( string sceneFramesPath , string sourceFramesPath )
{
2020-12-23 12:42:06 +01:00
if ( ! Directory . Exists ( sceneFramesPath ) | | IOUtils . GetAmountOfFiles ( sceneFramesPath , false ) < 1 )
return ;
2020-12-23 00:07:06 +01:00
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
2020-12-23 16:13:04 +01:00
if ( ( sourceIndexForScnFrame + 1 ) = = sourceFrames . Count )
continue ;
2020-12-23 00:07:06 +01:00
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 )
IOUtils . TryDeleteIfExists ( Path . Combine ( sceneFramesPath , frame + ".png" ) ) ;
}
2020-11-23 16:51:05 +01:00
}
}