From 85199870a1780c0679662c9bd375087de904c67f Mon Sep 17 00:00:00 2001 From: n00mkrad <61149547+n00mkrad@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:52:09 +0100 Subject: [PATCH] Handle re-encoding (or dropping) or incompatible streams per-stream --- CodeLegacy/Media/AvOutputHandler.cs | 2 +- CodeLegacy/Media/FfmpegAudioAndMetadata.cs | 19 +--- CodeLegacy/Media/FfmpegCommands.cs | 17 --- CodeLegacy/Media/FfmpegUtils.cs | 118 +++++++++++++++++---- 4 files changed, 105 insertions(+), 51 deletions(-) diff --git a/CodeLegacy/Media/AvOutputHandler.cs b/CodeLegacy/Media/AvOutputHandler.cs index 40b02a2..cd5a395 100644 --- a/CodeLegacy/Media/AvOutputHandler.cs +++ b/CodeLegacy/Media/AvOutputHandler.cs @@ -73,7 +73,7 @@ namespace Flowframes.Media if (line.MatchesWildcard("*codec*not supported*")) { - Interpolate.Cancel($"Error: {line}\n\nTry using a different codec."); + Interpolate.Cancel($"Error: {line}\n\nTry using a different codec or container format."); return; } diff --git a/CodeLegacy/Media/FfmpegAudioAndMetadata.cs b/CodeLegacy/Media/FfmpegAudioAndMetadata.cs index 95ff619..0e35ff5 100644 --- a/CodeLegacy/Media/FfmpegAudioAndMetadata.cs +++ b/CodeLegacy/Media/FfmpegAudioAndMetadata.cs @@ -38,27 +38,18 @@ namespace Flowframes.Media string inName = Path.GetFileName(tempPath); string outName = Path.GetFileName(outPath); - string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt); - - bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.currentSettings.outSettings.Format, GetAudioCodecs(inputVideo)); - bool slowmo = I.currentSettings.outItsScale != 0 && I.currentSettings.outItsScale != 1; - string audioArgs = audioCompat && !slowmo ? "" : await Utils.GetAudioFallbackArgs(inputVideo, I.currentSettings.outSettings.Format, I.currentSettings.outItsScale); - - if (!audioCompat && !slowmo) - Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg"); + // if (!audioCompat && !slowmo) + // Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg"); bool audio = Config.GetBool(Config.Key.keepAudio); bool subs = Config.GetBool(Config.Key.keepSubs); bool meta = Config.GetBool(Config.Key.keepMeta); - if (!audio) - audioArgs = "-an"; - - if (!subs || (subs && !Utils.ContainerSupportsSubs(containerExt))) - subArgs = "-sn"; + string audioArgs = audio ? Utils.MapAudio(I.currentMediaFile, I.currentSettings.outSettings.Format, I.currentSettings.outItsScale) : "-an"; + string subArgs = subs && Utils.ContainerSupportsSubs(containerExt) ? Utils.MapSubtitles(I.currentMediaFile, I.currentSettings.outSettings.Format) : "-sn"; bool isMkv = I.currentSettings.outSettings.Format == Data.Enums.Output.Format.Mkv; - var muxArgs = new List() { "-map 0:v:0", "-map 1:a:?", "-map 1:s:?", "-c copy", audioArgs, subArgs }; + var muxArgs = new List() { "-map 0:v:0", "-c:v copy", audioArgs, subArgs }; muxArgs.AddIf("-max_interleave_delta 0", isMkv); // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/ muxArgs.AddIf("-map 1:t?", isMkv && meta); // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/ muxArgs.AddIf("-shortest", shortest); diff --git a/CodeLegacy/Media/FfmpegCommands.cs b/CodeLegacy/Media/FfmpegCommands.cs index d3c383a..805a113 100644 --- a/CodeLegacy/Media/FfmpegCommands.cs +++ b/CodeLegacy/Media/FfmpegCommands.cs @@ -412,23 +412,6 @@ namespace Flowframes return compat; } - public static List GetAudioCodecs(string path, int streamIndex = -1) - { - Logger.Log($"GetAudioCodecs('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg"); - List codecNames = new List(); - string args = $"-loglevel panic -select_streams a -show_entries stream=codec_name {path.Wrap()}"; - string info = GetFfprobeOutput(args); - string[] entries = info.SplitIntoLines(); - - foreach (string entry in entries) - { - if (entry.Contains("codec_name=")) - codecNames.Add(entry.Remove("codec_name=").Trim()); - } - - return codecNames; - } - public static void DeleteSource(string path) { Logger.Log("[FFCmds] Deleting input file/dir: " + path, true); diff --git a/CodeLegacy/Media/FfmpegUtils.cs b/CodeLegacy/Media/FfmpegUtils.cs index 8fa18b9..8e0835a 100644 --- a/CodeLegacy/Media/FfmpegUtils.cs +++ b/CodeLegacy/Media/FfmpegUtils.cs @@ -462,6 +462,29 @@ namespace Flowframes.Media return supported; } + public static bool ContainerSupportsSubtitleCodec(Enums.Output.Format outFormat, string codec) + { + bool supported = false; + + string[] formatsMp4 = new string[] { "mov_text" }; + string[] formatsMkv = new string[] { "subrip", "ass", "ssa", "dvdsub", "hdmv_pgs_subtitle", "webvtt", "dvbsub" }; + string[] formatsWebm = new string[] { "webvtt" }; + string[] formatsMov = new string[] { "mov_text" }; + string[] formatsAvi = new string[] { "xsub" }; + + switch (outFormat) + { + case Enums.Output.Format.Mp4: supported = formatsMp4.Contains(codec); break; + case Enums.Output.Format.Mkv: supported = formatsMkv.Contains(codec); break; + case Enums.Output.Format.Webm: supported = formatsWebm.Contains(codec); break; + case Enums.Output.Format.Mov: supported = formatsMov.Contains(codec); break; + case Enums.Output.Format.Avi: supported = formatsAvi.Contains(codec); break; + } + + Logger.Log($"Checking if {outFormat} supports subtitle format '{codec}': {supported}", true, false, "ffmpeg"); + return supported; + } + public static string GetExt(OutputSettings settings, bool dot = true) { string ext = dot ? "." : ""; @@ -499,44 +522,101 @@ namespace Flowframes.Media return "unsupported"; } - public static async Task GetAudioFallbackArgs(string videoPath, Enums.Output.Format outFormat, float itsScale) + public static string GetAudioFallbackArgs(Enums.Output.Format outFormat, int ac, int relIdx) { - bool opusMp4 = Config.GetBool(Config.Key.allowOpusInMp4); int opusBr = Config.GetInt(Config.Key.opusBitrate, 128); int aacBr = Config.GetInt(Config.Key.aacBitrate, 160); - int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt(); - string af = GetAudioFilters(itsScale); - if (outFormat == Enums.Output.Format.Mkv || outFormat == Enums.Output.Format.Webm || (outFormat == Enums.Output.Format.Mp4 && opusMp4)) - return $"-c:a libopus -b:a {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; // Double bitrate if 5ch or more, ignore ac if <= 0 + if (outFormat == Enums.Output.Format.Mkv || outFormat == Enums.Output.Format.Webm || (outFormat == Enums.Output.Format.Mp4)) + return $"-c:a:{relIdx} libopus -b:a:{relIdx} {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac:a:{relIdx} {(ac > 0 ? $"{ac}" : "2")}"; // Double bitrate if 5ch or more, ignore ac if <= 0 else - return $"-c:a aac -b:a {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; + return $"-c:a:{relIdx} aac -b:a:{relIdx} {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac:a:{relIdx} {(ac > 0 ? $"{ac}" : "2")}"; + } + + public static string MapAudio(MediaFile m, Enums.Output.Format outFormat, float itsScale) + { + string filters = GetAudioFilters(itsScale); + Dictionary supported = new Dictionary(); + + foreach (var a in m.AudioStreams) + { + supported[a.Codec] = filters.IsEmpty() && ContainerSupportsAudioFormat(outFormat, a.Codec); // Filters = Re-encoding required + } + + // If all are supported, simply copy all streams + if (supported.All(x => x.Value)) + return "-map 1:a -c:a copy"; + + // Otherwise, map each stream individually + string args = ""; + int relIdx = 0; + foreach (var a in m.AudioStreams) + { + if (supported[a.Codec]) + args += $"-map 1:{a.Index} -c:a:{relIdx} copy "; + else + args += $"-map 1:{a.Index} {GetAudioFallbackArgs(outFormat, a.Channels, relIdx)} "; + relIdx++; + } + + return args.TrimEnd(); } private static string GetAudioFilters(float itsScale) { - if (itsScale == 0 || itsScale == 1) + if (itsScale <= 0.0001 || itsScale == 1) return ""; - if (itsScale > 4) - return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4).ToString()}"; - else if (itsScale > 2) - return $"-af atempo=0.5,atempo={((1f / itsScale) * 2).ToString()}"; - else - return $"-af atempo={(1f / itsScale).ToString()}"; + return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4)}"; + if (itsScale > 2) + return $"-af atempo=0.5,atempo={((1f / itsScale) * 2)}"; + return $"-af atempo={(1f / itsScale)}"; } - public static string GetSubCodecForContainer(string containerExt) + public static string MapSubtitles(MediaFile m, Enums.Output.Format outFormat) { - containerExt = containerExt.Remove("."); + Dictionary codec = new Dictionary(); - if (containerExt == "mp4" || containerExt == "mov") + foreach (var a in m.SubtitleStreams) + { + codec[a.Codec] = GetSubCodecForContainer(outFormat, a.Codec); + } + + // If all are supported, simply copy all streams + if (codec.All(x => x.Value == "copy")) + return "-map 1:s -c:s copy"; + + // Otherwise, map each stream individually + string args = ""; + int relIdx = 0; + foreach (var a in m.SubtitleStreams) + { + args += codec[a.Codec].IsEmpty() ? "" : $"-map 1:{a.Index} -c:s:{relIdx} {codec[a.Codec]} "; + relIdx++; + } + + return args.TrimEnd(); + } + + public static string GetSubCodecForContainer(Enums.Output.Format outFormat, string codec) + { + bool muxSupport = ContainerSupportsSubtitleCodec(outFormat, codec); + + if (muxSupport) + return "copy"; + + var textCodecs = new List { "text", "ssa", "mov_text", "srt", "subrip", "webvtt", "ass", "hdmv_text_subtitle", "ttml" }; + + if(!textCodecs.Contains(codec)) + return ""; // -> Won't be included + + if (outFormat == Enums.Output.Format.Mp4 || outFormat == Enums.Output.Format.Mov) return "mov_text"; - if (containerExt == "webm") + if (outFormat == Enums.Output.Format.Webm) return "webvtt"; - return "copy"; // Default: Copy subs + return ""; // -> Won't be included } public static bool ContainerSupportsSubs(string containerExt, bool showWarningIfNotSupported = true)