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