using System.Globalization; using System.Text.Json; namespace EmbyToolbox.Services; /// /// Извлечение количества аудиодорожек и оценка суммарного размера аудио по JSON ffprobe. /// public static class FfprobeAudioInfoParser { public static FfprobeAudioInfo? 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 FfprobeAudioInfo? ParseRoot(JsonElement root) { if (root.ValueKind != JsonValueKind.Object) { return null; } double? formatDurationSec = null; if (root.TryGetProperty("format", out var format) && format.ValueKind == JsonValueKind.Object) { formatDurationSec = TryReadDurationSeconds(format); } if (!root.TryGetProperty("streams", out var streams) || streams.ValueKind != JsonValueKind.Array) { return new FfprobeAudioInfo(0, null, false); } var audioList = new List(); foreach (var stream in streams.EnumerateArray()) { if (stream.ValueKind == JsonValueKind.Object && stream.TryGetProperty("codec_type", out var ct) && ct.ValueKind == JsonValueKind.String && string.Equals(ct.GetString(), "audio", StringComparison.OrdinalIgnoreCase)) { audioList.Add(stream); } } var count = audioList.Count; if (count == 0) { return new FfprobeAudioInfo(0, 0, false); } var partial = false; var totalBytes = 0.0; var anySize = false; foreach (var stream in audioList) { var duration = TryReadStreamDurationSeconds(stream) ?? formatDurationSec; var bps = TryReadBitrateBps(stream); if (duration is null || bps is null) { partial = true; continue; } if (double.IsNaN(duration.Value) || double.IsInfinity(duration.Value) || duration.Value <= 0) { partial = true; continue; } if (bps.Value <= 0) { partial = true; continue; } totalBytes += duration.Value * (bps.Value / 8.0); anySize = true; } int? sizeMb; if (!anySize) { sizeMb = null; } else { var mb = totalBytes / (1024.0 * 1024.0); sizeMb = (int)Math.Round(mb, MidpointRounding.AwayFromZero); } return new FfprobeAudioInfo(count, sizeMb, partial); } private static double? TryReadDurationSeconds(JsonElement el) { if (el.TryGetProperty("duration", out var d) && d.ValueKind == JsonValueKind.String) { if (double.TryParse(d.GetString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var s)) { return s; } } return null; } private static double? TryReadStreamDurationSeconds(JsonElement stream) { return TryReadDurationSeconds(stream); } private static long? TryReadBitrateBps(JsonElement stream) { if (stream.TryGetProperty("bit_rate", out var br) && br.ValueKind == JsonValueKind.String) { var s = br.GetString(); if (long.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var b) && b > 0) { return b; } } if (stream.TryGetProperty("max_bit_rate", out var mbr) && mbr.ValueKind == JsonValueKind.String) { var s = mbr.GetString(); if (long.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var b) && b > 0) { return b; } } if (stream.TryGetProperty("tags", out var tags) && tags.ValueKind == JsonValueKind.Object) { foreach (var p in tags.EnumerateObject()) { if (p.Name.Contains("BPS", StringComparison.OrdinalIgnoreCase) && p.Value.ValueKind is JsonValueKind.String or JsonValueKind.Number) { if (p.Value.ValueKind == JsonValueKind.String && long.TryParse(p.Value.GetString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var b) && b > 0) { return b; } if (p.Value.ValueKind == JsonValueKind.Number && p.Value.TryGetInt64(out var n) && n > 0) { return n; } } } } return null; } } public readonly record struct FfprobeAudioInfo(int AudioStreamCount, int? AudioSizeMbTotal, bool IsPartial);