using System.Collections.Generic;
using System.Threading.Tasks;
public abstract record Video(string Title, VideoMedia Media)
private record VideoImpl(string Title, VideoMedia Media) : Video(Title, Media);
public static Video Create(string title, VideoMedia media) => new VideoImpl(
string.IsNullOrWhiteSpace(title) ? throw new ArgumentException("Title must be non-empty", nameof(title)) : title,
public record VideoMedia(byte[] Content, TimeSpan Duration)
public static VideoMedia CreateEmpty(TimeSpan duration) => new(Array.Empty<byte>(), duration);
public static VideoMedia Create(byte[] content, TimeSpan duration) => new(content, duration);
public static class VideoMediaLoading
public static async Task<VideoMedia> Populate(this VideoMedia media, Func<Task<byte[]>> load) => media switch
{ Content: [] } empty => empty with { Content = await load() },
static class VideoLoading
public static async Task<Video> Populate(Video video, string file) =>
video with { Media = await video.Media.Populate(async () => await File.ReadAllBytesAsync(file)) };
public record VideoClip(Video Video, TimeInterval Interval)
public static VideoClip CreateClipped(Video video, TimeInterval interval) => new(video, interval);
public static VideoClip CreateEntire(Video video) => new(video, TimeInterval.Create(TimeSpan.Zero, video.Media.Duration));
public abstract record TimeInterval(TimeSpan Offset, TimeSpan Duration)
private record TimeIntervalImpl(TimeSpan Offset, TimeSpan Duration) : TimeInterval(Offset, Duration);
public static TimeInterval FromDuration(TimeSpan duration) => Create(TimeSpan.Zero, duration);
public static TimeInterval Create(TimeSpan offset, TimeSpan duration) => new TimeIntervalImpl(
? throw new ArgumentException("Offset must be non-negative", nameof(offset))
_ when duration < TimeSpan.Zero =>
throw new ArgumentException("Duration must be non-negative", nameof(duration)),
_ when duration > TimeSpan.MaxValue - offset =>
throw new ArgumentException("Duration is too long", nameof(duration)),
public static class Clipping
public static TimeInterval Clip(this TimeInterval interval, TimeInterval clip) => interval switch
_ when interval.Duration <= clip.Offset =>
TimeInterval.Create(interval.Offset + interval.Duration, TimeSpan.Zero),
_ when interval.Duration <= clip.Offset + clip.Duration =>
TimeInterval.Create(interval.Offset + clip.Offset, interval.Duration),
_ => TimeInterval.Create(interval.Offset + clip.Offset, clip.Duration),
public static VideoClip Clip(this Video video, TimeInterval clip) =>
VideoClip.CreateClipped(video, TimeInterval.FromDuration(video.Media.Duration).Clip(clip));
public static class TimeIntervals
public static IEnumerable<TimeInterval> CreateMany(TimeSpan offset, TimeSpan duration, TimeSpan step)
TimeSpan current = offset;
while (current <= TimeSpan.MaxValue - duration)
yield return TimeInterval.Create(current, duration);
if (current > TimeSpan.MaxValue - step) yield break;