2020-11-23 16:51:05 +01:00
using Flowframes ;
using Flowframes.FFmpeg ;
using Flowframes.IO ;
using Flowframes.Magick ;
using Flowframes.Main ;
using Flowframes.OS ;
using Flowframes.UI ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using System.Windows.Forms ;
2020-12-04 16:53:39 +01:00
using Padding = Flowframes . Data . Padding ;
2020-11-23 16:51:05 +01:00
using i = Flowframes . Interpolate ;
2020-12-20 21:25:34 +01:00
using System.Diagnostics ;
2020-12-23 17:15:42 +01:00
using Flowframes.AudioVideo ;
2020-11-23 16:51:05 +01:00
namespace Flowframes.Main
{
class CreateVideo
{
2020-12-10 22:39:45 +01:00
static string currentOutFile ; // Keeps track of the out file, in case it gets renamed (FPS limiting, looping, etc) before finishing export
2020-12-02 15:34:59 +01:00
public static async Task Export ( string path , string outPath , i . OutMode mode )
2020-11-23 16:51:05 +01:00
{
2020-12-02 15:34:59 +01:00
if ( ! mode . ToString ( ) . ToLower ( ) . Contains ( "vid" ) ) // Copy interp frames out of temp folder and skip video export for image seq export
{
try
{
2020-12-20 21:25:34 +01:00
await CopyOutputFrames ( path , Path . GetFileNameWithoutExtension ( outPath ) ) ;
2020-12-02 15:34:59 +01:00
}
catch ( Exception e )
{
Logger . Log ( "Failed to move interp frames folder: " + e . Message ) ;
}
2020-11-23 16:51:05 +01:00
return ;
2020-12-02 15:34:59 +01:00
}
2020-12-20 21:25:34 +01:00
if ( IOUtils . GetAmountOfFiles ( path , false , $"*.{InterpolateUtils.GetOutExt()}" ) < = 1 )
2020-11-23 16:51:05 +01:00
{
2020-11-24 12:28:47 +01:00
i . Cancel ( "Output folder does not contain frames - An error must have occured during interpolation!" , AiProcess . hasShownError ) ;
2020-11-23 16:51:05 +01:00
return ;
}
await Task . Delay ( 10 ) ;
Program . mainForm . SetStatus ( "Creating output video from frames..." ) ;
try
{
int maxFps = Config . GetInt ( "maxFps" ) ;
if ( maxFps = = 0 ) maxFps = 500 ;
2020-12-23 16:13:04 +01:00
if ( i . current . outFps > maxFps )
2020-12-17 11:32:45 +01:00
await Encode ( mode , path , outPath , i . current . outFps , maxFps , ( Config . GetInt ( "maxFpsMode" ) = = 1 ) ) ;
2020-11-23 16:51:05 +01:00
else
2020-12-17 11:32:45 +01:00
await Encode ( mode , path , outPath , i . current . outFps ) ;
2020-11-23 16:51:05 +01:00
}
catch ( Exception e )
{
Logger . Log ( "FramesToVideo Error: " + e . Message , false ) ;
MessageBox . Show ( "An error occured while trying to convert the interpolated frames to a video.\nCheck the log for details." ) ;
}
}
2020-12-20 21:25:34 +01:00
static async Task CopyOutputFrames ( string framesPath , string folderName )
{
Program . mainForm . SetStatus ( "Copying output frames..." ) ;
string copyPath = Path . Combine ( i . current . outPath , folderName ) ;
Logger . Log ( $"Copying interpolated frames to '{copyPath}'" ) ;
IOUtils . TryDeleteIfExists ( copyPath ) ;
IOUtils . CreateDir ( copyPath ) ;
Stopwatch sw = new Stopwatch ( ) ;
sw . Restart ( ) ;
string vfrFile = Path . Combine ( framesPath . GetParentDir ( ) , $"vfr-{i.current.interpFactor}x.ini" ) ;
string [ ] vfrLines = IOUtils . ReadLines ( vfrFile ) ;
for ( int idx = 1 ; idx < = vfrLines . Length ; idx + + )
{
string line = vfrLines [ idx - 1 ] ;
string inFilename = line . Split ( '/' ) . Last ( ) . Remove ( "'" ) ;
string framePath = Path . Combine ( framesPath , inFilename ) ;
2021-01-03 22:37:06 +01:00
string outFilename = Path . Combine ( copyPath , idx . ToString ( ) . PadLeft ( Padding . interpFrames , '0' ) ) + Path . GetExtension ( framePath ) ;
2020-12-20 21:25:34 +01:00
if ( ( idx < vfrLines . Length ) & & vfrLines [ idx ] . Contains ( inFilename ) ) // If file is re-used in the next line, copy instead of move
File . Copy ( framePath , outFilename ) ;
else
File . Move ( framePath , outFilename ) ;
2021-01-03 22:37:06 +01:00
if ( sw . ElapsedMilliseconds > = 500 | | idx = = vfrLines . Length )
2020-12-20 21:25:34 +01:00
{
sw . Restart ( ) ;
Logger . Log ( $"Copying interpolated frames to '{Path.GetFileName(copyPath)}' - {idx}/{vfrLines.Length}" , false , true ) ;
await Task . Delay ( 1 ) ;
}
}
}
2020-11-23 16:51:05 +01:00
static async Task Encode ( i . OutMode mode , string framesPath , string outPath , float fps , float changeFps = - 1 , bool keepOriginalFpsVid = true )
{
2020-12-10 22:44:14 +01:00
currentOutFile = outPath ;
2020-12-17 11:32:45 +01:00
string vfrFile = Path . Combine ( framesPath . GetParentDir ( ) , $"vfr-{i.current.interpFactor}x.ini" ) ;
2020-12-08 14:43:03 +01:00
2020-11-23 16:51:05 +01:00
if ( mode = = i . OutMode . VidGif )
2020-12-23 16:13:04 +01:00
{
2021-01-05 14:10:57 +01:00
await FFmpegCommands . FramesToGifConcat ( vfrFile , outPath , fps , true , Config . GetInt ( "gifColors" ) ) ;
2020-12-23 16:13:04 +01:00
}
else
2020-11-23 16:51:05 +01:00
{
2020-12-07 10:48:50 +01:00
int looptimes = GetLoopTimes ( ) ;
2020-11-23 16:51:05 +01:00
bool h265 = Config . GetInt ( "mp4Enc" ) = = 1 ;
int crf = h265 ? Config . GetInt ( "h265Crf" ) : Config . GetInt ( "h264Crf" ) ;
2021-01-02 16:20:21 +01:00
await FFmpegCommands . FramesToVideoConcat ( vfrFile , outPath , mode , fps ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , outPath ) ;
2020-11-29 16:10:31 +01:00
2020-11-23 16:51:05 +01:00
if ( changeFps > 0 )
{
2020-12-10 22:39:45 +01:00
currentOutFile = IOUtils . FilenameSuffix ( outPath , $"-{changeFps.ToString(" 0 ")}fps" ) ;
2020-11-23 16:51:05 +01:00
Program . mainForm . SetStatus ( "Creating video with desired frame rate..." ) ;
2020-12-10 22:39:45 +01:00
await FFmpegCommands . ConvertFramerate ( outPath , currentOutFile , h265 , crf , changeFps , ! keepOriginalFpsVid ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , currentOutFile ) ;
2020-11-23 16:51:05 +01:00
}
2020-12-10 22:39:45 +01:00
if ( looptimes > 0 )
await Loop ( currentOutFile , looptimes ) ;
2020-11-23 16:51:05 +01:00
}
}
2020-11-30 02:14:04 +01:00
public static async Task ChunksToVideo ( string chunksPath , string vfrFile , string outPath )
{
2020-12-23 17:15:42 +01:00
if ( IOUtils . GetAmountOfFiles ( chunksPath , false , $"*{FFmpegUtils.GetExt(i.current.outMode)}" ) < 1 )
2020-11-30 02:14:04 +01:00
{
i . Cancel ( "No video chunks found - An error must have occured during chunk encoding!" , AiProcess . hasShownError ) ;
return ;
}
await Task . Delay ( 10 ) ;
Program . mainForm . SetStatus ( "Merging video chunks..." ) ;
try
{
int maxFps = Config . GetInt ( "maxFps" ) ;
if ( maxFps = = 0 ) maxFps = 500 ;
2020-12-17 11:32:45 +01:00
if ( i . current . outFps > maxFps )
await MergeChunks ( chunksPath , vfrFile , outPath , i . current . outFps , maxFps , ( Config . GetInt ( "maxFpsMode" ) = = 1 ) ) ;
2020-11-30 02:14:04 +01:00
else
2020-12-17 11:32:45 +01:00
await MergeChunks ( chunksPath , vfrFile , outPath , i . current . outFps ) ;
2020-11-30 02:14:04 +01:00
}
catch ( Exception e )
{
Logger . Log ( "ChunksToVideo Error: " + e . Message , false ) ;
MessageBox . Show ( "An error occured while trying to merge the video chunks.\nCheck the log for details." ) ;
}
}
static async Task MergeChunks ( string chunksPath , string vfrFile , string outPath , float fps , float changeFps = - 1 , bool keepOriginalFpsVid = true )
{
2020-12-07 10:48:50 +01:00
int looptimes = GetLoopTimes ( ) ;
2020-11-30 02:14:04 +01:00
bool h265 = Config . GetInt ( "mp4Enc" ) = = 1 ;
int crf = h265 ? Config . GetInt ( "h265Crf" ) : Config . GetInt ( "h264Crf" ) ;
await FFmpegCommands . ConcatVideos ( vfrFile , outPath , fps , - 1 ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , outPath ) ;
2020-11-30 02:14:04 +01:00
if ( looptimes > 0 )
await Loop ( outPath , looptimes ) ;
if ( changeFps > 0 )
{
string newOutPath = IOUtils . FilenameSuffix ( outPath , $"-{changeFps.ToString(" 0 ")}fps" ) ;
Program . mainForm . SetStatus ( "Creating video with desired frame rate..." ) ;
await FFmpegCommands . ConvertFramerate ( outPath , newOutPath , h265 , crf , changeFps , ! keepOriginalFpsVid ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , newOutPath ) ;
2020-11-30 02:14:04 +01:00
if ( looptimes > 0 )
await Loop ( newOutPath , looptimes ) ;
}
}
2020-12-23 16:13:04 +01:00
public static async Task EncodeChunk ( string outPath , i . OutMode mode , int firstFrameNum , int framesAmount )
2020-11-30 02:14:04 +01:00
{
2020-12-17 11:32:45 +01:00
string vfrFileOriginal = Path . Combine ( i . current . tempFolder , $"vfr-{i.current.interpFactor}x.ini" ) ;
string vfrFile = Path . Combine ( i . current . tempFolder , $"vfr-chunk-{firstFrameNum}-{firstFrameNum + framesAmount}.ini" ) ;
2021-01-03 21:00:32 +01:00
File . WriteAllLines ( vfrFile , IOUtils . ReadLines ( vfrFileOriginal ) . Skip ( firstFrameNum ) . Take ( framesAmount ) ) ;
2020-11-30 02:14:04 +01:00
2021-01-02 16:20:21 +01:00
await FFmpegCommands . FramesToVideoConcat ( vfrFile , outPath , mode , i . current . outFps , AvProcess . LogMode . Hidden , true ) ;
2020-11-30 02:14:04 +01:00
}
static async Task Loop ( string outPath , int looptimes )
2020-11-26 21:18:31 +01:00
{
2020-12-07 10:48:50 +01:00
Logger . Log ( $"Looping {looptimes} times to reach target length..." ) ;
2020-11-26 21:18:31 +01:00
await FFmpegCommands . LoopVideo ( outPath , looptimes , Config . GetInt ( "loopMode" ) = = 0 ) ;
}
2020-12-07 10:48:50 +01:00
static int GetLoopTimes ( )
2020-11-23 16:51:05 +01:00
{
2020-11-30 20:32:33 +01:00
//Logger.Log("Getting loop times for path " + framesOutPath);
2020-11-26 21:18:31 +01:00
int times = - 1 ;
2020-11-29 16:10:31 +01:00
int minLength = Config . GetInt ( "minOutVidLength" ) ;
2020-11-30 20:32:33 +01:00
//Logger.Log("minLength: " + minLength);
2020-12-17 11:32:45 +01:00
int minFrameCount = ( minLength * i . current . outFps ) . RoundToInt ( ) ;
2020-11-30 20:32:33 +01:00
//Logger.Log("minFrameCount: " + minFrameCount);
2020-12-10 16:37:33 +01:00
//int outFrames = new DirectoryInfo(framesOutPath).GetFiles($"*.{InterpolateUtils.GetExt()}", SearchOption.TopDirectoryOnly).Length;
2020-12-17 11:32:45 +01:00
int outFrames = i . currentInputFrameCount * i . current . interpFactor ;
2020-11-30 20:32:33 +01:00
//Logger.Log("outFrames: " + outFrames);
2020-12-17 11:32:45 +01:00
if ( outFrames / i . current . outFps < minLength )
2020-11-26 21:18:31 +01:00
times = ( int ) Math . Ceiling ( ( double ) minFrameCount / ( double ) outFrames ) ;
2020-11-30 20:32:33 +01:00
//Logger.Log("times: " + times);
2020-11-26 21:18:31 +01:00
times - - ; // Account for this calculation not counting the 1st play (0 loops)
if ( times < = 0 ) return - 1 ; // Never try to loop 0 times, idk what would happen, probably nothing
return times ;
2020-11-23 16:51:05 +01:00
}
2020-11-30 02:14:04 +01:00
public static async Task MergeAudio ( string inputPath , string outVideo , int looptimes = - 1 )
2020-11-23 16:51:05 +01:00
{
if ( ! Config . GetBool ( "enableAudio" ) ) return ;
try
{
2020-12-17 11:32:45 +01:00
string audioFileBasePath = Path . Combine ( i . current . tempFolder , "audio" ) ;
2020-12-02 23:11:27 +01:00
if ( inputPath ! = null & & IOUtils . IsPathDirectory ( inputPath ) & & ! File . Exists ( IOUtils . GetAudioFile ( audioFileBasePath ) ) ) // Try loading out of same folder as input if input is a folder
2020-12-17 11:32:45 +01:00
audioFileBasePath = Path . Combine ( i . current . tempFolder . GetParentDir ( ) , "audio" ) ;
2020-12-02 23:11:27 +01:00
2020-11-23 16:51:05 +01:00
if ( ! File . Exists ( IOUtils . GetAudioFile ( audioFileBasePath ) ) )
2020-11-30 02:14:04 +01:00
await FFmpegCommands . ExtractAudio ( inputPath , audioFileBasePath ) ; // Extract from sourceVideo to audioFile unless it already exists
2020-12-02 23:11:27 +01:00
2020-11-26 20:17:18 +01:00
if ( ! File . Exists ( IOUtils . GetAudioFile ( audioFileBasePath ) ) | | new FileInfo ( IOUtils . GetAudioFile ( audioFileBasePath ) ) . Length < 4096 )
2020-11-23 16:51:05 +01:00
{
2020-12-04 23:45:04 +01:00
Logger . Log ( "No compatible audio stream found." , true ) ;
2020-11-23 16:51:05 +01:00
return ;
}
await FFmpegCommands . MergeAudio ( outVideo , IOUtils . GetAudioFile ( audioFileBasePath ) ) ; // Merge from audioFile into outVideo
}
2020-11-29 16:10:31 +01:00
catch ( Exception e )
2020-11-23 16:51:05 +01:00
{
Logger . Log ( "Failed to copy audio!" ) ;
2020-11-29 16:10:31 +01:00
Logger . Log ( "MergeAudio() Exception: " + e . Message , true ) ;
2020-11-23 16:51:05 +01:00
}
}
}
}