Files
flowframes/Code/Main/CreateVideo.cs

236 lines
11 KiB
C#
Raw Normal View History

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;
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
}
//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)
{
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)
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)
{
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.lastInterpFactor}x.ini");
2020-11-23 16:51:05 +01:00
if (mode == i.OutMode.VidGif)
{
await FFmpegCommands.FramesToGifVfr(vfrFile, outPath, true);
// TODO: Remove old code once new code works well
// if (new DirectoryInfo(framesPath).GetFiles()[0].Extension != ".png")
// {
// Logger.Log("Converting output frames to PNG to encode with Gifski...");
// await Converter.Convert(framesPath, ImageMagick.MagickFormat.Png00, 20, "png", false);
// }
// await GifskiCommands.CreateGifFromFrames(i.currentOutFps.RoundToInt(), Config.GetInt("gifskiQ"), framesPath, outPath);
2020-11-23 16:51:05 +01:00
}
if (mode == i.OutMode.VidMp4)
{
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");
await FFmpegCommands.FramesToMp4Vfr(vfrFile, outPath, h265, crf, fps, i.constantFrameRate);
/* DELETE THIS AS SOON AS I'M SURE I CAN USE VFR WITH TIMING DISABLED
if (Config.GetInt("timingMode") == 1 && Config.GetInt("dedupMode") != 0)
{
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-x{i.lastInterpFactor}.ini");
await FFmpegCommands.FramesToMp4Vfr(vfrFile, outPath, h265, crf, fps, -1);
}
else
{
await FFmpegCommands.FramesToMp4(framesPath, outPath, h265, crf, fps, "", false, -1, InterpolateUtils.lastExt); // Create video
}
*/
2020-11-23 16:51:05 +01:00
await MergeAudio(i.lastInputPath, outPath);
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);
if (looptimes > 0)
await Loop(newOutPath, looptimes);
2020-11-23 16:51:05 +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)
{
int looptimes = GetLoopTimes();
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-{i.lastInterpFactor}x.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, i.constantFrameRate);
IOUtils.TryDeleteIfExists(vfrFile);
}
static async Task Loop(string outPath, int looptimes)
{
Logger.Log($"Looping {looptimes} times to reach target length...");
await FFmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
}
static int GetLoopTimes()
2020-11-23 16:51:05 +01:00
{
//Logger.Log("Getting loop times for path " + framesOutPath);
int times = -1;
int minLength = Config.GetInt("minOutVidLength");
//Logger.Log("minLength: " + minLength);
2020-11-23 16:51:05 +01:00
int minFrameCount = (minLength * i.currentOutFps).RoundToInt();
//Logger.Log("minFrameCount: " + minFrameCount);
//int outFrames = new DirectoryInfo(framesOutPath).GetFiles($"*.{InterpolateUtils.lastExt}", SearchOption.TopDirectoryOnly).Length;
int outFrames = i.currentInputFrameCount * i.lastInterpFactor;
//Logger.Log("outFrames: " + outFrames);
2020-11-23 16:51:05 +01:00
if (outFrames / i.currentOutFps < minLength)
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
//Logger.Log("times: " + times);
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
}
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");
if (inputPath != null && IOUtils.IsPathDirectory(inputPath) && !File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) // Try loading out of same folder as input if input is a folder
audioFileBasePath = Path.Combine(i.currentTempDir.GetParentDir(), "audio");
2020-11-23 16:51:05 +01:00
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
await FFmpegCommands.ExtractAudio(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
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.", true);
2020-11-23 16:51:05 +01:00
return;
}
await FFmpegCommands.MergeAudio(outVideo, IOUtils.GetAudioFile(audioFileBasePath)); // Merge from audioFile into outVideo
}
catch (Exception e)
2020-11-23 16:51:05 +01:00
{
Logger.Log("Failed to copy audio!");
Logger.Log("MergeAudio() Exception: " + e.Message, true);
2020-11-23 16:51:05 +01:00
}
}
}
}