namespace ytLive.Models; /// /// The one entitlement rule, as pure policy: what a build is allowed to emit. /// /// Why pixels and a frame rate rather than "720p". The paid delta is output /// quality, and a resolution name is a marketing word, not a measurable bound — 720p60 is /// 921,600 pixels at twice the frame rate, and vertical 720×1280 is the same pixel count /// with a different shape. A pixel budget plus a frame-rate ceiling expresses the same /// promise without a lookup table that a new tier can quietly escape. /// /// Fail closed. Callers pass pro: false when entitlement is unknown, /// unreadable or unresolved; the free profile is the default state, never the fallback /// after an error. /// /// This is not a feature gate. Every feature, scene, overlay and the recording /// itself stay available; only the emitted frame gets smaller. Never call it for anything /// else — a cracked build buys almost nothing, which is the entire anti-piracy posture. /// public static class OutputProfile { /// Free ceiling: 921,600 px = 1280×720, i.e. one 720p frame. public const int FreeMaxPixels = 921_600; /// Free ceiling: 30 frames per second. public const int FreeMaxFps = 30; /// The named tier a capped build falls back to when nothing else qualifies. public const string FreeFallbackName = "720p30"; /// Is this tier within the free budget? Pro may use anything shipped. public static bool IsAllowed(QualityOption tier, bool pro) => pro || (tier.Width * tier.Height <= FreeMaxPixels && tier.Fps <= FreeMaxFps); /// /// The tier a session will actually encode. A permitted selection passes through /// untouched (so Pro and the free tier's own 720p30 keep their exact bitrate); a /// disallowed one resolves to the best tier the budget allows — most pixels first, then /// most frames, then the cheapest bitrate — because silently encoding something the user /// did not pick is only acceptable if it is the closest legal thing to it. /// /// What the dropdown says. /// Every tier the build offers. /// Entitlement, already resolved. Unknown must be passed as false. public static QualityOption Resolve( QualityOption requested, IEnumerable shipped, bool pro) { if (IsAllowed(requested, pro)) return requested; var candidate = shipped .Where(tier => IsAllowed(tier, pro)) .OrderByDescending(tier => tier.Width * tier.Height) .ThenByDescending(tier => tier.Fps) .ThenBy(tier => tier.Bitrate) .FirstOrDefault(); // No shipped tier fits the budget: build the floor explicitly rather than trusting // the table. A build whose tiers were all redefined must still emit a legal frame. return candidate ?? new QualityOption(FreeFallbackName, FreeMaxFps, 6.0, 1280, 720); } }