Handle re-encoding (or dropping) or incompatible streams per-stream

This commit is contained in:
n00mkrad
2026-01-12 20:52:09 +01:00
parent beee67bf09
commit 85199870a1
4 changed files with 105 additions and 51 deletions

View File

@@ -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;
}

View File

@@ -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<string>() { "-map 0:v:0", "-map 1:a:?", "-map 1:s:?", "-c copy", audioArgs, subArgs };
var muxArgs = new List<string>() { "-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);

View File

@@ -412,23 +412,6 @@ namespace Flowframes
return compat;
}
public static List<string> GetAudioCodecs(string path, int streamIndex = -1)
{
Logger.Log($"GetAudioCodecs('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
List<string> codecNames = new List<string>();
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);

View File

@@ -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<string> 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<string, bool> supported = new Dictionary<string, bool>();
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<string, string> codec = new Dictionary<string, string>();
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<string> { "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)