Source: lib/text/web_vtt_generator.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.text.WebVttGenerator');
  7. goog.require('shaka.text.Cue');
  8. /**
  9. * @summary Manage the conversion to WebVTT.
  10. * @export
  11. */
  12. shaka.text.WebVttGenerator = class {
  13. /**
  14. * @param {!Array.<!shaka.text.Cue>} cues
  15. * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
  16. * @return {string}
  17. */
  18. static convert(cues, adCuePoints) {
  19. // Flatten nested cue payloads recursively. If a cue has nested cues,
  20. // their contents should be combined and replace the payload of the parent.
  21. const flattenPayload = (cue) => {
  22. // Handle styles (currently bold/italics/underline).
  23. // TODO: add support for color rendering.
  24. const openStyleTags = [];
  25. const bold = cue.fontWeight >= shaka.text.Cue.fontWeight.BOLD;
  26. const italics = cue.fontStyle == shaka.text.Cue.fontStyle.ITALIC;
  27. const underline = cue.textDecoration.includes(
  28. shaka.text.Cue.textDecoration.UNDERLINE);
  29. if (bold) {
  30. openStyleTags.push('b');
  31. }
  32. if (italics) {
  33. openStyleTags.push('i');
  34. }
  35. if (underline) {
  36. openStyleTags.push('u');
  37. }
  38. // Prefix opens tags, suffix closes tags in reverse order of opening.
  39. const prefixStyleTags = openStyleTags.reduce((acc, tag) => {
  40. return `${acc}<${tag}>`;
  41. }, '');
  42. const suffixStyleTags = openStyleTags.reduceRight((acc, tag) => {
  43. return `${acc}</${tag}>`;
  44. }, '');
  45. if (cue.lineBreak) {
  46. // This is a vertical lineBreak, so insert a newline.
  47. return '\n';
  48. } else if (cue.nestedCues.length) {
  49. return cue.nestedCues.map(flattenPayload).join('');
  50. } else {
  51. // This is a real cue.
  52. return prefixStyleTags + cue.payload + suffixStyleTags;
  53. }
  54. };
  55. const webvttTimeString = (time) => {
  56. let newTime = time;
  57. for (const adCuePoint of adCuePoints) {
  58. if (adCuePoint.end && adCuePoint.start < time) {
  59. const offset = adCuePoint.end - adCuePoint.start;
  60. newTime += offset;
  61. }
  62. }
  63. const hours = Math.floor(newTime / 3600);
  64. const minutes = Math.floor(newTime / 60 % 60);
  65. const seconds = Math.floor(newTime % 60);
  66. const milliseconds = Math.floor(newTime * 1000 % 1000);
  67. return (hours < 10 ? '0' : '') + hours + ':' +
  68. (minutes < 10 ? '0' : '') + minutes + ':' +
  69. (seconds < 10 ? '0' : '') + seconds + '.' +
  70. (milliseconds < 100 ? (milliseconds < 10 ? '00' : '0') : '') +
  71. milliseconds;
  72. };
  73. // We don't want to modify the array or objects passed in, since we don't
  74. // technically own them. So we build a new array and replace certain items
  75. // in it if they need to be flattened.
  76. const flattenedCues = cues.map((cue) => {
  77. if (cue.nestedCues.length) {
  78. const flatCue = cue.clone();
  79. flatCue.nestedCues = [];
  80. flatCue.payload = flattenPayload(cue);
  81. return flatCue;
  82. } else {
  83. return cue;
  84. }
  85. });
  86. let webvttString = 'WEBVTT\n\n';
  87. for (const cue of flattenedCues) {
  88. const webvttSettings = (cue) => {
  89. const settings = [];
  90. const Cue = shaka.text.Cue;
  91. switch (cue.textAlign) {
  92. case Cue.textAlign.LEFT:
  93. settings.push('align:left');
  94. break;
  95. case Cue.textAlign.RIGHT:
  96. settings.push('align:right');
  97. break;
  98. case Cue.textAlign.CENTER:
  99. settings.push('align:middle');
  100. break;
  101. case Cue.textAlign.START:
  102. settings.push('align:start');
  103. break;
  104. case Cue.textAlign.END:
  105. settings.push('align:end');
  106. break;
  107. }
  108. switch (cue.writingMode) {
  109. case Cue.writingMode.VERTICAL_LEFT_TO_RIGHT:
  110. settings.push('vertical:lr');
  111. break;
  112. case Cue.writingMode.VERTICAL_RIGHT_TO_LEFT:
  113. settings.push('vertical:rl');
  114. break;
  115. }
  116. if (settings.length) {
  117. return ' ' + settings.join(' ');
  118. }
  119. return '';
  120. };
  121. webvttString += webvttTimeString(cue.startTime) + ' --> ' +
  122. webvttTimeString(cue.endTime) + webvttSettings(cue) + '\n';
  123. webvttString += cue.payload + '\n\n';
  124. }
  125. return webvttString;
  126. }
  127. };