From 4860c3121ae37309f9d0c701cc2042651f867d7a Mon Sep 17 00:00:00 2001 From: Hellbringer Date: Mon, 15 Jun 2026 00:40:11 +0400 Subject: [PATCH] Cache Matroska subtitle tracks --- .../ContainerFormats/Matroska/MatroskaFile.cs | 88 ++++++++++++++----- .../PickMatroskaTrackViewModel.cs | 12 ++- .../EmbedTrackPreviewViewModel.cs | 7 +- 3 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/libse/ContainerFormats/Matroska/MatroskaFile.cs b/src/libse/ContainerFormats/Matroska/MatroskaFile.cs index 180dc1dd70..99d562d563 100644 --- a/src/libse/ContainerFormats/Matroska/MatroskaFile.cs +++ b/src/libse/ContainerFormats/Matroska/MatroskaFile.cs @@ -18,8 +18,9 @@ public sealed class MatroskaFile : IDisposable private double _frameRate; private string _videoCodecId; - private int _subtitleRipTrackNumber; - private readonly List _subtitleRip = new List(); + private readonly Dictionary> _subtitleRipByTrackNumber = new Dictionary>(); + private HashSet _subtitleTrackNumbers; + private bool _subtitleRipLoaded; private List _tracks; private List _chapters; @@ -558,11 +559,7 @@ private void ReadCluster(Element clusterElement) ReadBlockGroupElement(element, clusterTimeCode); break; case ElementId.SimpleBlock: - var subtitle = ReadSubtitleBlock(element, clusterTimeCode); - if (subtitle != null) - { - _subtitleRip.Add(subtitle); - } + AddSubtitleBlock(ReadSubtitleBlock(element, clusterTimeCode)); break; default: _stream.Seek(element.DataSize, SeekOrigin.Current); @@ -581,12 +578,9 @@ private void ReadBlockGroupElement(Element clusterElement, long clusterTimeCode) switch (element.Id) { case ElementId.Block: - subtitle = ReadSubtitleBlock(element, clusterTimeCode); - if (subtitle == null) - { - return; - } - _subtitleRip.Add(subtitle); + var subtitleBlock = ReadSubtitleBlock(element, clusterTimeCode); + subtitle = subtitleBlock?.Subtitle; + AddSubtitleBlock(subtitleBlock); break; case ElementId.BlockDuration: var duration = ReadUIntAsLong(element.DataSize); @@ -602,10 +596,10 @@ private void ReadBlockGroupElement(Element clusterElement, long clusterTimeCode) } } - private MatroskaSubtitle ReadSubtitleBlock(Element blockElement, long clusterTimeCode) + private MatroskaSubtitleBlock ReadSubtitleBlock(Element blockElement, long clusterTimeCode) { var trackNumber = (int)ReadVariableLengthUInt(); - if (trackNumber != _subtitleRipTrackNumber) + if (!IsSubtitleTrackNumber(trackNumber)) { _stream.Seek(blockElement.EndPosition, SeekOrigin.Begin); return null; @@ -644,22 +638,68 @@ private MatroskaSubtitle ReadSubtitleBlock(Element blockElement, long clusterTim var data = new byte[dataLength]; _stream.Read(data, 0, dataLength); - return new MatroskaSubtitle(data, (long)Math.Round(GetTimeScaledToMilliseconds(clusterTimeCode + timeCode))); + var subtitle = new MatroskaSubtitle(data, (long)Math.Round(GetTimeScaledToMilliseconds(clusterTimeCode + timeCode))); + return new MatroskaSubtitleBlock(trackNumber, subtitle); } public List GetSubtitle(int trackNumber, LoadMatroskaCallback progressCallback) { - // Cache: GetSubtitle is called multiple times per UI flow (preview, OCR). - // Reuse the previously parsed list when the track matches. - if (_subtitleRipTrackNumber == trackNumber && _subtitleRip.Count > 0) + if (!_subtitleRipLoaded) { - return _subtitleRip; + EnsureSubtitleTrackNumbers(); + _subtitleRipByTrackNumber.Clear(); + ReadSegmentCluster(progressCallback); + _subtitleRipLoaded = true; } - _subtitleRip.Clear(); - _subtitleRipTrackNumber = trackNumber; - ReadSegmentCluster(progressCallback); - return _subtitleRip; + return _subtitleRipByTrackNumber.TryGetValue(trackNumber, out var subtitles) + ? subtitles + : new List(); + } + + private bool IsSubtitleTrackNumber(int trackNumber) + { + EnsureSubtitleTrackNumbers(); + return _subtitleTrackNumbers.Contains(trackNumber); + } + + private void EnsureSubtitleTrackNumbers() + { + if (_subtitleTrackNumbers == null) + { + ReadSegmentInfoAndTracks(); + _subtitleTrackNumbers = new HashSet( + _tracks?.Where(p => p.IsSubtitle).Select(p => p.TrackNumber) ?? Enumerable.Empty()); + } + } + + private void AddSubtitleBlock(MatroskaSubtitleBlock subtitleBlock) + { + if (subtitleBlock == null) + { + return; + } + + if (!_subtitleRipByTrackNumber.TryGetValue(subtitleBlock.TrackNumber, out var subtitles)) + { + subtitles = new List(); + _subtitleRipByTrackNumber.Add(subtitleBlock.TrackNumber, subtitles); + } + + subtitles.Add(subtitleBlock.Subtitle); + } + + private sealed class MatroskaSubtitleBlock + { + public MatroskaSubtitleBlock(int trackNumber, MatroskaSubtitle subtitle) + { + TrackNumber = trackNumber; + Subtitle = subtitle; + } + + public int TrackNumber { get; } + + public MatroskaSubtitle Subtitle { get; } } public void Dispose() => Dispose(true); diff --git a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs index f9a335ac0e..0e4cdaf808 100644 --- a/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs +++ b/src/ui/Features/Shared/PickMatroskaTrack/PickMatroskaTrackViewModel.cs @@ -154,9 +154,8 @@ private async Task Export() else if (trackInfo.CodecId == MatroskaTrackType.TextSt && subtitles != null && _matroskaFile != null) { var subtitle = new Subtitle(); - var sub = _matroskaFile.GetSubtitle(trackInfo.TrackNumber, null); - Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, sub, subtitle); - Utilities.ParseMatroskaTextSt(trackInfo, sub, subtitle); + Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, subtitles, subtitle); + Utilities.ParseMatroskaTextSt(trackInfo, subtitles, subtitle); await WriteTextSubtitleFile(Window, trackInfo, subtitles, new SubRip()); } else @@ -261,9 +260,8 @@ private bool TrackChanged() else if (trackInfo.CodecId == MatroskaTrackType.TextSt && subtitles != null && _matroskaFile != null) { var subtitle = new Subtitle(); - var sub = _matroskaFile.GetSubtitle(trackInfo.TrackNumber, null); - Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, sub, subtitle); - Utilities.ParseMatroskaTextSt(trackInfo, sub, subtitle); + Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, subtitles, subtitle); + Utilities.ParseMatroskaTextSt(trackInfo, subtitles, subtitle); for (var i = 0; i < 20 && i < subtitle.Paragraphs.Count; i++) { @@ -315,4 +313,4 @@ internal void SelectAndScrollToRow(int index) TrackChanged(); }, DispatcherPriority.Background); } -} \ No newline at end of file +} diff --git a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewViewModel.cs b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewViewModel.cs index 7d211657c3..7813bd484f 100644 --- a/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewViewModel.cs +++ b/src/ui/Features/Video/EmbeddedSubtitlesEdit/EmbedTrackPreviewViewModel.cs @@ -167,9 +167,8 @@ private bool InitTrack() else if (trackInfo.CodecId == MatroskaTrackType.TextSt && subtitles != null && _matroskaFile != null) { var subtitle = new Subtitle(); - var sub = _matroskaFile.GetSubtitle(trackInfo.TrackNumber, null); - Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, sub, subtitle); - Utilities.ParseMatroskaTextSt(trackInfo, sub, subtitle); + Utilities.LoadMatroskaTextSubtitle(trackInfo, _matroskaFile, subtitles, subtitle); + Utilities.ParseMatroskaTextSt(trackInfo, subtitles, subtitle); for (var i = 0; i < 20 && i < subtitle.Paragraphs.Count; i++) { @@ -211,4 +210,4 @@ internal void Loaded() { InitTrack(); } -} \ No newline at end of file +}