mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
afd24224ce
- Full SRT → WebVTT conversion now supports timestamps, alignment,
position, and basic styles (bold, italic, underline).
- Added proper handling for <font color="..."> tags:
- Converts known colors to WebVTT <c.color> classes.
- Safely removes unknown colors without leaving orphaned </c> tags.
- Supports Aegisub cues ({\anX} → line & align, {\pos(x,y)} → position &
line).
- Normalizes timestamps (MM:SS,mmm → 00:MM:SS.mmm) for WebVTT
compliance.
224 lines
5.3 KiB
JavaScript
224 lines
5.3 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.text.SrtTextParser');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.text.TextEngine');
|
|
goog.require('shaka.text.Utils');
|
|
goog.require('shaka.text.VttTextParser');
|
|
goog.require('shaka.util.BufferUtils');
|
|
goog.require('shaka.util.StringUtils');
|
|
|
|
|
|
/**
|
|
* @implements {shaka.extern.TextParser}
|
|
* @export
|
|
*/
|
|
shaka.text.SrtTextParser = class {
|
|
constructor() {
|
|
/**
|
|
* @type {!shaka.extern.TextParser}
|
|
* @private
|
|
*/
|
|
this.parser_ = new shaka.text.VttTextParser();
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @export
|
|
*/
|
|
parseInit(data) {
|
|
goog.asserts.assert(false, 'SRT does not have init segments');
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @export
|
|
*/
|
|
setManifestType(manifestType) {
|
|
// Unused.
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @export
|
|
*/
|
|
parseMedia(data, time, uri) {
|
|
const BufferUtils = shaka.util.BufferUtils;
|
|
const StringUtils = shaka.util.StringUtils;
|
|
|
|
// Get the input as a string.
|
|
const str = StringUtils.fromUTF8(data);
|
|
|
|
const vvtText = this.srt2webvtt_(str);
|
|
|
|
const newData = BufferUtils.toUint8(StringUtils.toUTF8(vvtText));
|
|
|
|
return this.parser_.parseMedia(newData, time, uri, /* images= */ []);
|
|
}
|
|
|
|
/**
|
|
* Convert a SRT format to WebVTT
|
|
*
|
|
* @param {string} data
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
srt2webvtt_(data) {
|
|
let result = 'WEBVTT\n\n';
|
|
|
|
// Supports no cues
|
|
if (data == '') {
|
|
return result;
|
|
}
|
|
|
|
// remove dos newlines
|
|
let srt = data.replace(/\r+/g, '');
|
|
// trim white space start and end
|
|
srt = srt.trim();
|
|
|
|
// get cues
|
|
const cuelist = srt.split('\n\n');
|
|
for (const cue of cuelist) {
|
|
result += this.convertSrtCue_(cue);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convert a single SRT cue into a WebVTT cue
|
|
* Handles: timestamps, alignment, position, styles, colors.
|
|
*
|
|
* @param {string} caption
|
|
* @return {string} WebVTT cue
|
|
* @private
|
|
*/
|
|
convertSrtCue_(caption) {
|
|
// Split cue into non-empty trimmed lines
|
|
const lines = caption.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
if (lines.length < 2) {
|
|
return '';
|
|
}
|
|
|
|
// 1. Remove numeric ID if present
|
|
if (/^\d+$/.test(lines[0])) {
|
|
lines.shift();
|
|
}
|
|
|
|
if (lines.length < 2) {
|
|
return '';
|
|
}
|
|
|
|
// 2. Parse time line (start --> end [settings])
|
|
const timeRegex = /^([\d:,]+)\s*-->\s*([\d:,]+)(.*)?$/;
|
|
const match = lines[0].match(timeRegex);
|
|
if (!match) {
|
|
return '';
|
|
}
|
|
|
|
const start = this.normalizeTime_(match[1]);
|
|
const end = this.normalizeTime_(match[2]);
|
|
let settings = '';
|
|
|
|
// 3. Combine remaining lines as cue text
|
|
let text = lines.slice(1).join('\n');
|
|
|
|
// 4. Aegisub alignment {\anX} → WebVTT line & align settings
|
|
const alignMatch = text.match(/{\\an(\d)}/);
|
|
if (alignMatch) {
|
|
const map = {
|
|
1: 'line:-1 align:left',
|
|
2: 'line:-1 align:center',
|
|
3: 'line:-1 align:right',
|
|
7: 'line:0 align:left',
|
|
8: 'line:0 align:center',
|
|
9: 'line:0 align:right',
|
|
};
|
|
settings += map[alignMatch[1]] ? ` ${map[alignMatch[1]]}` : '';
|
|
}
|
|
|
|
// 5. Aegisub position {\pos(x,y)} → WebVTT position & line
|
|
const posMatch = text.match(/{\\pos\((\d+),(\d+)\)}/);
|
|
if (posMatch) {
|
|
// Convert coordinates to percentages (approximation)
|
|
const x = Math.min(100, Math.round(parseFloat(posMatch[1]) / 19.2));
|
|
const y = Math.min(100, Math.round(parseFloat(posMatch[2]) / 10.8));
|
|
settings += ` position:${x}% line:${y}%`;
|
|
}
|
|
|
|
// 6. Remove all remaining Aegisub/unsupported tags
|
|
text = text.replace(/{\\.*?}/g, '');
|
|
|
|
// 7. Convert basic SRT style tags {b}{/b}, {i}{/i}, {u}{/u} → HTML
|
|
text = text
|
|
.replace(/{b}/gi, '<b>')
|
|
.replace(/{\/b}/gi, '</b>')
|
|
.replace(/{i}/gi, '<i>')
|
|
.replace(/{\/i}/gi, '</i>')
|
|
.replace(/{u}/gi, '<u>')
|
|
.replace(/{\/u}/gi, '</u>');
|
|
|
|
// 8. Convert <font color="#XXXXXX"> → <c.colorName> (WebVTT spec)
|
|
text = this.convertColors_(text);
|
|
|
|
// 9. Return formatted WebVTT cue
|
|
return `${start} --> ${end}${settings}\n${text}\n\n`;
|
|
}
|
|
|
|
/**
|
|
* Normalize timestamp for WebVTT
|
|
* Supports MM:SS,mmm → 00:MM:SS.mmm
|
|
*
|
|
* @param {string} time
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
normalizeTime_(time) {
|
|
if (/^\d{2}:\d{2},\d{3}$/.test(time)) {
|
|
return '00:' + time.replace(',', '.');
|
|
}
|
|
return time.replace(',', '.');
|
|
}
|
|
|
|
/**
|
|
* Convert SRT <font color="#XXXXXX"> or <font color="name"> tags
|
|
* into WebVTT <c.colorName>. Unknown colors are removed safely.
|
|
*
|
|
* @param {string} text
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
convertColors_(text) {
|
|
const openColors = [];
|
|
|
|
text = text.replace(/<font color=["']?([^"'>]+)["']?>/gi, (_, color) => {
|
|
const key = color.toLowerCase();
|
|
const colorName = shaka.text.Utils.getColorName(key);
|
|
if (colorName) {
|
|
openColors.push(colorName);
|
|
return `<c.${colorName}>`;
|
|
}
|
|
return '';
|
|
});
|
|
|
|
text = text.replace(/<\/font>/gi, () => {
|
|
if (openColors.length) {
|
|
openColors.pop();
|
|
return '</c>';
|
|
}
|
|
return '';
|
|
});
|
|
|
|
return text;
|
|
}
|
|
};
|
|
|
|
|
|
shaka.text.TextEngine.registerParser(
|
|
'text/srt', () => new shaka.text.SrtTextParser());
|