2020-11-23 16:51:05 +01:00
using Flowframes ;
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 ;
2021-02-02 12:56:48 +01:00
using Flowframes.Media ;
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
2021-01-27 21:12:28 +01:00
public static async Task Export ( string path , string outPath , i . OutMode mode , bool stepByStep )
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
{
2021-01-27 21:12:28 +01:00
await CopyOutputFrames ( path , Path . GetFileNameWithoutExtension ( outPath ) , stepByStep ) ;
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
{
2021-01-06 17:41:18 +01:00
float maxFps = Config . GetFloat ( "maxFps" ) ;
bool fpsLimit = maxFps ! = 0 & & i . current . outFps > maxFps ;
2020-11-23 16:51:05 +01:00
2021-01-06 17:41:18 +01:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( "maxFpsMode" ) = = 0 ;
if ( ! dontEncodeFullFpsVid )
2020-12-17 11:32:45 +01:00
await Encode ( mode , path , outPath , i . current . outFps ) ;
2021-01-06 17:41:18 +01:00
if ( fpsLimit )
await Encode ( mode , path , outPath . FilenameSuffix ( $"-{maxFps.ToStringDot(" 0.00 ")}fps" ) , i . current . outFps , maxFps ) ;
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." ) ;
}
}
2021-01-27 21:12:28 +01:00
static async Task CopyOutputFrames ( string framesPath , string folderName , bool dontMove )
2020-12-20 21:25:34 +01:00
{
Program . mainForm . SetStatus ( "Copying output frames..." ) ;
string copyPath = Path . Combine ( i . current . outPath , folderName ) ;
2021-01-22 13:31:49 +01:00
Logger . Log ( $"Moving output frames to '{copyPath}'" ) ;
2020-12-20 21:25:34 +01:00
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 ] ;
2021-01-14 20:31:45 +01:00
string inFilename = line . Split ( '/' ) . Last ( ) . Remove ( "'" ) . RemoveComments ( ) ;
2020-12-20 21:25:34 +01:00
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
2021-01-27 21:12:28 +01:00
if ( dontMove | | ( ( idx < vfrLines . Length ) & & vfrLines [ idx ] . Contains ( inFilename ) ) ) // If file is re-used in the next line, copy instead of move
2020-12-20 21:25:34 +01:00
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 ( ) ;
2021-01-22 13:31:49 +01:00
Logger . Log ( $"Moving output frames to '{Path.GetFileName(copyPath)}' - {idx}/{vfrLines.Length}" , false , true ) ;
2020-12-20 21:25:34 +01:00
await Task . Delay ( 1 ) ;
}
}
}
2021-01-06 17:41:18 +01:00
static async Task Encode ( i . OutMode mode , string framesPath , string outPath , float fps , float resampleFps = - 1 )
2020-11-23 16:51:05 +01:00
{
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-02-02 12:56:48 +01:00
await FfmpegEncode . FramesToGifConcat ( vfrFile , outPath , fps , true , Config . GetInt ( "gifColors" ) , resampleFps ) ;
2020-12-23 16:13:04 +01:00
}
else
2020-11-23 16:51:05 +01:00
{
2021-02-01 16:23:35 +01:00
await FfmpegEncode . FramesToVideoConcat ( vfrFile , outPath , mode , fps , resampleFps ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , outPath ) ;
2021-01-26 16:45:02 +01:00
await Loop ( currentOutFile , GetLoopTimes ( ) ) ;
2020-11-23 16:51:05 +01:00
}
}
2021-01-06 17:41:18 +01:00
public static async Task ChunksToVideos ( string tempFolder , string chunksFolder , string baseOutPath )
2020-11-30 02:14:04 +01:00
{
2021-01-06 17:41:18 +01:00
if ( IOUtils . GetAmountOfFiles ( chunksFolder , true , $"*{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 ;
}
2021-01-06 17:41:18 +01:00
2020-11-30 02:14:04 +01:00
await Task . Delay ( 10 ) ;
Program . mainForm . SetStatus ( "Merging video chunks..." ) ;
try
{
2021-01-06 17:41:18 +01:00
DirectoryInfo chunksDir = new DirectoryInfo ( chunksFolder ) ;
foreach ( DirectoryInfo dir in chunksDir . GetDirectories ( ) )
{
string suffix = dir . Name . Replace ( "chunks" , "" ) ;
string tempConcatFile = Path . Combine ( tempFolder , $"chunks-concat{suffix}.ini" ) ;
string concatFileContent = "" ;
foreach ( string vid in IOUtils . GetFilesSorted ( dir . FullName ) )
concatFileContent + = $"file '{Paths.chunksDir}/{dir.Name}/{Path.GetFileName(vid)}'\n" ;
File . WriteAllText ( tempConcatFile , concatFileContent ) ;
Logger . Log ( $"CreateVideo: Running MergeChunks() for vfrFile '{Path.GetFileName(tempConcatFile)}'" , true ) ;
await MergeChunks ( tempConcatFile , baseOutPath . FilenameSuffix ( suffix ) ) ;
}
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." ) ;
}
}
2021-01-06 17:41:18 +01:00
static async Task MergeChunks ( string vfrFile , string outPath )
2020-11-30 02:14:04 +01:00
{
2021-02-02 12:56:48 +01:00
await FfmpegCommands . ConcatVideos ( vfrFile , outPath , - 1 ) ;
2020-12-17 11:32:45 +01:00
await MergeAudio ( i . current . inPath , outPath ) ;
2021-01-26 16:45:02 +01:00
await Loop ( outPath , GetLoopTimes ( ) ) ;
2020-11-30 02:14:04 +01:00
}
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-06 17:41:18 +01:00
float maxFps = Config . GetFloat ( "maxFps" ) ;
bool fpsLimit = maxFps ! = 0 & & i . current . outFps > maxFps ;
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( "maxFpsMode" ) = = 0 ;
if ( ! dontEncodeFullFpsVid )
2021-02-01 16:23:35 +01:00
await FfmpegEncode . FramesToVideoConcat ( vfrFile , outPath , mode , i . current . outFps , AvProcess . LogMode . Hidden , true ) ; // Encode
2021-01-06 17:41:18 +01:00
if ( fpsLimit )
{
string filename = Path . GetFileName ( outPath ) ;
string newParentDir = outPath . GetParentDir ( ) + "-" + maxFps . ToStringDot ( "0.00" ) + "fps" ;
outPath = Path . Combine ( newParentDir , filename ) ;
2021-02-01 16:23:35 +01:00
await FfmpegEncode . FramesToVideoConcat ( vfrFile , outPath , mode , i . current . outFps , maxFps , AvProcess . LogMode . Hidden , true ) ; // Encode with limited fps
2021-01-06 17:41:18 +01:00
}
2020-11-30 02:14:04 +01:00
}
static async Task Loop ( string outPath , int looptimes )
2020-11-26 21:18:31 +01:00
{
2021-01-26 16:45:02 +01:00
if ( looptimes < 1 | | ! Config . GetBool ( "enableLoop" ) ) return ;
2021-01-07 11:02:43 +01:00
Logger . Log ( $"Looping {looptimes} {(looptimes == 1 ? " time " : " times ")} to reach target length of {Config.GetInt(" minOutVidLength ")}s..." ) ;
2021-02-02 12:56:48 +01:00
await FfmpegCommands . LoopVideo ( outPath , looptimes , Config . GetInt ( "loopMode" ) = = 0 ) ;
2020-11-26 21:18:31 +01:00
}
2020-12-07 10:48:50 +01:00
static int GetLoopTimes ( )
2020-11-23 16:51:05 +01:00
{
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-12-17 11:32:45 +01:00
int minFrameCount = ( minLength * i . current . outFps ) . RoundToInt ( ) ;
2021-02-01 18:05:50 +01:00
int outFrames = ( i . currentInputFrameCount * i . current . interpFactor ) . RoundToInt ( ) ;
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 ) ;
2021-01-07 11:02:43 +01:00
times - - ; // Not counting the 1st play (0 loops)
2020-11-26 21:18:31 +01:00
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
{
2021-01-16 14:42:47 +01:00
if ( ! Config . GetBool ( "keepAudio" ) ) return ;
2020-11-23 16:51:05 +01:00
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 ) ) )
2021-02-02 12:56:48 +01:00
await FfmpegAudioAndMetadata . 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 ;
}
2021-01-07 11:02:43 +01:00
2021-02-02 12:56:48 +01:00
await FfmpegAudioAndMetadata . MergeAudioAndSubs ( outVideo , IOUtils . GetAudioFile ( audioFileBasePath ) , i . current . tempFolder ) ; // Merge from audioFile into outVideo
2020-11-23 16:51:05 +01:00
}
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
}
}
}
}