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 ;
namespace Flowframes.Main
{
class CreateVideo
{
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
{
Logger . Log ( "Moving interpolated frames out of temp folder..." ) ;
string copyPath = Path . Combine ( i . currentTempDir . ReplaceLast ( "-temp" , "-interpolated" ) ) ;
Logger . Log ( $"{path} -> {copyPath}" ) ;
IOUtils . CreateDir ( copyPath ) ;
IOUtils . Copy ( path , copyPath , true ) ;
}
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-04 16:53:39 +01:00
//Logger.Log("zero-padding " + path);
//IOUtils.ZeroPadDir(path, $"*.{InterpolateUtils.lastExt}", Padding.interpFrames);
2020-11-23 16:51:05 +01:00
if ( IOUtils . GetAmountOfFiles ( path , false , $"*.{InterpolateUtils.lastExt}" ) < = 1 )
{
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 ;
if ( mode = = i . OutMode . VidMp4 & & i . currentOutFps > maxFps )
2020-11-30 02:14:04 +01:00
await Encode ( mode , path , outPath , i . currentOutFps , maxFps , ( Config . GetInt ( "maxFpsMode" ) = = 1 ) ) ;
2020-11-23 16:51:05 +01:00
else
await Encode ( mode , path , outPath , i . currentOutFps ) ;
}
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." ) ;
}
}
static async Task Encode ( i . OutMode mode , string framesPath , string outPath , float fps , float changeFps = - 1 , bool keepOriginalFpsVid = true )
{
if ( mode = = i . OutMode . VidGif )
{
if ( new DirectoryInfo ( framesPath ) . GetFiles ( ) [ 0 ] . Extension ! = ".png" )
{
Logger . Log ( "Converting output frames to PNG to encode with Gifski..." ) ;
2020-11-24 02:23:19 +01:00
await Converter . Convert ( framesPath , ImageMagick . MagickFormat . Png00 , 20 , "png" , false ) ;
2020-11-23 16:51:05 +01:00
}
await GifskiCommands . CreateGifFromFrames ( i . currentOutFps . RoundToInt ( ) , Config . GetInt ( "gifskiQ" ) , framesPath , outPath ) ;
}
if ( mode = = i . OutMode . VidMp4 )
{
int looptimes = GetLoopTimes ( framesPath ) ;
bool h265 = Config . GetInt ( "mp4Enc" ) = = 1 ;
int crf = h265 ? Config . GetInt ( "h265Crf" ) : Config . GetInt ( "h264Crf" ) ;
2020-12-02 23:11:27 +01:00
string vfrFile = Path . Combine ( framesPath . GetParentDir ( ) , $"vfr-x{i.lastInterpFactor}.ini" ) ;
await FFmpegCommands . FramesToMp4Vfr ( vfrFile , outPath , h265 , crf , fps , - 1 ) ;
/ * DELETE THIS AS SOON AS I ' M SURE I CAN USE VFR WITH TIMING DISABLED
2020-11-25 12:40:17 +01:00
if ( Config . GetInt ( "timingMode" ) = = 1 & & Config . GetInt ( "dedupMode" ) ! = 0 )
{
2020-11-29 16:10:31 +01:00
string vfrFile = Path . Combine ( framesPath . GetParentDir ( ) , $"vfr-x{i.lastInterpFactor}.ini" ) ;
await FFmpegCommands . FramesToMp4Vfr ( vfrFile , outPath , h265 , crf , fps , - 1 ) ;
2020-11-25 12:40:17 +01:00
}
else
{
2020-11-30 02:14:04 +01:00
await FFmpegCommands . FramesToMp4 ( framesPath , outPath , h265 , crf , fps , "" , false , - 1 , InterpolateUtils . lastExt ) ; // Create video
2020-11-25 12:40:17 +01:00
}
2020-12-02 23:11:27 +01:00
* /
2020-11-23 16:51:05 +01:00
2020-11-29 16:10:31 +01:00
await MergeAudio ( i . lastInputPath , outPath ) ;
2020-11-26 23:47:09 +01:00
if ( looptimes > 0 )
await Loop ( outPath , looptimes ) ;
2020-11-23 16:51:05 +01:00
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 ) ;
await MergeAudio ( i . lastInputPath , newOutPath ) ;
2020-11-26 21:18:31 +01:00
if ( looptimes > 0 )
await Loop ( newOutPath , 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 )
{
if ( IOUtils . GetAmountOfFiles ( chunksPath , false , "*.mp4" ) < 1 )
{
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 ;
if ( i . currentOutFps > maxFps )
await MergeChunks ( chunksPath , vfrFile , outPath , i . currentOutFps , maxFps , ( Config . GetInt ( "maxFpsMode" ) = = 1 ) ) ;
else
await MergeChunks ( chunksPath , vfrFile , outPath , i . currentOutFps ) ;
}
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-11-30 20:32:33 +01:00
int looptimes = GetLoopTimes ( Path . Combine ( chunksPath . GetParentDir ( ) , Paths . interpDir ) ) ;
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 ) ;
await MergeAudio ( i . lastInputPath , outPath ) ;
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 ) ;
await MergeAudio ( i . lastInputPath , newOutPath ) ;
if ( looptimes > 0 )
await Loop ( newOutPath , looptimes ) ;
}
}
public static async Task EncodeChunk ( string outPath , int firstFrameNum , int framesAmount )
{
bool h265 = Config . GetInt ( "mp4Enc" ) = = 1 ;
int crf = h265 ? Config . GetInt ( "h265Crf" ) : Config . GetInt ( "h264Crf" ) ;
string vfrFileOriginal = Path . Combine ( i . currentTempDir , $"vfr-x{i.lastInterpFactor}.ini" ) ;
string vfrFile = Path . Combine ( i . currentTempDir , $"vfr-chunk-temp.ini" ) ;
File . WriteAllLines ( vfrFile , IOUtils . ReadLines ( vfrFileOriginal ) . Skip ( firstFrameNum * 2 ) . Take ( framesAmount * 2 ) ) ;
await FFmpegCommands . FramesToMp4VfrChunk ( vfrFile , outPath , h265 , crf , i . currentOutFps ) ;
IOUtils . TryDeleteIfExists ( vfrFile ) ;
}
static async Task Loop ( string outPath , int looptimes )
2020-11-26 21:18:31 +01:00
{
Logger . Log ( $"Looping {looptimes} times to reach target length" ) ;
await FFmpegCommands . LoopVideo ( outPath , looptimes , Config . GetInt ( "loopMode" ) = = 0 ) ;
}
2020-11-23 16:51:05 +01:00
static int GetLoopTimes ( string framesOutPath )
{
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-11-23 16:51:05 +01:00
int minFrameCount = ( minLength * i . currentOutFps ) . RoundToInt ( ) ;
2020-11-30 20:32:33 +01:00
//Logger.Log("minFrameCount: " + minFrameCount);
2020-11-23 16:51:05 +01:00
int outFrames = new DirectoryInfo ( framesOutPath ) . GetFiles ( $"*.{InterpolateUtils.lastExt}" , SearchOption . TopDirectoryOnly ) . Length ;
2020-11-30 20:32:33 +01:00
//Logger.Log("outFrames: " + outFrames);
2020-11-23 16:51:05 +01:00
if ( outFrames / i . currentOutFps < 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
{
string audioFileBasePath = Path . Combine ( i . currentTempDir , "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-11-30 02:14:04 +01:00
audioFileBasePath = Path . Combine ( i . currentTempDir . 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
{
Logger . Log ( "No compatible audio stream found." ) ;
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
}
}
}
}