2021-03-22 21:56:14 +01:00
using Flowframes.IO ;
2020-11-23 16:51:05 +01:00
using Flowframes.Magick ;
using System ;
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 ;
2021-02-08 21:24:28 +01:00
using I = Flowframes . Interpolate ;
2020-12-20 21:25:34 +01:00
using System.Diagnostics ;
2021-04-02 14:36:08 +02:00
using Flowframes.Data ;
2021-02-02 12:56:48 +01:00
using Flowframes.Media ;
2021-04-05 12:04:23 +02:00
using Microsoft.VisualBasic.Logging ;
2020-11-23 16:51:05 +01:00
namespace Flowframes.Main
{
class CreateVideo
{
2021-03-19 19:34:48 +01:00
2020-12-10 22:39:45 +01:00
2021-03-09 15:55:50 +01:00
public static async Task Export ( string path , string outFolder , I . OutMode mode , bool stepByStep )
2020-11-23 16:51:05 +01:00
{
2021-03-19 19:34:48 +01:00
if ( Config . GetInt ( "sceneChangeFillMode" ) = = 1 )
{
string frameFile = Path . Combine ( I . current . tempFolder , Paths . GetFrameOrderFilename ( I . current . interpFactor ) ) ;
await Blend . BlendSceneChanges ( frameFile ) ;
}
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-04-25 15:11:39 +02:00
await ExportFrames ( path , stepByStep ) ;
2020-12-02 15:34:59 +01:00
}
2021-03-19 19:34:48 +01:00
catch ( Exception e )
2020-12-02 15:34:59 +01:00
{
2021-04-25 15:11:39 +02:00
Logger . Log ( "Failed to move interpolated frames: " + e . Message ) ;
2020-12-02 15:34:59 +01:00
}
2021-02-08 21:24:28 +01:00
2020-11-23 16:51:05 +01:00
return ;
2020-12-02 15:34:59 +01:00
}
2021-04-22 16:15:17 +02:00
if ( IOUtils . GetAmountOfFiles ( path , false , "*" + I . current . interpExt ) < = 1 )
2020-11-23 16:51:05 +01:00
{
2021-02-08 21:24:28 +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 ;
}
2021-02-08 21:24:28 +01:00
2020-11-23 16:51:05 +01:00
await Task . Delay ( 10 ) ;
Program . mainForm . SetStatus ( "Creating output video from frames..." ) ;
2021-02-08 21:24:28 +01:00
2020-11-23 16:51:05 +01:00
try
{
2021-04-26 14:13:22 +02:00
string max = Config . Get ( "maxFps" ) ;
Fraction maxFps = max . Contains ( "/" ) ? new Fraction ( max ) : new Fraction ( max . GetFloat ( ) ) ;
bool fpsLimit = maxFps . GetFloat ( ) > 0f & & I . current . outFps . GetFloat ( ) > maxFps . GetFloat ( ) ;
2021-01-06 17:41:18 +01:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( "maxFpsMode" ) = = 0 ;
2021-03-19 19:34:48 +01:00
if ( ! dontEncodeFullFpsVid )
2021-04-26 14:13:22 +02:00
await Encode ( mode , path , Path . Combine ( outFolder , await IOUtils . GetCurrentExportFilename ( false , true ) ) , I . current . outFps , new Fraction ( ) ) ;
2021-01-06 17:41:18 +01:00
if ( fpsLimit )
2021-04-18 18:11:47 +02:00
await Encode ( mode , path , Path . Combine ( outFolder , await IOUtils . GetCurrentExportFilename ( true , true ) ) , 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-04-25 15:11:39 +02:00
static async Task ExportFrames ( string framesPath , bool stepByStep )
2020-12-20 21:25:34 +01:00
{
Program . mainForm . SetStatus ( "Copying output frames..." ) ;
2021-04-29 09:20:35 +02:00
string desiredFormat = Config . Get ( "imgSeqFormat" ) . ToLower ( ) ;
string availableFormat = Path . GetExtension ( IOUtils . GetFilesSorted ( framesPath ) [ 0 ] . Remove ( "." ) . ToLower ( ) ) ;
2021-04-26 14:13:22 +02:00
string max = Config . Get ( "maxFps" ) ;
Fraction maxFps = max . Contains ( "/" ) ? new Fraction ( max ) : new Fraction ( max . GetFloat ( ) ) ;
bool fpsLimit = maxFps . GetFloat ( ) > 0f & & I . current . outFps . GetFloat ( ) > maxFps . GetFloat ( ) ;
2021-04-25 15:11:39 +02:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( "maxFpsMode" ) = = 0 ;
string framesFile = Path . Combine ( framesPath . GetParentDir ( ) , Paths . GetFrameOrderFilename ( I . current . interpFactor ) ) ;
if ( ! dontEncodeFullFpsVid )
{
string outputFolderPath = Path . Combine ( I . current . outPath , await IOUtils . GetCurrentExportFilename ( false , false ) ) ;
2021-04-29 09:20:35 +02:00
Logger . Log ( $"Exporting {desiredFormat.ToUpper()} frames to '{Path.GetFileName(outputFolderPath)}'..." ) ;
if ( desiredFormat = = availableFormat ) // Move as the frames are already in the desired format
await CopyOutputFrames ( framesPath , framesFile , outputFolderPath , fpsLimit ) ;
else // Encode with ffmpeg
await FfmpegEncode . FramesToFrames ( framesFile , outputFolderPath , I . current . outFps , new Fraction ( ) , desiredFormat ) ;
2021-04-25 15:11:39 +02:00
}
if ( fpsLimit )
{
string outputFolderPath = Path . Combine ( I . current . outPath , await IOUtils . GetCurrentExportFilename ( true , false ) ) ;
2021-04-29 09:20:35 +02:00
Logger . Log ( $"Exporting {desiredFormat.ToUpper()} frames to '{Path.GetFileName(outputFolderPath)}' (Resampled to {maxFps} FPS)..." ) ;
await FfmpegEncode . FramesToFrames ( framesFile , outputFolderPath , I . current . outFps , maxFps , desiredFormat ) ;
2021-04-25 15:11:39 +02:00
}
if ( ! stepByStep )
IOUtils . DeleteContentsOfDir ( I . current . interpFolder ) ;
}
static async Task CopyOutputFrames ( string framesPath , string framesFile , string outputFolderPath , bool dontMove )
{
IOUtils . TryDeleteIfExists ( outputFolderPath ) ;
IOUtils . CreateDir ( outputFolderPath ) ;
2020-12-20 21:25:34 +01:00
Stopwatch sw = new Stopwatch ( ) ;
sw . Restart ( ) ;
2021-04-03 16:30:07 +02:00
string [ ] framesLines = IOUtils . ReadLines ( framesFile ) ;
2020-12-20 21:25:34 +01:00
2021-04-03 16:30:07 +02:00
for ( int idx = 1 ; idx < = framesLines . Length ; idx + + )
2020-12-20 21:25:34 +01:00
{
2021-04-03 16:30:07 +02:00
string line = framesLines [ idx - 1 ] ;
2021-03-14 20:34:35 +01:00
string inFilename = line . RemoveComments ( ) . Split ( '/' ) . Last ( ) . Remove ( "'" ) . Trim ( ) ;
2020-12-20 21:25:34 +01:00
string framePath = Path . Combine ( framesPath , inFilename ) ;
2021-04-25 15:11:39 +02:00
string outFilename = Path . Combine ( outputFolderPath , idx . ToString ( ) . PadLeft ( Padding . interpFrames , '0' ) ) + Path . GetExtension ( framePath ) ;
2020-12-20 21:25:34 +01:00
2021-04-03 16:30:07 +02:00
if ( dontMove | | ( ( idx < framesLines . Length ) & & framesLines [ 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-04-03 16:30:07 +02:00
if ( sw . ElapsedMilliseconds > = 500 | | idx = = framesLines . Length )
2020-12-20 21:25:34 +01:00
{
sw . Restart ( ) ;
2021-04-29 09:20:35 +02:00
Logger . Log ( $"Moving output frames... {idx}/{framesLines.Length}" , false , true ) ;
2020-12-20 21:25:34 +01:00
await Task . Delay ( 1 ) ;
}
}
}
2021-04-26 14:13:22 +02:00
static async Task Encode ( I . OutMode mode , string framesPath , string outPath , Fraction fps , Fraction resampleFps )
2020-11-23 16:51:05 +01:00
{
2021-03-19 19:34:48 +01:00
string currentOutFile = outPath ;
2021-04-02 14:36:08 +02:00
string framesFile = Path . Combine ( framesPath . GetParentDir ( ) , Paths . GetFrameOrderFilename ( I . current . interpFactor ) ) ;
2021-02-08 21:24:28 +01:00
2021-04-02 14:36:08 +02:00
if ( ! File . Exists ( framesFile ) )
2021-02-08 21:24:28 +01:00
{
bool sbs = Config . GetInt ( "processingMode" ) = = 1 ;
I . Cancel ( $"Frame order file for this interpolation factor not found!{(sbs ? " \ n \ nDid you run the interpolation step with the current factor ? " : " ")}" ) ;
return ;
}
2020-12-08 14:43:03 +01:00
2021-02-08 21:24:28 +01:00
if ( mode = = I . OutMode . VidGif )
2020-12-23 16:13:04 +01:00
{
2021-04-02 14:36:08 +02:00
await FfmpegEncode . FramesToGifConcat ( framesFile , 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-05-07 23:39:52 +02:00
VidExtraData extraData = await FfmpegCommands . GetVidExtraInfo ( I . current . inPath ) ;
await FfmpegEncode . FramesToVideo ( framesFile , outPath , mode , fps , resampleFps , extraData ) ;
2021-03-03 16:00:48 +01:00
await MuxOutputVideo ( I . current . inPath , outPath ) ;
2021-05-07 23:06:43 +02:00
await Loop ( currentOutFile , await 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-04-22 16:15:17 +02:00
if ( IOUtils . GetAmountOfFiles ( chunksFolder , true , "*" + FFmpegUtils . GetExt ( I . current . outMode ) ) < 1 )
2020-11-30 02:14:04 +01:00
{
2021-02-08 21:24:28 +01:00
I . Cancel ( "No video chunks found - An error must have occured during chunk encoding!" , AiProcess . hasShownError ) ;
2020-11-30 02:14:04 +01:00
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 ) ;
2021-03-19 19:34:48 +01:00
foreach ( DirectoryInfo dir in chunksDir . GetDirectories ( ) )
2021-01-06 17:41:18 +01:00
{
string suffix = dir . Name . Replace ( "chunks" , "" ) ;
string tempConcatFile = Path . Combine ( tempFolder , $"chunks-concat{suffix}.ini" ) ;
string concatFileContent = "" ;
2021-03-09 15:55:50 +01:00
2021-01-06 17:41:18 +01:00
foreach ( string vid in IOUtils . GetFilesSorted ( dir . FullName ) )
concatFileContent + = $"file '{Paths.chunksDir}/{dir.Name}/{Path.GetFileName(vid)}'\n" ;
2021-03-09 15:55:50 +01:00
File . WriteAllText ( tempConcatFile , concatFileContent ) ;
Logger . Log ( $"CreateVideo: Running MergeChunks() for frames file '{Path.GetFileName(tempConcatFile)}'" , true ) ;
bool fpsLimit = dir . Name . Contains ( Paths . fpsLimitSuffix ) ;
2021-04-18 18:11:47 +02:00
string outPath = Path . Combine ( baseOutPath , await IOUtils . GetCurrentExportFilename ( fpsLimit , true ) ) ;
2021-03-09 15:55:50 +01:00
await MergeChunks ( tempConcatFile , outPath ) ;
2021-01-06 17:41:18 +01:00
}
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 ) ;
2021-03-03 16:00:48 +01:00
await MuxOutputVideo ( I . current . inPath , outPath ) ;
2021-05-07 23:06:43 +02:00
await Loop ( outPath , await GetLoopTimes ( ) ) ;
2020-11-30 02:14:04 +01:00
}
2021-02-08 21:24:28 +01:00
public static async Task EncodeChunk ( string outPath , I . OutMode mode , int firstFrameNum , int framesAmount )
2020-11-30 02:14:04 +01:00
{
2021-03-21 16:00:53 +01:00
string framesFileFull = Path . Combine ( I . current . tempFolder , Paths . GetFrameOrderFilename ( I . current . interpFactor ) ) ;
string framesFileChunk = Path . Combine ( I . current . tempFolder , Paths . GetFrameOrderFilenameChunk ( firstFrameNum , firstFrameNum + framesAmount ) ) ;
File . WriteAllLines ( framesFileChunk , IOUtils . ReadLines ( framesFileFull ) . Skip ( firstFrameNum ) . Take ( framesAmount ) ) ;
if ( Config . GetInt ( "sceneChangeFillMode" ) = = 1 )
2021-03-21 21:56:08 +01:00
await Blend . BlendSceneChanges ( framesFileChunk , false ) ;
2020-11-30 02:14:04 +01:00
2021-04-26 14:13:22 +02:00
string max = Config . Get ( "maxFps" ) ;
Fraction maxFps = max . Contains ( "/" ) ? new Fraction ( max ) : new Fraction ( max . GetFloat ( ) ) ;
bool fpsLimit = maxFps . GetFloat ( ) ! = 0 & & I . current . outFps . GetFloat ( ) > maxFps . GetFloat ( ) ;
2021-05-07 23:39:52 +02:00
VidExtraData extraData = await FfmpegCommands . GetVidExtraInfo ( I . current . inPath ) ;
2021-01-06 17:41:18 +01:00
bool dontEncodeFullFpsVid = fpsLimit & & Config . GetInt ( "maxFpsMode" ) = = 0 ;
2021-03-19 19:34:48 +01:00
if ( ! dontEncodeFullFpsVid )
2021-05-07 23:39:52 +02:00
await FfmpegEncode . FramesToVideo ( framesFileChunk , outPath , mode , I . current . outFps , new Fraction ( ) , extraData , AvProcess . LogMode . Hidden , true ) ; // Encode
2021-01-06 17:41:18 +01:00
if ( fpsLimit )
{
string filename = Path . GetFileName ( outPath ) ;
2021-03-09 15:55:50 +01:00
string newParentDir = outPath . GetParentDir ( ) + Paths . fpsLimitSuffix ;
2021-01-06 17:41:18 +01:00
outPath = Path . Combine ( newParentDir , filename ) ;
2021-05-07 23:39:52 +02:00
await FfmpegEncode . FramesToVideo ( framesFileChunk , outPath , mode , I . current . outFps , maxFps , extraData , 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
}
2021-05-07 23:06:43 +02:00
static async Task < 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" ) ;
2021-04-02 14:36:08 +02:00
int minFrameCount = ( minLength * I . current . outFps . GetFloat ( ) ) . RoundToInt ( ) ;
2021-05-07 23:06:43 +02:00
int outFrames = ( ( await I . GetCurrentInputFrameCount ( ) ) * I . current . interpFactor ) . RoundToInt ( ) ;
2021-04-02 14:36:08 +02:00
if ( outFrames / I . current . outFps . GetFloat ( ) < 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
}
2021-03-03 16:00:48 +01:00
public static async Task MuxOutputVideo ( string inputPath , string outVideo )
2020-11-23 16:51:05 +01:00
{
2021-04-05 11:58:07 +02:00
if ( ! File . Exists ( outVideo ) )
{
I . Cancel ( $"No video was encoded!\n\nFFmpeg Output:\n{AvProcess.lastOutputFfmpeg}" ) ;
return ;
}
2021-03-03 16:12:49 +01:00
if ( ! Config . GetBool ( "keepAudio" ) & & ! Config . GetBool ( "keepAudio" ) )
return ;
2021-03-09 18:52:07 +01:00
Program . mainForm . SetStatus ( "Muxing audio/subtitles into video..." ) ;
2021-03-03 16:12:49 +01:00
bool muxFromInput = Config . GetInt ( "audioSubTransferMode" ) = = 0 ;
2021-03-03 16:00:48 +01:00
2021-04-05 12:04:23 +02:00
if ( muxFromInput & & I . current . inputIsFrames )
{
Logger . Log ( "Skipping muxing from input step as there is no input video, only frames." , true ) ;
return ;
}
2020-11-23 16:51:05 +01:00
try
{
2021-03-03 16:00:48 +01:00
if ( muxFromInput )
await FfmpegAudioAndMetadata . MergeStreamsFromInput ( inputPath , outVideo , I . current . tempFolder ) ;
else
2021-03-22 21:56:14 +01:00
await FfmpegAudioAndMetadata . MergeAudioAndSubs ( outVideo , I . current . tempFolder ) ;
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
{
2021-02-24 17:57:30 +01:00
Logger . Log ( "Failed to merge audio/subtitles with output video!" ) ;
2020-11-29 16:10:31 +01:00
Logger . Log ( "MergeAudio() Exception: " + e . Message , true ) ;
2020-11-23 16:51:05 +01:00
}
}
}
}