SubtitleEditParser.cs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using Jellyfin.Extensions;
  8. using MediaBrowser.Model.MediaInfo;
  9. using Microsoft.Extensions.Logging;
  10. using Nikse.SubtitleEdit.Core.Common;
  11. using Nikse.SubtitleEdit.Core.SubtitleFormats;
  12. using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;
  13. namespace MediaBrowser.MediaEncoding.Subtitles
  14. {
  15. /// <summary>
  16. /// SubStation Alpha subtitle parser.
  17. /// </summary>
  18. public class SubtitleEditParser : ISubtitleParser
  19. {
  20. private readonly ILogger<SubtitleEditParser> _logger;
  21. private readonly Dictionary<string, SubtitleFormat[]> _subtitleFormats;
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="SubtitleEditParser"/> class.
  24. /// </summary>
  25. /// <param name="logger">The logger.</param>
  26. public SubtitleEditParser(ILogger<SubtitleEditParser> logger)
  27. {
  28. _logger = logger;
  29. _subtitleFormats = GetSubtitleFormats()
  30. .Where(subtitleFormat => !string.IsNullOrEmpty(subtitleFormat.Extension))
  31. .GroupBy(subtitleFormat => subtitleFormat.Extension.TrimStart('.'), StringComparer.OrdinalIgnoreCase)
  32. .ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
  33. }
  34. /// <inheritdoc />
  35. public SubtitleTrackInfo Parse(Stream stream, string fileExtension)
  36. {
  37. var subtitle = new Subtitle();
  38. var lines = stream.ReadAllLines().ToList();
  39. if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats))
  40. {
  41. throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension));
  42. }
  43. foreach (var subtitleFormat in subtitleFormats)
  44. {
  45. _logger.LogDebug(
  46. "Trying to parse '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
  47. fileExtension,
  48. subtitleFormat.Name);
  49. subtitleFormat.LoadSubtitle(subtitle, lines, fileExtension);
  50. if (subtitleFormat.ErrorCount == 0)
  51. {
  52. break;
  53. }
  54. _logger.LogError(
  55. "{ErrorCount} errors encountered while parsing '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
  56. subtitleFormat.ErrorCount,
  57. fileExtension,
  58. subtitleFormat.Name);
  59. }
  60. if (subtitle.Paragraphs.Count == 0)
  61. {
  62. throw new ArgumentException("Unsupported format: " + fileExtension);
  63. }
  64. var trackInfo = new SubtitleTrackInfo();
  65. int len = subtitle.Paragraphs.Count;
  66. var trackEvents = new SubtitleTrackEvent[len];
  67. for (int i = 0; i < len; i++)
  68. {
  69. var p = subtitle.Paragraphs[i];
  70. trackEvents[i] = new SubtitleTrackEvent(p.Number.ToString(CultureInfo.InvariantCulture), p.Text)
  71. {
  72. StartPositionTicks = p.StartTime.TimeSpan.Ticks,
  73. EndPositionTicks = p.EndTime.TimeSpan.Ticks
  74. };
  75. }
  76. trackInfo.TrackEvents = trackEvents;
  77. return trackInfo;
  78. }
  79. /// <inheritdoc />
  80. public bool SupportsFileExtension(string fileExtension)
  81. => _subtitleFormats.ContainsKey(fileExtension);
  82. private IEnumerable<SubtitleFormat> GetSubtitleFormats()
  83. {
  84. var subtitleFormats = new List<SubtitleFormat>();
  85. var assembly = Assembly.GetAssembly(typeof(SubtitleFormat));
  86. if (assembly == null)
  87. {
  88. _logger.LogError("Missing assembly containing {SubtitleFormatName}", nameof(SubtitleFormat));
  89. return GetFallbackSubtitleFormats();
  90. }
  91. foreach (var type in assembly.GetTypes())
  92. {
  93. if (!type.IsSubclassOf(typeof(SubtitleFormat)) || type.IsAbstract)
  94. {
  95. continue;
  96. }
  97. try
  98. {
  99. // It shouldn't be null, but the exception is caught if it is
  100. var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(type, true)!;
  101. subtitleFormats.Add(subtitleFormat);
  102. }
  103. catch (Exception ex)
  104. {
  105. _logger.LogWarning(ex, "Failed to create instance of the subtitle format {SubtitleFormatType}", type.Name);
  106. }
  107. }
  108. return subtitleFormats;
  109. }
  110. private static IEnumerable<SubtitleFormat> GetFallbackSubtitleFormats()
  111. => new SubtitleFormat[]
  112. {
  113. // Preferred and likely more common formats
  114. new SubRip(),
  115. new WebVTT(),
  116. new WebVTTFileWithLineNumber(),
  117. new AdvancedSubStationAlpha(),
  118. new SubStationAlpha(),
  119. new Sami(),
  120. new SamiAvDicPlayer(),
  121. new SamiModern(),
  122. new SamiYouTube(),
  123. new DvdSubtitle(),
  124. new DvdSubtitleSystem(),
  125. new JsonAeneas(),
  126. new JsonTed(),
  127. new Json(),
  128. new JsonType2(),
  129. new JsonType3(),
  130. new JsonType4(),
  131. new JsonType5(),
  132. new JsonType6(),
  133. new JsonType7(),
  134. new JsonType8(),
  135. new JsonType8b(),
  136. new JsonType9(),
  137. new JsonType10(),
  138. new JsonType11(),
  139. new JsonType12(),
  140. new JsonType13(),
  141. new JsonType14(),
  142. new JsonType15(),
  143. new JsonType16(),
  144. new JsonType17(),
  145. new JsonType18(),
  146. new JsonType19(),
  147. new JsonType20(),
  148. new ItunesTimedText(),
  149. new FLVCoreCuePoints(),
  150. new Csv(),
  151. new Csv2(),
  152. new Csv3(),
  153. new Csv4(),
  154. new Csv5(),
  155. new Ebu(),
  156. new NetflixImsc11Japanese(),
  157. new NetflixTimedText(),
  158. new QuickTimeText(),
  159. new RealTime(),
  160. new SmpteTt2052()
  161. };
  162. }
  163. }