📄 src/Infrastructure/Ffmpeg/SubtitleReader.cs
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore;
using FFMpegCore.Enums;
using FFMpegCore.Pipes;
using Microsoft.Extensions.Logging;
using Slopper.Domain;
using SubtitlesParserV2;

namespace Slopper.Infrastructure.Ffmpeg;

internal sealed class SubtitleReader(ILogger<SubtitleReader> logger) : ISubtitleReader
{
    public async Task<SubtitleEntry[]> ReadSubtitles(MediaItem media, CancellationToken cancellationToken) =>
        media.Subtitles switch
        {
            Subtitles.External(var path) => ReadSubtitles(path),
            Subtitles.Embedded(var index) => await ReadSubtitles(media.Path, index),
            _ => throw new ArgumentException("Unknown subtitle type"),
        };

    private static SubtitleEntry[] ReadSubtitles(string path)
    {
        using var stream = File.OpenRead(path);
        return ReadSubtitles(stream);
    }

    private async Task<SubtitleEntry[]> ReadSubtitles(string path, int index)
    {
        using var stream = new MemoryStream();
        var args = FFMpegArguments
            .FromFileInput(path)
            .OutputToPipe(
                new StreamPipeSink(stream),
                addArguments: options => options.SelectStream(index, channel: Channel.Subtitle).ForceFormat("srt")
            );
        logger.LogInformation("Running ffmpeg {FfmpegArguments}", args.Arguments);
        await args.ProcessAsynchronously();
        stream.Position = 0;
        return ReadSubtitles(stream);
    }

    private static SubtitleEntry[] ReadSubtitles(Stream stream)
    {
        var result = SubtitleParser.ParseStream(stream) ?? throw new Exception("Cannot parse subtitles.");
        return
        [
            .. result.Subtitles.Select(s => new SubtitleEntry(
                [.. s.Lines],
                TimeSpan.FromMilliseconds(s.StartTime),
                TimeSpan.FromMilliseconds(s.EndTime - s.StartTime)
            )),
        ];
    }
}