From 2bdda6522f9367b8aa7268f9c6c26f1def0dfa9d Mon Sep 17 00:00:00 2001 From: N00MKRAD Date: Tue, 2 Feb 2021 12:56:48 +0100 Subject: [PATCH] FfmpegCommands refactoring, renamed AudioVideo namespace --- Code/Data/InterpSettings.cs | 2 +- Code/Flowframes.csproj | 1 + Code/IO/IOUtils.cs | 4 +- Code/Main/AutoEncode.cs | 2 +- Code/Main/CreateVideo.cs | 12 +- Code/Main/Interpolate.cs | 10 +- Code/Main/InterpolateSteps.cs | 2 +- Code/Main/InterpolateUtils.cs | 6 +- Code/{AudioVideo => Media}/AvProcess.cs | 0 Code/{AudioVideo => Media}/FFmpegUtils.cs | 4 +- Code/Media/FfmpegAudioAndMetadata.cs | 148 +++++++++++++++++ .../FfmpegCommands.cs} | 149 +----------------- Code/{AudioVideo => Media}/FfmpegEncode.cs | 19 ++- Code/{AudioVideo => Media}/FfmpegExtract.cs | 4 +- Code/MiscUtils/FormatUtils.cs | 22 ++- Code/OS/AiProcess.cs | 2 +- Code/UI/MainUiFunctions.cs | 2 +- Code/UI/UtilsTab.cs | 8 +- 18 files changed, 212 insertions(+), 185 deletions(-) rename Code/{AudioVideo => Media}/AvProcess.cs (100%) rename Code/{AudioVideo => Media}/FFmpegUtils.cs (98%) create mode 100644 Code/Media/FfmpegAudioAndMetadata.cs rename Code/{AudioVideo/FFmpegCommands.cs => Media/FfmpegCommands.cs} (57%) rename Code/{AudioVideo => Media}/FfmpegEncode.cs (67%) rename Code/{AudioVideo => Media}/FfmpegExtract.cs (98%) diff --git a/Code/Data/InterpSettings.cs b/Code/Data/InterpSettings.cs index fd87f78..7c59c2b 100644 --- a/Code/Data/InterpSettings.cs +++ b/Code/Data/InterpSettings.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.IO; using Flowframes.Main; diff --git a/Code/Flowframes.csproj b/Code/Flowframes.csproj index 8400d97..10723da 100644 --- a/Code/Flowframes.csproj +++ b/Code/Flowframes.csproj @@ -200,6 +200,7 @@ + diff --git a/Code/IO/IOUtils.cs b/Code/IO/IOUtils.cs index a3eac51..d646cb5 100644 --- a/Code/IO/IOUtils.cs +++ b/Code/IO/IOUtils.cs @@ -313,7 +313,7 @@ namespace Flowframes.IO Logger.Log("Failed to read FPS - Trying alternative method...", true); try { - fps = FFmpegCommands.GetFramerate(path); + fps = FfmpegCommands.GetFramerate(path); Logger.Log("Detected FPS of " + Path.GetFileName(path) + " as " + fps + " FPS", true); } catch @@ -377,7 +377,7 @@ namespace Flowframes.IO Logger.Log($"Failed to read video size ({e.Message}) - Trying alternative method...", true); try { - size = FFmpegCommands.GetSize(path); + size = FfmpegCommands.GetSize(path); Logger.Log($"Detected video size of {Path.GetFileName(path)} as {size.Width}x{size.Height}", true); } catch diff --git a/Code/Main/AutoEncode.cs b/Code/Main/AutoEncode.cs index 132a9a4..a79b57e 100644 --- a/Code/Main/AutoEncode.cs +++ b/Code/Main/AutoEncode.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.IO; using Flowframes.MiscUtils; diff --git a/Code/Main/CreateVideo.cs b/Code/Main/CreateVideo.cs index 81806ac..e0f0457 100644 --- a/Code/Main/CreateVideo.cs +++ b/Code/Main/CreateVideo.cs @@ -13,7 +13,7 @@ using System.Windows.Forms; using Padding = Flowframes.Data.Padding; using i = Flowframes.Interpolate; using System.Diagnostics; -using Flowframes.AudioVideo; +using Flowframes.Media; namespace Flowframes.Main { @@ -104,7 +104,7 @@ namespace Flowframes.Main if (mode == i.OutMode.VidGif) { - await FFmpegCommands.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps); + await FfmpegEncode.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps); } else { @@ -149,7 +149,7 @@ namespace Flowframes.Main static async Task MergeChunks(string vfrFile, string outPath) { - await FFmpegCommands.ConcatVideos(vfrFile, outPath, -1); + await FfmpegCommands.ConcatVideos(vfrFile, outPath, -1); await MergeAudio(i.current.inPath, outPath); await Loop(outPath, GetLoopTimes()); } @@ -181,7 +181,7 @@ namespace Flowframes.Main { 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); + await FfmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0); } static int GetLoopTimes() @@ -208,7 +208,7 @@ namespace Flowframes.Main audioFileBasePath = Path.Combine(i.current.tempFolder.GetParentDir(), "audio"); if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) - await FFmpegCommands.ExtractAudio(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists + await FfmpegAudioAndMetadata.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) { @@ -216,7 +216,7 @@ namespace Flowframes.Main return; } - await FFmpegCommands.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo + await FfmpegAudioAndMetadata.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo } catch (Exception e) { diff --git a/Code/Main/Interpolate.cs b/Code/Main/Interpolate.cs index 3406613..738409e 100644 --- a/Code/Main/Interpolate.cs +++ b/Code/Main/Interpolate.cs @@ -1,5 +1,5 @@ using Flowframes; -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.IO; using Flowframes.Magick; @@ -75,9 +75,9 @@ namespace Flowframes { Program.mainForm.SetStatus("Extracting transparency..."); Logger.Log("Extracting transparency... (1/2)"); - await FFmpegCommands.ExtractAlphaDir(current.framesFolder, current.framesFolder + Paths.alphaSuffix); + await FfmpegCommands.ExtractAlphaDir(current.framesFolder, current.framesFolder + Paths.alphaSuffix); Logger.Log("Extracting transparency... (2/2)", false, true); - await FFmpegCommands.RemoveAlpha(current.framesFolder, current.framesFolder); + await FfmpegCommands.RemoveAlpha(current.framesFolder, current.framesFolder); } } @@ -110,10 +110,10 @@ namespace Flowframes { string audioFile = Path.Combine(current.tempFolder, "audio"); if (audioFile != null && !File.Exists(audioFile)) - await FFmpegCommands.ExtractAudio(inPath, audioFile); + await FfmpegAudioAndMetadata.ExtractAudio(inPath, audioFile); } - await FFmpegCommands.ExtractSubtitles(inPath, current.tempFolder, current.outMode); + await FfmpegAudioAndMetadata.ExtractSubtitles(inPath, current.tempFolder, current.outMode); } public static async Task PostProcessFrames (bool sbsMode = false) diff --git a/Code/Main/InterpolateSteps.cs b/Code/Main/InterpolateSteps.cs index 683336d..a2a9fab 100644 --- a/Code/Main/InterpolateSteps.cs +++ b/Code/Main/InterpolateSteps.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.IO; using System; diff --git a/Code/Main/InterpolateUtils.cs b/Code/Main/InterpolateUtils.cs index 74818cb..26033fd 100644 --- a/Code/Main/InterpolateUtils.cs +++ b/Code/Main/InterpolateUtils.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.Forms; using Flowframes.IO; @@ -178,7 +178,7 @@ namespace Flowframes.Main if (IOUtils.IsPathDirectory(path)) frameCount = IOUtils.GetAmountOfFiles(path, false); else - frameCount = await FFmpegCommands.GetFrameCountAsync(path); + frameCount = await FfmpegCommands.GetFrameCountAsync(path); if (hash.Length > 1 && frameCount > 5000) // Cache if >5k frames to avoid re-reading it every single time { @@ -456,7 +456,7 @@ namespace Flowframes.Main public static void UpdateVideoDuration(string path) { - Program.mainForm.currInDuration = FFmpegCommands.GetDuration(path); + Program.mainForm.currInDuration = FfmpegCommands.GetDuration(path); } } } diff --git a/Code/AudioVideo/AvProcess.cs b/Code/Media/AvProcess.cs similarity index 100% rename from Code/AudioVideo/AvProcess.cs rename to Code/Media/AvProcess.cs diff --git a/Code/AudioVideo/FFmpegUtils.cs b/Code/Media/FFmpegUtils.cs similarity index 98% rename from Code/AudioVideo/FFmpegUtils.cs rename to Code/Media/FFmpegUtils.cs index 2fb710d..1529b43 100644 --- a/Code/AudioVideo/FFmpegUtils.cs +++ b/Code/Media/FFmpegUtils.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Flowframes.AudioVideo +namespace Flowframes.Media { class FFmpegUtils { @@ -123,7 +123,7 @@ namespace Flowframes.AudioVideo public static string GetAudioExt(string videoFile) { - switch (FFmpegCommands.GetAudioCodec(videoFile)) + switch (FfmpegCommands.GetAudioCodec(videoFile)) { case "vorbis": return "ogg"; case "opus": return "ogg"; diff --git a/Code/Media/FfmpegAudioAndMetadata.cs b/Code/Media/FfmpegAudioAndMetadata.cs new file mode 100644 index 0000000..285c18c --- /dev/null +++ b/Code/Media/FfmpegAudioAndMetadata.cs @@ -0,0 +1,148 @@ +using Flowframes.Data; +using Flowframes.IO; +using Flowframes.Main; +using Flowframes.MiscUtils; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using static Flowframes.AvProcess; +using Utils = Flowframes.Media.FFmpegUtils; + +namespace Flowframes.Media +{ + partial class FfmpegAudioAndMetadata : FfmpegCommands + { + public static async Task ExtractAudio(string inputFile, string outFile) // https://stackoverflow.com/a/27413824/14274419 + { + string audioExt = Utils.GetAudioExt(inputFile); + outFile = Path.ChangeExtension(outFile, audioExt); + Logger.Log($"[FFCmds] Extracting audio from {inputFile} to {outFile}", true); + string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}"; + await RunFfmpeg(args, LogMode.Hidden); + if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) + { + Logger.Log("Failed to extract audio losslessly! Trying to re-encode."); + File.Delete(outFile); + + outFile = Path.ChangeExtension(outFile, Utils.GetAudioExtForContainer(Path.GetExtension(inputFile))); + args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outFile.Wrap()}"; + await RunFfmpeg(args, LogMode.Hidden); + + if ((File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) || lastOutputFfmpeg.Contains("Invalid data")) + { + Logger.Log("Failed to extract audio, even with re-encoding. Output will not have audio."); + IOUtils.TryDeleteIfExists(outFile); + return; + } + + Logger.Log($"Source audio has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly.", false, true); + } + } + + public static async Task ExtractSubtitles(string inputFile, string outFolder, Interpolate.OutMode outMode) + { + Dictionary subtitleTracks = await GetSubtitleTracks(inputFile); + + foreach (KeyValuePair subTrack in subtitleTracks) + { + string trackName = subTrack.Value.Length > 4 ? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(subTrack.Value.ToLower()) : subTrack.Value.ToUpper(); + string outPath = Path.Combine(outFolder, $"{subTrack.Key}-{trackName}.srt"); + string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}"; + await RunFfmpeg(args, LogMode.Hidden); + if (lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks + break; + Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg"); + } + + if (subtitleTracks.Count > 0) + { + Logger.Log($"Extracted {subtitleTracks.Count} subtitle tracks from the input video."); + Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true); + } + } + + public static async Task> GetSubtitleTracks(string inputFile) + { + Dictionary subDict = new Dictionary(); + string args = $"-i {inputFile.Wrap()}"; + string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines(); + string[] filteredLines = outputLines.Where(l => l.Contains(" Subtitle: ")).ToArray(); + int idx = 0; + foreach (string line in filteredLines) + { + string lang = "unknown"; + bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: "); + if (hasLangInfo) + lang = line.Split('(')[1].Split(')')[0]; + subDict.Add(idx, lang); + idx++; + } + return subDict; + } + + public static async Task MergeAudioAndSubs(string inputFile, string audioPath, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667 + { + Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true); + string containerExt = Path.GetExtension(inputFile); + string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile); + string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile); + File.Move(inputFile, tempPath); + string inName = Path.GetFileName(tempPath); + string audioName = Path.GetFileName(audioPath); + string outName = Path.GetFileName(outPath); + + bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs"); + string subInputArgs = ""; + string subMapArgs = ""; + string subMetaArgs = ""; + string[] subTracks = subs ? IOUtils.GetFilesSorted(tempFolder, false, "*.srt") : new string[0]; + + for (int subTrack = 0; subTrack < subTracks.Length; subTrack++) + { + subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}"; + subMapArgs += $" -map {subTrack + 2}"; + subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}"; + } + + string subCodec = Utils.GetSubCodecForContainer(containerExt); + string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" + + $"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}"; + + await RunFfmpeg(args, tempFolder, LogMode.Hidden); + + if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream")) + { + Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg"); + + args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" + + $"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}"; + + await RunFfmpeg(args, tempFolder, LogMode.Hidden); + + if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream")) + { + Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg"); + IOUtils.TryDeleteIfExists(tempPath); + return; + } + + string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper(); + Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg"); + } + + if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512) + { + File.Delete(tempPath); + File.Move(outPath, inputFile); + } + else + { + File.Move(tempPath, inputFile); + } + } + } +} diff --git a/Code/AudioVideo/FFmpegCommands.cs b/Code/Media/FfmpegCommands.cs similarity index 57% rename from Code/AudioVideo/FFmpegCommands.cs rename to Code/Media/FfmpegCommands.cs index dbc2f69..2a5e8cf 100644 --- a/Code/AudioVideo/FFmpegCommands.cs +++ b/Code/Media/FfmpegCommands.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.Data; using Flowframes.IO; using Flowframes.Main; @@ -11,11 +11,11 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using static Flowframes.AvProcess; -using Utils = Flowframes.AudioVideo.FFmpegUtils; +using Utils = Flowframes.Media.FFmpegUtils; namespace Flowframes { - class FFmpegCommands + class FfmpegCommands { public static string divisionFilter = "\"pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0\""; public static string pngComprArg = "-compression_level 3"; @@ -31,19 +31,6 @@ namespace Flowframes await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge); } - public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine) - { - if (logMode != LogMode.Hidden) - Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS..."); - string vfrFilename = Path.GetFileName(framesFile); - string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : ""; - string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}"; - string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter }); - string rate = fps.ToStringDot(); - string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}"; - await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, TaskType.Encode); - } - public static async Task ExtractAlphaDir (string rgbDir, string alphaDir) { Directory.CreateDirectory(alphaDir); @@ -100,142 +87,12 @@ namespace Flowframes DeleteSource(inputFile); } - public static async Task ExtractAudio(string inputFile, string outFile) // https://stackoverflow.com/a/27413824/14274419 - { - string audioExt = Utils.GetAudioExt(inputFile); - outFile = Path.ChangeExtension(outFile, audioExt); - Logger.Log($"[FFCmds] Extracting audio from {inputFile} to {outFile}", true); - string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}"; - await RunFfmpeg(args, LogMode.Hidden); - if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) - { - Logger.Log("Failed to extract audio losslessly! Trying to re-encode."); - File.Delete(outFile); - - outFile = Path.ChangeExtension(outFile, Utils.GetAudioExtForContainer(Path.GetExtension(inputFile))); - args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outFile.Wrap()}"; - await RunFfmpeg(args, LogMode.Hidden); - - if ((File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) || lastOutputFfmpeg.Contains("Invalid data")) - { - Logger.Log("Failed to extract audio, even with re-encoding. Output will not have audio."); - IOUtils.TryDeleteIfExists(outFile); - return; - } - - Logger.Log($"Source audio has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly.", false, true); - } - } - - public static async Task ExtractSubtitles (string inputFile, string outFolder, Interpolate.OutMode outMode) - { - Dictionary subtitleTracks = await GetSubtitleTracks(inputFile); - - foreach (KeyValuePair subTrack in subtitleTracks) - { - string trackName = subTrack.Value.Length > 4 ? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(subTrack.Value.ToLower()) : subTrack.Value.ToUpper(); - string outPath = Path.Combine(outFolder, $"{subTrack.Key}-{trackName}.srt"); - string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}"; - await RunFfmpeg(args, LogMode.Hidden); - if (lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks - break; - Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg"); - } - - if(subtitleTracks.Count > 0) - { - Logger.Log($"Extracted {subtitleTracks.Count} subtitle tracks from the input video."); - Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true); - } - } - - public static async Task> GetSubtitleTracks (string inputFile) - { - Dictionary subDict = new Dictionary(); - string args = $"-i {inputFile.Wrap()}"; - string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines(); - string[] filteredLines = outputLines.Where(l => l.Contains(" Subtitle: ")).ToArray(); - int idx = 0; - foreach(string line in filteredLines) - { - string lang = "unknown"; - bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: "); - if (hasLangInfo) - lang = line.Split('(')[1].Split(')')[0]; - subDict.Add(idx, lang); - idx++; - } - return subDict; - } - - public static async Task MergeAudioAndSubs(string inputFile, string audioPath, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667 - { - Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true); - string containerExt = Path.GetExtension(inputFile); - string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile); - string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile); - File.Move(inputFile, tempPath); - string inName = Path.GetFileName(tempPath); - string audioName = Path.GetFileName(audioPath); - string outName = Path.GetFileName(outPath); - - bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs"); - string subInputArgs = ""; - string subMapArgs = ""; - string subMetaArgs = ""; - string[] subTracks = subs ? IOUtils.GetFilesSorted(tempFolder, false, "*.srt") : new string[0]; - - for (int subTrack = 0; subTrack < subTracks.Length; subTrack++) - { - subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}"; - subMapArgs += $" -map {subTrack+2}"; - subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}"; - } - - string subCodec = Utils.GetSubCodecForContainer(containerExt); - string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" + - $"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}"; - - await RunFfmpeg(args, tempFolder, LogMode.Hidden); - - if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream")) - { - Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg"); - - args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" + - $"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}"; - - await RunFfmpeg(args, tempFolder, LogMode.Hidden); - - if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream")) - { - Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg"); - IOUtils.TryDeleteIfExists(tempPath); - return; - } - - string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper(); - Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg"); - } - - if(File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512) - { - File.Delete(tempPath); - File.Move(outPath, inputFile); - } - else - { - File.Move(tempPath, inputFile); - } - } - public static long GetDuration(string inputFile) { Logger.Log("Reading Duration using ffprobe.", true, false, "ffprobe"); string args = $" -v panic -select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}"; string info = GetFfprobeOutput(args); return FormatUtils.MsFromTimestamp(info); - return -1; } public static float GetFramerate(string inputFile) diff --git a/Code/AudioVideo/FfmpegEncode.cs b/Code/Media/FfmpegEncode.cs similarity index 67% rename from Code/AudioVideo/FfmpegEncode.cs rename to Code/Media/FfmpegEncode.cs index 9eca024..ea8284f 100644 --- a/Code/AudioVideo/FfmpegEncode.cs +++ b/Code/Media/FfmpegEncode.cs @@ -10,11 +10,11 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using static Flowframes.AvProcess; -using Utils = Flowframes.AudioVideo.FFmpegUtils; +using Utils = Flowframes.Media.FFmpegUtils; -namespace Flowframes.AudioVideo +namespace Flowframes.Media { - partial class FfmpegEncode : FFmpegCommands + partial class FfmpegEncode : FfmpegCommands { public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false) { @@ -36,6 +36,19 @@ namespace Flowframes.AudioVideo await RunFfmpeg(args, framesFile.GetParentDir(), logMode, TaskType.Encode, !isChunk); } + public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine) + { + if (logMode != LogMode.Hidden) + Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS..."); + string vfrFilename = Path.GetFileName(framesFile); + string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : ""; + string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}"; + string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter }); + string rate = fps.ToStringDot(); + string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}"; + await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, TaskType.Encode); + } + public static async Task Encode(string inputFile, string vcodec, string acodec, int crf, int audioKbps = 0, bool delSrc = false) { string outPath = Path.ChangeExtension(inputFile, null) + "-convert.mp4"; diff --git a/Code/AudioVideo/FfmpegExtract.cs b/Code/Media/FfmpegExtract.cs similarity index 98% rename from Code/AudioVideo/FfmpegExtract.cs rename to Code/Media/FfmpegExtract.cs index 653f190..0cd4cea 100644 --- a/Code/AudioVideo/FfmpegExtract.cs +++ b/Code/Media/FfmpegExtract.cs @@ -11,9 +11,9 @@ using System.Linq; using System.Threading.Tasks; using static Flowframes.AvProcess; -namespace Flowframes.AudioVideo +namespace Flowframes.Media { - partial class FfmpegExtract : FFmpegCommands + partial class FfmpegExtract : FfmpegCommands { public static async Task ExtractSceneChanges(string inputFile, string frameFolderPath, float rate) { diff --git a/Code/MiscUtils/FormatUtils.cs b/Code/MiscUtils/FormatUtils.cs index 278d8f2..a92d376 100644 --- a/Code/MiscUtils/FormatUtils.cs +++ b/Code/MiscUtils/FormatUtils.cs @@ -59,13 +59,21 @@ namespace Flowframes.MiscUtils public static long MsFromTimestamp(string timestamp) { - string[] values = timestamp.Split(':'); - int hours = int.Parse(values[0]); - int minutes = int.Parse(values[1]); - int seconds = int.Parse(values[2].Split('.')[0]); - int milliseconds = int.Parse(values[2].Split('.')[1].Substring(0, 2)) * 10; - long ms = hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds; - return ms; + try + { + string[] values = timestamp.Split(':'); + int hours = int.Parse(values[0]); + int minutes = int.Parse(values[1]); + int seconds = int.Parse(values[2].Split('.')[0]); + int milliseconds = int.Parse(values[2].Split('.')[1].Substring(0, 2)) * 10; + long ms = hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds; + return ms; + } + catch (Exception e) + { + Logger.Log("MsFromTimeStamp Exception: " + e.Message); + return 0; + } } public static string MsToTimestamp(long milliseconds) diff --git a/Code/OS/AiProcess.cs b/Code/OS/AiProcess.cs index 596f08c..dc27046 100644 --- a/Code/OS/AiProcess.cs +++ b/Code/OS/AiProcess.cs @@ -82,7 +82,7 @@ namespace Flowframes string rgbInterpDir = Path.Combine(Interpolate.current.tempFolder, Paths.interpDir); string alphaInterpDir = Path.Combine(Interpolate.current.tempFolder, Paths.interpDir + Paths.alphaSuffix); if (!Directory.Exists(alphaInterpDir)) return; - await FFmpegCommands.MergeAlphaIntoRgb(rgbInterpDir, Padding.interpFrames, alphaInterpDir, Padding.interpFrames, false); + await FfmpegCommands.MergeAlphaIntoRgb(rgbInterpDir, Padding.interpFrames, alphaInterpDir, Padding.interpFrames, false); } } diff --git a/Code/UI/MainUiFunctions.cs b/Code/UI/MainUiFunctions.cs index 44c8746..76dfc64 100644 --- a/Code/UI/MainUiFunctions.cs +++ b/Code/UI/MainUiFunctions.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.IO; using Flowframes.Magick; using Flowframes.Main; diff --git a/Code/UI/UtilsTab.cs b/Code/UI/UtilsTab.cs index 788c778..3646edb 100644 --- a/Code/UI/UtilsTab.cs +++ b/Code/UI/UtilsTab.cs @@ -1,4 +1,4 @@ -using Flowframes.AudioVideo; +using Flowframes.Media; using Flowframes.IO; using Flowframes.Magick; using Flowframes.Main; @@ -22,7 +22,7 @@ namespace Flowframes.UI await FfmpegExtract.VideoToFrames(videoPath, Path.Combine(outPath, Paths.framesDir), false, Interpolate.current.inFps, false, false, false); File.WriteAllText(Path.Combine(outPath, "fps.ini"), Interpolate.current.inFps.ToString()); if (withAudio) - await FFmpegCommands.ExtractAudio(videoPath, Path.Combine(outPath, "audio")); + await FfmpegAudioAndMetadata.ExtractAudio(videoPath, Path.Combine(outPath, "audio")); Program.mainForm.SetWorking(false); Logger.Log("Done."); Program.mainForm.SetProgress(0); @@ -34,7 +34,7 @@ namespace Flowframes.UI return; int times = loopTimes.GetInt(); Logger.Log("Lopping video " + times + "x...", true); - await FFmpegCommands.LoopVideo(inputFile, times, false); + await FfmpegCommands.LoopVideo(inputFile, times, false); Logger.Log("Done", true); Program.mainForm.SetProgress(0); } @@ -45,7 +45,7 @@ namespace Flowframes.UI return; float speedFloat = speed.GetFloat(); Logger.Log("Creating video with " + speed + "% speed...", true); - await FFmpegCommands.ChangeSpeed(inputFile, speedFloat, false); + await FfmpegCommands.ChangeSpeed(inputFile, speedFloat, false); Logger.Log("Done", true); Program.mainForm.SetProgress(0); }