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 ?? "?";
}