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