49 lines
2.9 KiB
C#
49 lines
2.9 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
namespace Jellyfin.Plugin.Enigma2;
|
|
|
|
/// <summary>Only the status picture is encoded. Unsupported programs stay entirely in stream-copy mode.</summary>
|
|
public sealed class LosslessSlateProfile
|
|
{
|
|
public int Width { get; private init; }
|
|
public int Height { get; private init; }
|
|
public double Rate { get; private init; }
|
|
public string RateArgument { get; private init; }
|
|
public string Aspect { get; private init; }
|
|
public string Profile { get; private init; }
|
|
public int Gop { get; private init; }
|
|
public double SegmentSeconds => Gop / Rate;
|
|
public static LosslessSlateProfile Create(BroadcastTracks tracks)
|
|
{
|
|
var video = tracks.Input[0];
|
|
// A guessed codec/layout is unsafe during cold-start outages. Never convert the channel to fit a slate.
|
|
if (video.Codec != "h264" || video.PixelFormat != "yuv420p" || video.IsInterlaced
|
|
|| video.Width is not (> 0 and <= 16384) || video.Height is not (> 0 and <= 16384)
|
|
|| video.Width % 2 != 0 || video.Height % 2 != 0
|
|
|| video.ColorTransfer is "smpte2084" or "arib-std-b67"
|
|
|| tracks.AudioCount == 0 || tracks.AudioCount > 8
|
|
|| tracks.Input.Where(s => s.Type == MediaStreamType.Audio).Any(s => s.Codec != "aac" || s.Profile != "LC" || s.Channels != 2 || s.SampleRate != 48000)) return null;
|
|
var profile = video.Profile switch { "Constrained Baseline" or "Baseline" => "baseline", "Main" => "main", "High" => "high", _ => null };
|
|
var rate = (double)(video.AverageFrameRate ?? video.RealFrameRate ?? 0);
|
|
var aspect = (video as BroadcastMediaStream)?.SampleAspectRatio;
|
|
if (profile == null || !double.IsFinite(rate) || rate <= 0 || rate > 240
|
|
|| aspect == null || !Regex.IsMatch(aspect, @"^[1-9][0-9]{0,5}:[1-9][0-9]{0,5}$")) return null;
|
|
var rational = (video as BroadcastMediaStream)?.FrameRateRational;
|
|
if (rational == null || !Regex.IsMatch(rational, @"^[1-9][0-9]{0,8}/[1-9][0-9]{0,8}$"))
|
|
rational = rate.ToString("0.########", CultureInfo.InvariantCulture);
|
|
// Repeating a cached clip must end on complete video AND AAC frames.
|
|
// If that needs a long clip (e.g. 23.976/59.94), retain the original
|
|
// broadcast without a status clip rather than rounding its cadence.
|
|
var gop = Enumerable.Range(Math.Max(1, (int)(rate * 1.5)), Math.Max(1, (int)(rate * 1.5) + 2))
|
|
.Where(n => Math.Abs(n / rate * 48000 / 1024 - Math.Round(n / rate * 48000 / 1024)) < 0.00001)
|
|
.OrderBy(n => Math.Abs(n / rate - ContinuityTimeline.SegmentSeconds)).FirstOrDefault();
|
|
if (gop == 0) return null;
|
|
return new() { Width = video.Width.Value, Height = video.Height.Value, Rate = rate,
|
|
RateArgument = rational, Aspect = aspect.Replace(':', '/'), Profile = profile, Gop = gop };
|
|
}
|
|
}
|