jellyfin/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs

129 lines
4.9 KiB
C#
Raw Normal View History

#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
public class AssParser : ISubtitleParser
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var trackInfo = new SubtitleTrackInfo();
2019-01-13 20:37:13 +00:00
var trackEvents = new List<SubtitleTrackEvent>();
var eventIndex = 1;
using (var reader = new StreamReader(stream))
{
string line;
while (reader.ReadLine() != "[Events]")
{
}
2020-06-15 21:43:52 +00:00
var headers = ParseFieldHeaders(reader.ReadLine());
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
2019-01-07 23:27:46 +00:00
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
2020-05-25 21:52:51 +00:00
if (line[0] == '[')
2020-05-25 21:52:51 +00:00
{
break;
2020-05-25 21:52:51 +00:00
}
var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
eventIndex++;
var sections = line.Substring(10).Split(',');
subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
RemoteNativeFormatting(subEvent);
subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
2019-01-07 23:27:46 +00:00
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
trackEvents.Add(subEvent);
}
}
2020-06-15 21:43:52 +00:00
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
private long GetTicks(ReadOnlySpan<char> time)
{
return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out var span)
? span.Ticks : 0;
}
private Dictionary<string, int> ParseFieldHeaders(string line)
{
var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
return new Dictionary<string, int>
{
{ "Start", fields.IndexOf("Start") },
{ "End", fields.IndexOf("End") },
{ "Text", fields.IndexOf("Text") }
};
}
private void RemoteNativeFormatting(SubtitleTrackEvent p)
{
int indexOfBegin = p.Text.IndexOf('{');
string pre = string.Empty;
while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin)
{
string s = p.Text.Substring(indexOfBegin);
if (s.StartsWith("{\\an1}", StringComparison.Ordinal) ||
s.StartsWith("{\\an2}", StringComparison.Ordinal) ||
s.StartsWith("{\\an3}", StringComparison.Ordinal) ||
s.StartsWith("{\\an4}", StringComparison.Ordinal) ||
s.StartsWith("{\\an5}", StringComparison.Ordinal) ||
s.StartsWith("{\\an6}", StringComparison.Ordinal) ||
s.StartsWith("{\\an7}", StringComparison.Ordinal) ||
s.StartsWith("{\\an8}", StringComparison.Ordinal) ||
s.StartsWith("{\\an9}", StringComparison.Ordinal))
{
pre = s.Substring(0, 6);
}
else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an2\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an3\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an4\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an5\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an6\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an7\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an8\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an9\\", StringComparison.Ordinal))
{
pre = s.Substring(0, 5) + "}";
}
2020-06-15 21:43:52 +00:00
int indexOfEnd = p.Text.IndexOf('}');
p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1);
indexOfBegin = p.Text.IndexOf('{');
}
2020-06-15 21:43:52 +00:00
p.Text = pre + p.Text;
}
}
}