📄
src/Infrastructure/Ffmpeg/SubtitleReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using FFMpegCore; using FFMpegCore.Pipes; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Slopper.Domain; using SubtitlesParserV2; namespace Slopper.Infrastructure.Ffmpeg; internal sealed class SubtitleReader(ILogger<SubtitleReader> logger, IMemoryCache cache) : ISubtitleReader { public async Task<IReadOnlyList<SubtitleEntry>> ReadSubtitles( MediaItem media, CancellationToken cancellationToken ) => await ReadSubtitlesInternal(media); public async Task<IReadOnlyList<SubtitleEntry>> ReadSubtitles( MediaItem media, TimeSpan start, TimeSpan duration, CancellationToken cancellationToken ) { var all = await ReadSubtitlesInternal(media); var first = Array.FindIndex(all, s => start <= s.Start); var end = start + duration; var last = Array.FindIndex(all, s => end <= s.Start + s.Duration); if (last < first) { return []; } return new ArraySegment<SubtitleEntry>(all, first, last - first + 1); } private async Task<SubtitleEntry[]> ReadSubtitlesInternal(MediaItem media) => ( await cache.GetOrCreateAsync( media, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15); return 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).ForceFormat("srt") ); using (Tracing.StartReadSubtitles(path, index)) { 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) )), ]; } }