SsaParser.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Threading;
  6. using MediaBrowser.Model.MediaInfo;
  7. namespace MediaBrowser.MediaEncoding.Subtitles
  8. {
  9. /// <summary>
  10. /// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
  11. /// </summary>
  12. public class SsaParser : ISubtitleParser
  13. {
  14. /// <inheritdoc />
  15. public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
  16. {
  17. var trackInfo = new SubtitleTrackInfo();
  18. var trackEvents = new List<SubtitleTrackEvent>();
  19. using (var reader = new StreamReader(stream))
  20. {
  21. bool eventsStarted = false;
  22. string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(',');
  23. int indexLayer = 0;
  24. int indexStart = 1;
  25. int indexEnd = 2;
  26. int indexStyle = 3;
  27. int indexName = 4;
  28. int indexEffect = 8;
  29. int indexText = 9;
  30. int lineNumber = 0;
  31. var header = new StringBuilder();
  32. string line;
  33. while ((line = reader.ReadLine()) != null)
  34. {
  35. cancellationToken.ThrowIfCancellationRequested();
  36. lineNumber++;
  37. if (!eventsStarted)
  38. {
  39. header.AppendLine(line);
  40. }
  41. if (string.Equals(line.Trim(), "[events]", StringComparison.OrdinalIgnoreCase))
  42. {
  43. eventsStarted = true;
  44. }
  45. else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";"))
  46. {
  47. // skip comment lines
  48. }
  49. else if (eventsStarted && line.Trim().Length > 0)
  50. {
  51. string s = line.Trim().ToLowerInvariant();
  52. if (s.StartsWith("format:"))
  53. {
  54. if (line.Length > 10)
  55. {
  56. format = line.ToLowerInvariant().Substring(8).Split(',');
  57. for (int i = 0; i < format.Length; i++)
  58. {
  59. if (string.Equals(format[i].Trim(), "layer", StringComparison.OrdinalIgnoreCase))
  60. {
  61. indexLayer = i;
  62. }
  63. else if (string.Equals(format[i].Trim(), "start", StringComparison.OrdinalIgnoreCase))
  64. {
  65. indexStart = i;
  66. }
  67. else if (string.Equals(format[i].Trim(), "end", StringComparison.OrdinalIgnoreCase))
  68. {
  69. indexEnd = i;
  70. }
  71. else if (string.Equals(format[i].Trim(), "text", StringComparison.OrdinalIgnoreCase))
  72. {
  73. indexText = i;
  74. }
  75. else if (string.Equals(format[i].Trim(), "effect", StringComparison.OrdinalIgnoreCase))
  76. {
  77. indexEffect = i;
  78. }
  79. else if (string.Equals(format[i].Trim(), "style", StringComparison.OrdinalIgnoreCase))
  80. {
  81. indexStyle = i;
  82. }
  83. }
  84. }
  85. }
  86. else if (!string.IsNullOrEmpty(s))
  87. {
  88. string text = string.Empty;
  89. string start = string.Empty;
  90. string end = string.Empty;
  91. string style = string.Empty;
  92. string layer = string.Empty;
  93. string effect = string.Empty;
  94. string name = string.Empty;
  95. string[] splittedLine;
  96. if (s.StartsWith("dialogue:"))
  97. {
  98. splittedLine = line.Substring(10).Split(',');
  99. }
  100. else
  101. {
  102. splittedLine = line.Split(',');
  103. }
  104. for (int i = 0; i < splittedLine.Length; i++)
  105. {
  106. if (i == indexStart)
  107. {
  108. start = splittedLine[i].Trim();
  109. }
  110. else if (i == indexEnd)
  111. {
  112. end = splittedLine[i].Trim();
  113. }
  114. else if (i == indexLayer)
  115. {
  116. layer = splittedLine[i];
  117. }
  118. else if (i == indexEffect)
  119. {
  120. effect = splittedLine[i];
  121. }
  122. else if (i == indexText)
  123. {
  124. text = splittedLine[i];
  125. }
  126. else if (i == indexStyle)
  127. {
  128. style = splittedLine[i];
  129. }
  130. else if (i == indexName)
  131. {
  132. name = splittedLine[i];
  133. }
  134. else if (i > indexText)
  135. {
  136. text += "," + splittedLine[i];
  137. }
  138. }
  139. try
  140. {
  141. var p = new SubtitleTrackEvent();
  142. p.StartPositionTicks = GetTimeCodeFromString(start);
  143. p.EndPositionTicks = GetTimeCodeFromString(end);
  144. p.Text = GetFormattedText(text);
  145. trackEvents.Add(p);
  146. }
  147. catch
  148. {
  149. }
  150. }
  151. }
  152. }
  153. // if (header.Length > 0)
  154. // subtitle.Header = header.ToString();
  155. // subtitle.Renumber(1);
  156. }
  157. trackInfo.TrackEvents = trackEvents.ToArray();
  158. return trackInfo;
  159. }
  160. private static long GetTimeCodeFromString(string time)
  161. {
  162. // h:mm:ss.cc
  163. string[] timeCode = time.Split(':', '.');
  164. return new TimeSpan(
  165. 0,
  166. int.Parse(timeCode[0]),
  167. int.Parse(timeCode[1]),
  168. int.Parse(timeCode[2]),
  169. int.Parse(timeCode[3]) * 10).Ticks;
  170. }
  171. private static string GetFormattedText(string text)
  172. {
  173. text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
  174. for (int i = 0; i < 10; i++) // just look ten times...
  175. {
  176. if (text.Contains(@"{\fn"))
  177. {
  178. int start = text.IndexOf(@"{\fn");
  179. int end = text.IndexOf('}', start);
  180. if (end > 0 && !text.Substring(start).StartsWith("{\\fn}"))
  181. {
  182. string fontName = text.Substring(start + 4, end - (start + 4));
  183. string extraTags = string.Empty;
  184. CheckAndAddSubTags(ref fontName, ref extraTags, out bool italic);
  185. text = text.Remove(start, end - start + 1);
  186. if (italic)
  187. {
  188. text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
  189. }
  190. else
  191. {
  192. text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
  193. }
  194. int indexOfEndTag = text.IndexOf("{\\fn}", start);
  195. if (indexOfEndTag > 0)
  196. {
  197. text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
  198. }
  199. else
  200. {
  201. text += "</font>";
  202. }
  203. }
  204. }
  205. if (text.Contains(@"{\fs"))
  206. {
  207. int start = text.IndexOf(@"{\fs");
  208. int end = text.IndexOf('}', start);
  209. if (end > 0 && !text.Substring(start).StartsWith("{\\fs}"))
  210. {
  211. string fontSize = text.Substring(start + 4, end - (start + 4));
  212. string extraTags = string.Empty;
  213. CheckAndAddSubTags(ref fontSize, ref extraTags, out bool italic);
  214. if (IsInteger(fontSize))
  215. {
  216. text = text.Remove(start, end - start + 1);
  217. if (italic)
  218. {
  219. text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
  220. }
  221. else
  222. {
  223. text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
  224. }
  225. int indexOfEndTag = text.IndexOf("{\\fs}", start);
  226. if (indexOfEndTag > 0)
  227. {
  228. text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
  229. }
  230. else
  231. {
  232. text += "</font>";
  233. }
  234. }
  235. }
  236. }
  237. if (text.Contains(@"{\c"))
  238. {
  239. int start = text.IndexOf(@"{\c");
  240. int end = text.IndexOf('}', start);
  241. if (end > 0 && !text.Substring(start).StartsWith("{\\c}"))
  242. {
  243. string color = text.Substring(start + 4, end - (start + 4));
  244. string extraTags = string.Empty;
  245. CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
  246. color = color.Replace("&", string.Empty).TrimStart('H');
  247. color = color.PadLeft(6, '0');
  248. // switch to rrggbb from bbggrr
  249. color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
  250. color = color.ToLowerInvariant();
  251. text = text.Remove(start, end - start + 1);
  252. if (italic)
  253. {
  254. text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
  255. }
  256. else
  257. {
  258. text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
  259. }
  260. int indexOfEndTag = text.IndexOf("{\\c}", start);
  261. if (indexOfEndTag > 0)
  262. {
  263. text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
  264. }
  265. else
  266. {
  267. text += "</font>";
  268. }
  269. }
  270. }
  271. if (text.Contains(@"{\1c")) // "1" specifices primary color
  272. {
  273. int start = text.IndexOf(@"{\1c");
  274. int end = text.IndexOf('}', start);
  275. if (end > 0 && !text.Substring(start).StartsWith("{\\1c}"))
  276. {
  277. string color = text.Substring(start + 5, end - (start + 5));
  278. string extraTags = string.Empty;
  279. CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
  280. color = color.Replace("&", string.Empty).TrimStart('H');
  281. color = color.PadLeft(6, '0');
  282. // switch to rrggbb from bbggrr
  283. color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
  284. color = color.ToLowerInvariant();
  285. text = text.Remove(start, end - start + 1);
  286. if (italic)
  287. {
  288. text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
  289. }
  290. else
  291. {
  292. text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
  293. }
  294. text += "</font>";
  295. }
  296. }
  297. }
  298. text = text.Replace(@"{\i1}", "<i>");
  299. text = text.Replace(@"{\i0}", "</i>");
  300. text = text.Replace(@"{\i}", "</i>");
  301. if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
  302. {
  303. text += "</i>";
  304. }
  305. text = text.Replace(@"{\u1}", "<u>");
  306. text = text.Replace(@"{\u0}", "</u>");
  307. text = text.Replace(@"{\u}", "</u>");
  308. if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
  309. {
  310. text += "</u>";
  311. }
  312. text = text.Replace(@"{\b1}", "<b>");
  313. text = text.Replace(@"{\b0}", "</b>");
  314. text = text.Replace(@"{\b}", "</b>");
  315. if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
  316. {
  317. text += "</b>";
  318. }
  319. return text;
  320. }
  321. private static bool IsInteger(string s)
  322. => int.TryParse(s, out _);
  323. private static int CountTagInText(string text, string tag)
  324. {
  325. int count = 0;
  326. int index = text.IndexOf(tag);
  327. while (index >= 0)
  328. {
  329. count++;
  330. if (index == text.Length)
  331. {
  332. return count;
  333. }
  334. index = text.IndexOf(tag, index + 1);
  335. }
  336. return count;
  337. }
  338. private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
  339. {
  340. italic = false;
  341. int indexOfSPlit = tagName.IndexOf(@"\");
  342. if (indexOfSPlit > 0)
  343. {
  344. string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
  345. tagName = tagName.Remove(indexOfSPlit);
  346. for (int i = 0; i < 10; i++)
  347. {
  348. if (rest.StartsWith("fs") && rest.Length > 2)
  349. {
  350. indexOfSPlit = rest.IndexOf(@"\");
  351. string fontSize = rest;
  352. if (indexOfSPlit > 0)
  353. {
  354. fontSize = rest.Substring(0, indexOfSPlit);
  355. rest = rest.Substring(indexOfSPlit).TrimStart('\\');
  356. }
  357. else
  358. {
  359. rest = string.Empty;
  360. }
  361. extraTags += " size=\"" + fontSize.Substring(2) + "\"";
  362. }
  363. else if (rest.StartsWith("fn") && rest.Length > 2)
  364. {
  365. indexOfSPlit = rest.IndexOf(@"\");
  366. string fontName = rest;
  367. if (indexOfSPlit > 0)
  368. {
  369. fontName = rest.Substring(0, indexOfSPlit);
  370. rest = rest.Substring(indexOfSPlit).TrimStart('\\');
  371. }
  372. else
  373. {
  374. rest = string.Empty;
  375. }
  376. extraTags += " face=\"" + fontName.Substring(2) + "\"";
  377. }
  378. else if (rest.StartsWith("c") && rest.Length > 2)
  379. {
  380. indexOfSPlit = rest.IndexOf(@"\");
  381. string fontColor = rest;
  382. if (indexOfSPlit > 0)
  383. {
  384. fontColor = rest.Substring(0, indexOfSPlit);
  385. rest = rest.Substring(indexOfSPlit).TrimStart('\\');
  386. }
  387. else
  388. {
  389. rest = string.Empty;
  390. }
  391. string color = fontColor.Substring(2);
  392. color = color.Replace("&", string.Empty).TrimStart('H');
  393. color = color.PadLeft(6, '0');
  394. // switch to rrggbb from bbggrr
  395. color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
  396. color = color.ToLowerInvariant();
  397. extraTags += " color=\"" + color + "\"";
  398. }
  399. else if (rest.StartsWith("i1") && rest.Length > 1)
  400. {
  401. indexOfSPlit = rest.IndexOf(@"\");
  402. italic = true;
  403. if (indexOfSPlit > 0)
  404. {
  405. rest = rest.Substring(indexOfSPlit).TrimStart('\\');
  406. }
  407. else
  408. {
  409. rest = string.Empty;
  410. }
  411. }
  412. else if (rest.Length > 0 && rest.Contains("\\"))
  413. {
  414. indexOfSPlit = rest.IndexOf(@"\");
  415. rest = rest.Substring(indexOfSPlit).TrimStart('\\');
  416. }
  417. }
  418. }
  419. }
  420. }
  421. }