using System;
using System.Collections.Generic;
using System.Linq;
namespace Jellyfin.Plugin.Floatplane.Api.Models;
/// GET /api/v3/delivery/info response.
public class DeliveryInfoModel
{
public List Groups { get; set; } = new();
/// Flattens every usable variant with its absolute URL.
public IEnumerable Resolve()
{
foreach (var group in Groups)
{
var groupOrigin = group.Origins.FirstOrDefault()?.Url;
foreach (var v in group.Variants)
{
var origin = v.Origins?.FirstOrDefault()?.Url ?? groupOrigin;
if (string.IsNullOrEmpty(v.Url))
{
continue;
}
string url;
if (IsHttpUrl(v.Url))
{
url = v.Url;
}
else if (!string.IsNullOrEmpty(origin))
{
url = origin.TrimEnd('/') + "/" + v.Url.TrimStart('/');
}
else
{
continue;
}
yield return new ResolvedVariant(v, url);
}
}
}
///
/// True for http(s) URLs only. Uri.TryCreate(…, UriKind.Absolute) must not be used
/// for this: on Linux a path like /Videos/x.m3u8 parses as an absolute file:/// URI.
///
public static bool IsHttpUrl(string value)
=> value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || value.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
}
public class DeliveryGroup
{
public List Origins { get; set; } = new();
public List Variants { get; set; } = new();
}
public class DeliveryOrigin
{
public string Url { get; set; } = string.Empty;
public string? QueryUrl { get; set; }
}
public class DeliveryVariant
{
/// e.g. "1080-avc1".
public string Name { get; set; } = string.Empty;
/// e.g. "1080p", "4K".
public string? Label { get; set; }
/// Relative (to origin) or absolute URL incl. signed token. Empty when denied.
public string Url { get; set; } = string.Empty;
public string? MimeType { get; set; }
public long Order { get; set; }
public bool Hidden { get; set; }
public bool Enabled { get; set; } = true;
public List? Origins { get; set; }
public VariantMeta? Meta { get; set; }
public bool IsUsable => Enabled && !Hidden && !string.IsNullOrEmpty(Url);
public int Height => Meta?.Video?.Height ?? ParseHeight(Name);
public static int ParseHeight(string name)
{
var dash = name.IndexOf('-', StringComparison.Ordinal);
var head = dash > 0 ? name[..dash] : name;
return int.TryParse(head, out var h) ? h : 0;
}
}
public class VariantMeta
{
public VariantVideoMeta? Video { get; set; }
public VariantAudioMeta? Audio { get; set; }
public VariantCommonMeta? Common { get; set; }
public VariantLiveMeta? Live { get; set; }
}
public class VariantVideoMeta
{
public string? Codec { get; set; }
public string? CodecSimple { get; set; }
public BitrateMeta? Bitrate { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool IsHdr { get; set; }
public double Fps { get; set; }
public string? MimeType { get; set; }
}
public class VariantAudioMeta
{
public string? Codec { get; set; }
public BitrateMeta? Bitrate { get; set; }
public int ChannelCount { get; set; }
public int Samplerate { get; set; }
public string? MimeType { get; set; }
}
public class BitrateMeta
{
public long Average { get; set; }
public long? Maximum { get; set; }
}
public class VariantCommonMeta
{
public VariantAccess? Access { get; set; }
}
public class VariantAccess
{
public string? DeniedReason { get; set; }
public string? DeniedMessage { get; set; }
}
public class VariantLiveMeta
{
public string? LowLatencyExtension { get; set; }
}
/// A delivery variant with its absolute URL.
public record ResolvedVariant(DeliveryVariant Variant, string Url)
{
public string Name => Variant.Name;
public int Height => Variant.Height;
public bool IsUsable => Variant.IsUsable;
}