using System.Globalization; using System.Linq; using System.Text.Json; using EmbyToolbox.Models; namespace EmbyToolbox.Services; /// Разбор JSON ffprobe в . public static class MediaAnalysisParser { public static MediaAnalysisResult? TryParse(string json) { if (string.IsNullOrWhiteSpace(json)) { return null; } try { using var doc = JsonDocument.Parse(json); return ParseRoot(doc.RootElement); } catch { return null; } } private static MediaAnalysisResult ParseRoot(JsonElement root) { var format = string.Empty; var name = string.Empty; long? formatBitRateBps = null; double? durationSec = null; if (root.TryGetProperty("format", out var fmt) && fmt.ValueKind == JsonValueKind.Object) { if (fmt.TryGetProperty("format_name", out var fn) && fn.ValueKind == JsonValueKind.String) { format = fn.GetString() ?? string.Empty; } if (fmt.TryGetProperty("format_long_name", out var fl) && fl.ValueKind == JsonValueKind.String) { name = fl.GetString() ?? string.Empty; } if (fmt.TryGetProperty("duration", out var d)) { durationSec = ParseDuration(d); } formatBitRateBps = ParseLong(fmt, "bit_rate"); } var streams = new List(); if (root.TryGetProperty("streams", out var st) && st.ValueKind == JsonValueKind.Array) { foreach (var e in st.EnumerateArray()) { if (e.ValueKind == JsonValueKind.Object) { var s = TryParseStream(e); if (s is not null) { streams.Add(s); } } } } var primaryVideoBitrate = streams .Where(x => x.Kind == MediaStreamKind.Video) .OrderByDescending(static v => ((long)(v.Width ?? 0)) * (v.Height ?? 0)) .ThenByDescending(static v => v.IsDefault ? 1 : 0) .Select(static v => v.BitRateBps) .FirstOrDefault(); return new MediaAnalysisResult { ContainerFormat = format, FormatName = string.IsNullOrEmpty(name) ? format : name, FormatBitRateBps = formatBitRateBps, DurationSeconds = durationSec, AllStreams = streams, VideoStreams = streams.Where(x => x.Kind == MediaStreamKind.Video).ToList(), AudioStreams = streams.Where(x => x.Kind == MediaStreamKind.Audio).ToList(), SubtitleStreams = streams.Where(x => x.Kind == MediaStreamKind.Subtitle).ToList(), DataStreams = streams.Where(x => x.Kind == MediaStreamKind.Data).ToList(), SourceVideoBitrateBps = primaryVideoBitrate ?? formatBitRateBps }; } private static MediaStreamInfo? TryParseStream(JsonElement s) { if (!s.TryGetProperty("codec_type", out var ct) || ct.ValueKind != JsonValueKind.String) { return null; } var streamDuration = GetStreamDuration(s); var t = (ct.GetString() ?? string.Empty).ToLowerInvariant(); if (t == "video") { return new MediaStreamInfo { Index = GetInt(s, "index", -1), Kind = MediaStreamKind.Video, CodecName = GetStr(s, "codec_name", "?") ?? "?", Profile = GetStr(s, "profile", null), Encoder = GetTagExact(s, "encoder"), Language = GetTagLang(s), Title = GetTagTitle(s), IsDefault = GetDisposition(s, "default", false), Width = (int?)GetIntNullable(s, "width"), Height = (int?)GetIntNullable(s, "height"), AverageFrameRate = ParseFpsByKey(s, "avg_frame_rate"), FrameRate = ParseFps(s), PixelFormat = GetStr(s, "pix_fmt", null), ColorSpace = GetStr(s, "color_space", null), ColorPrimaries = GetStr(s, "color_primaries", null), ColorTransfer = GetStr(s, "color_transfer", null), BitRateBps = ParseLong(s, "bit_rate") ?? ParseLong(s, "max_bit_rate"), DurationSeconds = streamDuration }; } if (t == "audio") { return new MediaStreamInfo { Index = GetInt(s, "index", -1), Kind = MediaStreamKind.Audio, CodecName = GetStr(s, "codec_name", "?") ?? "?", Profile = GetStr(s, "profile", null), Encoder = GetTagExact(s, "encoder"), Language = GetTagLang(s), Title = GetTagTitle(s), IsDefault = GetDisposition(s, "default", false), BitRateBps = ParseLong(s, "bit_rate") ?? ParseLong(s, "max_bit_rate"), Channels = (int?)GetIntNullable(s, "channels"), SampleRateHz = (int?)GetIntNullable(s, "sample_rate"), DurationSeconds = streamDuration }; } if (t == "subtitle") { var isForcedDisposition = GetDisposition(s, "forced", false); return new MediaStreamInfo { Index = GetInt(s, "index", -1), Kind = MediaStreamKind.Subtitle, CodecName = GetStr(s, "codec_name", "?") ?? "?", Profile = GetStr(s, "profile", null), Encoder = GetTagExact(s, "encoder"), Language = GetTagLang(s), Title = GetTagTitle(s), IsDefault = GetDisposition(s, "default", false), IsForcedByDisposition = isForcedDisposition, IsForced = isForcedDisposition, ForcedDetectionReason = isForcedDisposition ? "disposition" : null, SubtitleFormat = GetStr(s, "codec_name", null), FileNameTag = GetTagExact(s, "filename"), DurationSeconds = streamDuration }; } if (t == "attachment") { return new MediaStreamInfo { Index = GetInt(s, "index", -1), Kind = MediaStreamKind.Attachment, CodecName = "attachment", DurationSeconds = streamDuration, AttachmentDeclaredFileName = GetTagExact(s, "filename"), AttachmentDeclaredMimeType = GetTagExact(s, "mimetype"), }; } if (t == "data") { return new MediaStreamInfo { Index = GetInt(s, "index", -1), Kind = MediaStreamKind.Data, CodecName = GetStr(s, "codec_name", "data") ?? "data", DurationSeconds = streamDuration }; } return null; } private static double? GetStreamDuration(JsonElement s) { if (!s.TryGetProperty("duration", out var d)) { return null; } return ParseDuration(d); } private static int GetInt(JsonElement s, string name, int def) { if (s.TryGetProperty(name, out var n)) { if (n.ValueKind == JsonValueKind.Number && n.TryGetInt32(out var v)) { return v; } if (n.ValueKind == JsonValueKind.String && int.TryParse(n.GetString(), out var s2)) { return s2; } } return def; } private static int? GetIntNullable(JsonElement s, string name) { if (s.TryGetProperty(name, out var n) && n.ValueKind == JsonValueKind.Number) { return n.GetInt32(); } if (s.TryGetProperty(name, out var s2) && s2.ValueKind == JsonValueKind.String && int.TryParse(s2.GetString(), out var p)) { return p; } return null; } private static string? GetStr(JsonElement s, string name, string? def) { if (s.TryGetProperty(name, out var n) && n.ValueKind == JsonValueKind.String) { return n.GetString() ?? def; } return def; } private static long? ParseLong(JsonElement s, string name) { if (!s.TryGetProperty(name, out var p)) { return null; } if (p.ValueKind == JsonValueKind.String && long.TryParse(p.GetString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var a)) { return a; } if (p.ValueKind == JsonValueKind.Number) { if (p.TryGetInt64(out var l)) { return l; } } return null; } private static string? GetTagLang(JsonElement s) { if (s.TryGetProperty("tags", out var t) && t.ValueKind == JsonValueKind.Object) { if (t.TryGetProperty("language", out var l) && l.ValueKind == JsonValueKind.String) { return l.GetString(); } } return null; } private static string? GetTagTitle(JsonElement s) { if (s.TryGetProperty("tags", out var t) && t.ValueKind == JsonValueKind.Object) { if (t.TryGetProperty("title", out var l) && l.ValueKind == JsonValueKind.String) { return l.GetString(); } } return null; } private static string? GetTagExact(JsonElement stream, string tagKey) { if (!stream.TryGetProperty("tags", out var tags) || tags.ValueKind != JsonValueKind.Object) { return null; } if (!tags.TryGetProperty(tagKey, out var val) || val.ValueKind != JsonValueKind.String) { return null; } return val.GetString(); } private static bool GetDisposition(JsonElement s, string d, bool def) { if (s.TryGetProperty("disposition", out var dis) && dis.ValueKind == JsonValueKind.Object) { if (dis.TryGetProperty(d, out var v)) { if (v.ValueKind == JsonValueKind.Number) { return v.GetInt32() != 0; } } } return def; } private static double? ParseFps(JsonElement s) { return ParseFpsByKey(s, "r_frame_rate"); } private static double? ParseFpsByKey(JsonElement s, string key) { if (!s.TryGetProperty(key, out var f) || f.ValueKind != JsonValueKind.String) { return null; } return ParseFpsString(f.GetString() ?? string.Empty); } private static double? ParseFpsString(string s) { if (string.IsNullOrEmpty(s) || s == "0/0") { return null; } var p = s.IndexOf('/'); if (p > 0) { if (int.TryParse(s.AsSpan(0, p), out var a) && int.TryParse(s.AsSpan(p + 1), out var b) && b != 0) { return a / (double)b; } } if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) { return d; } return null; } private static double? ParseDuration(JsonElement d) { if (d.ValueKind == JsonValueKind.Number) { return d.GetDouble(); } if (d.ValueKind == JsonValueKind.String && double.TryParse(d.GetString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v)) { return v; } return null; } }