Source: lib/text/lrc_text_parser.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.text.LrcTextParser');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.log');
  9. goog.require('shaka.text.Cue');
  10. goog.require('shaka.text.TextEngine');
  11. goog.require('shaka.util.StringUtils');
  12. /**
  13. * LRC file format: https://en.wikipedia.org/wiki/LRC_(file_format)
  14. *
  15. * @implements {shaka.extern.TextParser}
  16. * @export
  17. */
  18. shaka.text.LrcTextParser = class {
  19. /**
  20. * @override
  21. * @export
  22. */
  23. parseInit(data) {
  24. goog.asserts.assert(false, 'LRC does not have init segments');
  25. }
  26. /**
  27. * @override
  28. * @export
  29. */
  30. setSequenceMode(sequenceMode) {
  31. // Unused.
  32. }
  33. /**
  34. * @override
  35. * @export
  36. */
  37. setManifestType(manifestType) {
  38. // Unused.
  39. }
  40. /**
  41. * @override
  42. * @export
  43. */
  44. parseMedia(data, time) {
  45. const StringUtils = shaka.util.StringUtils;
  46. const LrcTextParser = shaka.text.LrcTextParser;
  47. // Get the input as a string.
  48. const str = StringUtils.fromUTF8(data);
  49. /** @type {shaka.text.Cue} */
  50. let prevCue = null;
  51. /** @type {!Array<!shaka.text.Cue>} */
  52. const cues = [];
  53. const lines = str.split(/\r?\n/);
  54. for (const line of lines) {
  55. if (!line || /^\s+$/.test(line)) {
  56. continue;
  57. }
  58. // LRC content
  59. const match = LrcTextParser.lyricLine_.exec(line);
  60. if (match) {
  61. const startTime = LrcTextParser.parseTime_(match[1]);
  62. // This time can be overwritten by a subsequent cue.
  63. // By default we add 2 seconds of duration.
  64. const endTime = time.segmentEnd ? time.segmentEnd : startTime + 2;
  65. const payload = match[2];
  66. const cue = new shaka.text.Cue(startTime, endTime, payload);
  67. // Update previous
  68. if (prevCue) {
  69. prevCue.endTime = startTime;
  70. cues.push(prevCue);
  71. }
  72. prevCue = cue;
  73. continue;
  74. }
  75. shaka.log.warning('LrcTextParser encountered an unknown line.', line);
  76. }
  77. if (prevCue) {
  78. cues.push(prevCue);
  79. }
  80. return cues;
  81. }
  82. /**
  83. * Parses a LRC time from the given parser.
  84. *
  85. * @param {string} string
  86. * @return {number}
  87. * @private
  88. */
  89. static parseTime_(string) {
  90. const LrcTextParser = shaka.text.LrcTextParser;
  91. const match = LrcTextParser.timeFormat_.exec(string);
  92. const minutes = parseInt(match[1], 10);
  93. const seconds = parseFloat(match[2].replace(',', '.'));
  94. return minutes * 60 + seconds;
  95. }
  96. };
  97. /**
  98. * @const
  99. * @private {!RegExp}
  100. * @example [00:12.0]Text or [00:12.00]Text or [00:12.000]Text or
  101. * [00:12,0]Text or [00:12,00]Text or [00:12,000]Text
  102. */
  103. shaka.text.LrcTextParser.lyricLine_ =
  104. /^\[(\d{1,2}:\d{1,2}(?:[.,]\d{1,3})?)\](.*)/;
  105. /**
  106. * @const
  107. * @private {!RegExp}
  108. * @example 00:12.0 or 00:12.00 or 00:12.000 or
  109. * 00:12,0 or 00:12,00 or 00:12,000
  110. */
  111. shaka.text.LrcTextParser.timeFormat_ =
  112. /^(\d+):(\d{1,2}(?:[.,]\d{1,3})?)$/;
  113. shaka.text.TextEngine.registerParser(
  114. 'application/x-subtitle-lrc', () => new shaka.text.LrcTextParser());