using EmbyToolbox.Models; namespace EmbyToolbox.Services; /// Правила совместимости субтитров с контейнером MKV/MP4 и codec copy в FFmpeg. public static class SubtitleCodecRules { public static string Normalize(string? codec) => string.IsNullOrWhiteSpace(codec) ? string.Empty : codec.Trim().ToLowerInvariant(); public static bool IsTeletext(string? codec) => Normalize(codec) == "dvb_teletext"; public static bool IsUnknownSubtitleCodec(string? codec) { var n = Normalize(codec); return n is "" or "?" or "unknown"; } /// Кодеки, которые можно mux copy в Matroska (без teletext / mov_text и пр.). public static bool IsMkvCopySafe(string? codec) { return Normalize(codec) switch { "subrip" or "srt" or "ass" or "ssa" => true, "webvtt" or "wvtt" => true, "hdmv_pgs_subtitle" or "pgssub" => true, "dvd_subtitle" or "dvdsub" => true, _ => false }; } public static bool IsMp4TextDirectCopy(string? codec) => Normalize(codec) == "mov_text"; /// Текстовые дорожки, для MP4 требующие перекодирования в mov_text (не mux copy). public static bool Mp4RequiresSubtitleTranscode(string? codec) { return Normalize(codec) switch { "subrip" or "srt" or "ass" or "ssa" => true, _ => false }; } public static bool TargetsMkv(string? container) => !string.IsNullOrWhiteSpace(container) && (container.Contains("mkv", StringComparison.OrdinalIgnoreCase) || container.Contains("matro", StringComparison.OrdinalIgnoreCase)); public static bool TargetsMp4(string? container) => !string.IsNullOrWhiteSpace(container) && (container.Contains("mp4", StringComparison.OrdinalIgnoreCase) || container.Contains("m4v", StringComparison.OrdinalIgnoreCase) || container.Contains("mov", StringComparison.OrdinalIgnoreCase)); /// Действо по умолчанию для встроенной дорожки субтитров после анализа. public static TrackActionKind DefaultEmbeddedSubtitleAction(string? codecName, string? profileContainer) { var c = codecName; if (IsTeletext(c) || IsUnknownSubtitleCodec(c)) { return TrackActionKind.Remove; } if (TargetsMkv(profileContainer)) { return IsMkvCopySafe(c) ? TrackActionKind.Keep : TrackActionKind.Remove; } if (TargetsMp4(profileContainer)) { if (IsMp4TextDirectCopy(c)) { return TrackActionKind.Keep; } if (Mp4RequiresSubtitleTranscode(c)) { return TrackActionKind.Convert; } return TrackActionKind.Remove; } return IsMkvCopySafe(c) ? TrackActionKind.Keep : TrackActionKind.Remove; } /// Встроенную субдорожку с этим решением включают в ffmpeg -map только если истина. public static bool ShouldMapEmbeddedSubtitle( MediaAnalysisResult media, TrackOverrideEntry t, string effectiveContainer) { if (t.Source != SourceKind.Embedded || t.StreamKind != MediaStreamKind.Subtitle) { return true; } if (t.Action == TrackActionKind.Remove) { return false; } var codec = media.AllStreams.FirstOrDefault(s => s.Index == t.StreamIndex)?.CodecName; if (TargetsMkv(effectiveContainer)) { if (!IsMkvCopySafe(codec)) { return false; } return t.Action is TrackActionKind.Keep or TrackActionKind.Convert; } if (TargetsMp4(effectiveContainer)) { if (IsMp4TextDirectCopy(codec) && t.Action == TrackActionKind.Keep) { return true; } if (Mp4RequiresSubtitleTranscode(codec) && t.Action is TrackActionKind.Convert or TrackActionKind.Keep) { return true; } return false; } return IsMkvCopySafe(codec) ? t.Action != TrackActionKind.Remove : false; } /// Строковая подпись Codec для дорожки (субтитры отображать как текстовые / teletext). public static string EmbeddedSubtitleCodecLabel(string? ffprobeCodec) => IsTeletext(ffprobeCodec) ? "dvb_teletext (subtitle)" : ffprobeCodec ?? "?"; }