Files
flowframes/Code/Main/CreateVideo.cs

238 lines
11 KiB
C#
Raw Normal View History

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;
using Padding = Flowframes.Data.Padding;
2021-02-08 21:24:28 +01:00
using I = Flowframes.Interpolate;
using System.Diagnostics;
using Flowframes.Media;
2020-11-23 16:51:05 +01:00
namespace Flowframes.Main
{
class CreateVideo
{
static string currentOutFile; // Keeps track of the out file, in case it gets renamed (FPS limiting, looping, etc) before finishing export
public static async Task Export(string path, string outFolder, 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
{
string folder = Path.Combine(outFolder, IOUtils.GetCurrentExportFilename(false, false));
await CopyOutputFrames(path, folder, stepByStep);
2020-12-02 15:34:59 +01:00
}
catch(Exception e)
{
Logger.Log("Failed to move interp frames folder: " + e.Message);
}
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
}
if (IOUtils.GetAmountOfFiles(path, false, $"*.{InterpolateUtils.GetOutExt()}") <= 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
{
float maxFps = Config.GetFloat("maxFps");
2021-02-08 21:24:28 +01:00
bool fpsLimit = maxFps != 0 && I.current.outFps > maxFps;
2020-11-23 16:51:05 +01:00
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
await Encode(mode, path, Path.Combine(outFolder, IOUtils.GetCurrentExportFilename(false, true)), I.current.outFps);
if (fpsLimit)
await Encode(mode, path, Path.Combine(outFolder, 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.");
}
}
static async Task CopyOutputFrames (string framesPath, string folderName, bool dontMove)
{
Program.mainForm.SetStatus("Copying output frames...");
2021-02-08 21:24:28 +01:00
string copyPath = Path.Combine(I.current.outPath, folderName);
2021-03-14 20:34:35 +01:00
Logger.Log($"Moving output frames from {framesPath} to '{copyPath}'");
IOUtils.TryDeleteIfExists(copyPath);
IOUtils.CreateDir(copyPath);
Stopwatch sw = new Stopwatch();
sw.Restart();
2021-02-08 21:24:28 +01:00
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
string[] vfrLines = IOUtils.ReadLines(vfrFile);
for (int idx = 1; idx <= vfrLines.Length; idx++)
{
string line = vfrLines[idx-1];
2021-03-14 20:34:35 +01:00
string inFilename = line.RemoveComments().Split('/').Last().Remove("'").Trim();
string framePath = Path.Combine(framesPath, inFilename);
string outFilename = Path.Combine(copyPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
if (dontMove || ((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);
if (sw.ElapsedMilliseconds >= 500 || idx == vfrLines.Length)
{
sw.Restart();
Logger.Log($"Moving output frames to '{Path.GetFileName(copyPath)}' - {idx}/{vfrLines.Length}", false, true);
await Task.Delay(1);
}
}
}
2021-02-08 21:24:28 +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;
2021-02-08 21:24:28 +01:00
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
if (!File.Exists(vfrFile))
{
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;
}
2021-02-08 21:24:28 +01:00
if (mode == I.OutMode.VidGif)
{
await FfmpegEncode.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
}
else
2020-11-23 16:51:05 +01:00
{
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
await MuxOutputVideo(I.current.inPath, outPath);
await Loop(currentOutFile, GetLoopTimes());
2020-11-23 16:51:05 +01:00
}
}
public static async Task ChunksToVideos(string tempFolder, string chunksFolder, string baseOutPath)
{
2021-02-08 21:24:28 +01:00
if (IOUtils.GetAmountOfFiles(chunksFolder, true, $"*{FFmpegUtils.GetExt(I.current.outMode)}") < 1)
{
2021-02-08 21:24:28 +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
{
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 frames file '{Path.GetFileName(tempConcatFile)}'", true);
bool fpsLimit = dir.Name.Contains(Paths.fpsLimitSuffix);
string outPath = Path.Combine(baseOutPath, IOUtils.GetCurrentExportFilename(fpsLimit, true));
await MergeChunks(tempConcatFile, outPath);
}
}
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 vfrFile, string outPath)
{
await FfmpegCommands.ConcatVideos(vfrFile, outPath, -1);
await MuxOutputVideo(I.current.inPath, outPath);
await Loop(outPath, GetLoopTimes());
}
2021-02-08 21:24:28 +01:00
public static async Task EncodeChunk(string outPath, I.OutMode mode, int firstFrameNum, int framesAmount)
{
2021-02-08 21:24:28 +01:00
string vfrFileOriginal = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilename(I.current.interpFactor));
string vfrFile = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilenameChunk(firstFrameNum, firstFrameNum + framesAmount));
File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum).Take(framesAmount));
float maxFps = Config.GetFloat("maxFps");
2021-02-08 21:24:28 +01:00
bool fpsLimit = maxFps != 0 && I.current.outFps > maxFps;
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
2021-02-08 21:24:28 +01:00
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, I.current.outFps, AvProcess.LogMode.Hidden, true); // Encode
if (fpsLimit)
{
string filename = Path.GetFileName(outPath);
string newParentDir = outPath.GetParentDir() + Paths.fpsLimitSuffix;
outPath = Path.Combine(newParentDir, filename);
2021-02-08 21:24:28 +01:00
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, I.current.outFps, maxFps, AvProcess.LogMode.Hidden, true); // Encode with limited fps
}
}
static async Task Loop(string outPath, int looptimes)
{
if (looptimes < 1 || !Config.GetBool("enableLoop")) return;
Logger.Log($"Looping {looptimes} {(looptimes == 1 ? "time" : "times")} to reach target length of {Config.GetInt("minOutVidLength")}s...");
await FfmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
}
static int GetLoopTimes()
2020-11-23 16:51:05 +01:00
{
int times = -1;
int minLength = Config.GetInt("minOutVidLength");
2021-02-08 21:24:28 +01:00
int minFrameCount = (minLength * I.current.outFps).RoundToInt();
int outFrames = (I.currentInputFrameCount * I.current.interpFactor).RoundToInt();
if (outFrames / I.current.outFps < minLength)
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
times--; // 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 MuxOutputVideo(string inputPath, string outVideo)
2020-11-23 16:51:05 +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...");
bool muxFromInput = Config.GetInt("audioSubTransferMode") == 0;
2020-11-23 16:51:05 +01:00
try
{
if (muxFromInput)
await FfmpegAudioAndMetadata.MergeStreamsFromInput(inputPath, outVideo, I.current.tempFolder);
else
await FfmpegAudioAndMetadata.MergeAudioAndSubs(outVideo, I.current.tempFolder); // Merge from audioFile into outVideo
2020-11-23 16:51:05 +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!");
Logger.Log("MergeAudio() Exception: " + e.Message, true);
2020-11-23 16:51:05 +01:00
}
}
}
}