Editor/Inference/CaptureTimeline.cs
using System;

namespace HumanoidMocapX.Inference;

/// <summary>A source-time interval sampled at FlowHMR's fixed 30 fps. The end is exclusive.</summary>
internal readonly struct CaptureTimeline
{
    internal const double TimestampTolerance = 0.0000001;
    internal double Start { get; }
    internal double End { get; }
    internal int Count { get; }

    internal CaptureTimeline(double duration, double start, double? end)
    {
        if (!double.IsFinite(duration) || duration <= 0 || !double.IsFinite(start) || start < 0 || start >= duration
            || end is { } stop && (!double.IsFinite(stop) || stop <= start || stop > duration + TimestampTolerance))
            throw new ArgumentException("Choose a nonempty capture range within the video's duration.");
        Start = start; End = Math.Min(end ?? duration, duration);
        var count = Math.Ceiling((End - Start) * 30 - TimestampTolerance);
        if (count < 1 || count > CaptureOptions.MaximumFrames)
            throw new ArgumentException("Choose a capture range of at most 5,000 frames (166.66 seconds at 30 fps).");
        Count = (int)count;
    }

    internal double Time(int frame)
    {
        if ((uint)frame >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(frame));
        return Start + frame / 30d;
    }
}