Changed SRT parser to a stream parser as well.

This commit is contained in:
Arnavion
2014-10-25 17:05:28 -07:00
parent cb2faea9d9
commit 985e9c8de8
4 changed files with 219 additions and 292 deletions
+13 -49
View File
@@ -134,57 +134,21 @@ addEventListener("DOMContentLoaded", function () {
// Find the ASS or SRT track and load it
var track = document.querySelector("#video > track[data-format='ass'], #video > track[data-format='srt']");
switch (track.getAttribute("data-format")) {
case "ass":
libjass.ASS.fromUrl(track.src || track.getAttribute("src")).then(function (result) {
debug("Script received.");
libjass.ASS.fromUrl(track.src || track.getAttribute("src"), libjass.Format[track.getAttribute("data-format").toUpperCase()]).then(function (result) {
debug("Script received.");
ass = result;
ass = result;
if (libjass.debugMode) {
// Export the ASS object for debugging
window.ass = ass;
}
if (libjass.debugMode) {
// Export the ASS object for debugging
window.ass = ass;
}
// Prepare the "Script resolution" option label
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
// Prepare the "Script resolution" option label
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
// Test if everything is loaded
testVideoAndASSLoaded();
});
break;
case "srt":
var subsRequest = new XMLHttpRequest();
subsRequest.open("GET", track.src || track.getAttribute("src"), true);
subsRequest.addEventListener("load", function () {
debug("Script received.");
if (libjass.debugMode) {
console.time("Parsing ASS took");
}
// Parse the response string into an ASS object
libjass.ASS.fromString(subsRequest.responseText, libjass.Format[track.getAttribute("data-format").toUpperCase()]).then(function (result) {
ass = result;
if (libjass.debugMode) {
console.timeEnd("Parsing ASS took");
// Export the ASS object for debugging
window.ass = ass;
}
// Prepare the "Script resolution" option label
document.querySelector("#script-resolution-label-width").appendChild(document.createTextNode(ass.properties.resolutionX));
document.querySelector("#script-resolution-label-height").appendChild(document.createTextNode(ass.properties.resolutionY));
// Test if everything is loaded
testVideoAndASSLoaded();
});
}, false);
subsRequest.send(null);
break;
}
// Test if everything is loaded
testVideoAndASSLoaded();
});
}, false);
+119 -208
View File
@@ -265,6 +265,125 @@ module libjass.parser {
}
}
/**
* A parser that parses an {@link libjass.ASS} object from a {@link libjass.parser.Stream} of an SRT script.
*
* @param {!libjass.parser.Stream} stream The {@link libjass.parser.Stream} to parse
*/
export class SrtStreamParser {
private _ass: ASS = new ASS();
private _deferred: DeferredPromise<ASS> = new DeferredPromise<ASS>();
private _currentDialogueNumber: string = null;
private _currentDialogueStart: string = null;
private _currentDialogueEnd: string = null;
private _currentDialogueText: string = null;
constructor(private _stream: Stream) {
this._stream.nextLine().then(line => this._onNextLine(line));
this._ass.properties.resolutionX = 1280;
this._ass.properties.resolutionY = 720;
this._ass.properties.wrappingStyle = 1;
this._ass.properties.scaleBorderAndShadow = true;
var newStyle = new Style(new Map<string, string>()
.set("Name", "Default")
.set("Italic", "0").set("Bold", "0").set("Underline", "0").set("StrikeOut", "0")
.set("Fontname", "").set("Fontsize", "50")
.set("ScaleX", "100").set("ScaleY", "100")
.set("Spacing", "0")
.set("Angle", "0")
.set("PrimaryColour", "&H0000FFFF").set("SecondaryColour", "&H00000000").set("OutlineColour", "&H00000000").set("BackColour", "&H00000000")
.set("Outline", "1").set("BorderStyle", "1")
.set("Shadow", "1")
.set("Alignment", "2")
.set("MarginL", "80").set("MarginR", "80").set("MarginV", "35")
);
this._ass.styles.set(newStyle.name, newStyle);
}
/**
* @type {!Promise.<!libjass.ASS>} A promise that will be resolved when the entire stream has been parsed.
*/
get ass(): Promise<ASS> {
return this._deferred.promise;
}
/**
* @param {string} line
*/
private _onNextLine(line: string): void {
if (line === null) {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map<string, string>()
.set("Style", "Default")
.set("Start", this._currentDialogueStart).set("End", this._currentDialogueEnd)
.set("Layer", "0")
.set("Text", this._currentDialogueText), this._ass)
);
}
this._deferred.resolve(this._ass);
return;
}
if (line[line.length - 1] === "\r") {
line = line.substr(0, line.length - 1);
}
if (line === "") {
if (this._currentDialogueNumber !== null && this._currentDialogueStart !== null && this._currentDialogueEnd !== null && this._currentDialogueText !== null) {
this._ass.dialogues.push(new Dialogue(new Map<string, string>()
.set("Style", "Default")
.set("Start", this._currentDialogueStart)
.set("End", this._currentDialogueEnd)
.set("Layer", "0")
.set("Text", this._currentDialogueText), this._ass)
);
}
this._currentDialogueNumber = this._currentDialogueStart = this._currentDialogueEnd = this._currentDialogueText = null;
}
else {
if (this._currentDialogueNumber === null) {
if (/^\d+$/.test(line)) {
this._currentDialogueNumber = line;
}
}
else if (this._currentDialogueStart === null && this._currentDialogueEnd === null) {
var match = /^(\d\d:\d\d:\d\d,\d\d\d) --> (\d\d:\d\d:\d\d,\d\d\d)/.exec(line);
if (match !== null) {
this._currentDialogueStart = match[1].replace(",", ".");
this._currentDialogueEnd = match[2].replace(",", ".");
}
}
else {
line = line
.replace(/<b>/g, "{\\b1}").replace(/\{b\}/g, "{\\b1}")
.replace(/<\/b>/g, "{\\b0}").replace(/\{\/b\}/g, "{\\b0}")
.replace(/<i>/g, "{\\i1}").replace(/\{i\}/g, "{\\i1}")
.replace(/<\/i>/g, "{\\i0}").replace(/\{\/i\}/g, "{\\i0}")
.replace(/<u>/g, "{\\u1}").replace(/\{u\}/g, "{\\u1}")
.replace(/<\/u>/g, "{\\u0}").replace(/\{\/u\}/g, "{\\u0}")
.replace(
/<font color="#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})">/g,
(/* ujs:unreferenced */ substring: string, red: string, green: string, blue: string) => "{\c&H" + blue + green + red + "&}"
).replace(/<\/font>/g, "{\\c}");
if (this._currentDialogueText !== null) {
this._currentDialogueText += "\\N" + line;
}
else {
this._currentDialogueText = line;
}
}
}
this._stream.nextLine().then(line => this._onNextLine(line));
}
}
/**
* Parses a line into a {@link libjass.Property}.
*
@@ -2073,214 +2192,6 @@ module libjass.parser {
return current;
}
/**
* @param {!libjass.parser.ParseNode} parent
* @return {libjass.parser.ParseNode}
*/
parse_srtScript(parent: ParseNode): ParseNode {
var current = new ParseNode(parent);
current.value = [];
while (this._haveMore()) {
var dialogueNode = this.parse_srtDialogue(current);
if (dialogueNode === null) {
parent.pop();
return null;
}
current.value.push(dialogueNode.value);
if (this.read(current, "\n") === null && this._haveMore()) {
parent.pop();
return null;
}
while (this.read(current, "\n") !== null) { }
}
return current;
}
/**
* @param {!libjass.parser.ParseNode} parent
* @return {libjass.parser.ParseNode}
*/
parse_srtDialogue(parent: ParseNode): ParseNode {
var current = new ParseNode(parent);
current.value = Object.create(null);
current.value.bounds = Object.create(null);
current.value.bounds.x1 = null;
current.value.bounds.y1 = null;
current.value.bounds.x2 = null;
current.value.bounds.y2 = null;
var numberNode = this.parse_unsignedDecimal(current);
if (numberNode === null) {
parent.pop();
return null;
}
current.value.number = parseInt(numberNode.value);
if (this.read(current, "\n") === null) {
parent.pop();
return null;
}
var startTimeNode = this.parse_srtTime(current);
if (startTimeNode === null) {
parent.pop();
return null;
}
current.value.start = startTimeNode.value;
if (this.read(current, " --> ") === null) {
parent.pop();
return null;
}
var endTimeNode = this.parse_srtTime(current);
if (endTimeNode === null) {
parent.pop();
return null;
}
current.value.end = endTimeNode.value;
if (this.read(current, " ") !== null) {
var positionNode = new ParseNode(current, "");
// TODO: Parse position properly into current.value.bounds
for (var next = this._peek(); next !== "\n" && this._haveMore(); next = this._peek()) {
positionNode.value += next;
}
current.value.bounds = positionNode.value;
}
if (this.read(current, "\n") === null) {
parent.pop();
return null;
}
var lineNode = new ParseNode(current, "");
while (this._peek() !== "\n" && this._haveMore()) {
var currentLine = "";
for (var next = this._peek(); currentLine[currentLine.length - 1] !== "\n" && this._haveMore(); next = this._peek()) {
currentLine += next;
lineNode.value += next;
}
}
current.value.text = lineNode.value;
if (current.value.text[current.value.text.length - 1] === "\n") {
current.value.text = current.value.text.substr(0, current.value.text.length - 1);
}
return current;
}
/**
* @param {!libjass.parser.ParseNode} parent
* @return {libjass.parser.ParseNode}
*/
parse_srtTime(parent: ParseNode): ParseNode {
var current = new ParseNode(parent);
var hourDigitNodes = new Array<ParseNode>(2);
for (var i = 0; i < hourDigitNodes.length; i++) {
if (!this._haveMore()) {
parent.pop();
return null;
}
var next = this._peek();
if (next >= "0" && next <= "9") {
hourDigitNodes[i] = new ParseNode(current, next);
}
else {
parent.pop();
return null;
}
}
if (this.read(current, ":") === null) {
parent.pop();
return null;
}
var minuteDigitNodes = new Array<ParseNode>(2);
for (i = 0; i < minuteDigitNodes.length; i++) {
if (!this._haveMore()) {
parent.pop();
return null;
}
var next = this._peek();
if (next >= "0" && next <= "9") {
minuteDigitNodes[i] = new ParseNode(current, next);
}
else {
parent.pop();
return null;
}
}
if (this.read(current, ":") === null) {
parent.pop();
return null;
}
var secondDigitNodes = new Array<ParseNode>(2);
for (i = 0; i < secondDigitNodes.length; i++) {
if (!this._haveMore()) {
parent.pop();
return null;
}
var next = this._peek();
if (next >= "0" && next <= "9") {
secondDigitNodes[i] = new ParseNode(current, next);
}
else {
parent.pop();
return null;
}
}
if (this.read(current, ",") === null) {
parent.pop();
return null;
}
var millisecondDigitNodes = new Array<ParseNode>(3);
for (i = 0; i < millisecondDigitNodes.length; i++) {
if (!this._haveMore()) {
parent.pop();
return null;
}
var next = this._peek();
if (next >= "0" && next <= "9") {
millisecondDigitNodes[i] = new ParseNode(current, next);
}
else {
parent.pop();
return null;
}
}
current.value =
hourDigitNodes[0].value + hourDigitNodes[1].value + ":" +
minuteDigitNodes[0].value + minuteDigitNodes[1].value + ":" +
secondDigitNodes[0].value + secondDigitNodes[1].value + "." +
millisecondDigitNodes[0].value + millisecondDigitNodes[1].value + millisecondDigitNodes[2].value;
return current;
}
/**
* @param {!libjass.parser.ParseNode} parent
* @param {string} next
+66 -24
View File
@@ -19,6 +19,7 @@
*/
var libjass = require("../libjass.js");
var assert = require("assert");
var parserTest = require("./parser-test.js");
suite("Miscellaneous", function () {
@@ -157,29 +158,70 @@ suite("Miscellaneous", function () {
new libjass.parts.Comment("p0")
]);
parserTest("SRT", "1\n00:00:10,500 --> 00:00:13,000\nElephant's Dream\n\n2\n00:00:15,000 --> 00:00:18,000 X1:52 X2:303 Y1:438 Y2:453\n<font color=\"cyan\">At the left we can see...</font>", "srtScript", [{
number: 1,
start: "00:00:10.500", end: "00:00:13.000",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Elephant's Dream"
}, {
number: 2,
start: "00:00:15.000", end: "00:00:18.000",
bounds: "X1:52 X2:303 Y1:438 Y2:453",
text: "<font color=\"cyan\">At the left we can see...</font>"
}
]);
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "1\n00:00:10,500 --> 00:00:13,000\nElephant's Dream\n\n2\n00:00:15,000 --> 00:00:18,000 X1:52 X2:303 Y1:438 Y2:453\n<font color=\"cyan\">At the left we can see...</font>"
};
parserTest("SRT multiple lines", "1\n00:01:15,940 --> 00:01:17,280\nHave you secured the key?\nWhy can't people forgive each other\n\n2\n00:01:17,280 --> 00:01:17,670\nWhy can't people forgive each other", "srtScript", [{
number: 1,
start: "00:01:15.940", end: "00:01:17.280",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Have you secured the key?\nWhy can't people forgive each other"
}, {
number: 2,
start: "00:01:17.280", end: "00:01:17.670",
bounds: { x1: null, y1: null, x2: null, y2: null },
text: "Why can't people forgive each other"
}
]);
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 10.500);
assert.deepEqual(ass.dialogues[0].end, 13.000);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Elephant's Dream")
]);
assert.deepEqual(ass.dialogues[1].start, 15.000);
assert.deepEqual(ass.dialogues[1].end, 18.000);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("<font color=\"cyan\">At the left we can see..."),
new libjass.parts.PrimaryColor(null)
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
test("SRT", function (done) {
this.customProperties = {
rule: "SRT",
input: "1\n00:01:15,940 --> 00:01:17,280\nHave you secured the key?\nWhy can't people forgive each other\n\n2\n00:01:17,280 --> 00:01:17,670\nWhy can't people forgive each other"
};
libjass.ASS.fromString(this.customProperties.input, libjass.Format.SRT).then(function (ass) {
try {
assert.deepEqual(ass.dialogues.length, 2);
assert.deepEqual(ass.dialogues[0].start, 1 * 60 + 15.940);
assert.deepEqual(ass.dialogues[0].end, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[0].parts, [
new libjass.parts.Text("Have you secured the key?"),
new libjass.parts.NewLine(),
new libjass.parts.Text("Why can't people forgive each other")
]);
assert.deepEqual(ass.dialogues[1].start, 1 * 60 + 17.280);
assert.deepEqual(ass.dialogues[1].end, 1 * 60 + 17.670);
assert.deepEqual(ass.dialogues[1].parts, [
new libjass.parts.Text("Why can't people forgive each other")
]);
done();
}
catch (ex) {
done(ex);
}
}, function (reason) {
done(reason);
});
});
});
+21 -11
View File
@@ -157,35 +157,45 @@ module libjass {
* @return {!Promise.<!libjass.ASS>}
*/
static fromString(raw: string, type: Format = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
return ASS.fromStream(new parser.StringStream(raw));
case Format.SRT:
return Promise.resolve(ASS._fromSRTString(raw));
default:
throw new Error("Illegal value of type: " + type);
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
}
return ASS.fromStream(new parser.StringStream(raw), type);
}
/**
* Creates an ASS object from the given {@link libjass.parser.Stream}.
*
* @param {!libjass.parser.Stream} stream The stream to parse the script from
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromStream(stream: parser.Stream): Promise<ASS> {
return new parser.StreamParser(stream).ass;
static fromStream(stream: parser.Stream, type: Format = Format.ASS): Promise<ASS> {
switch (type) {
case Format.ASS:
return new parser.StreamParser(stream).ass;
case Format.SRT:
return new parser.SrtStreamParser(stream).ass;
default:
throw new Error("Illegal value of type: " + type);
}
}
/**
* Creates an ASS object from the given URL.
*
* @param {string} url The URL of the script.
* @param {number=0} type The type of the script. One of the {@link libjass.Format} constants.
* @return {!Promise.<!libjass.ASS>} A promise that will be resolved with the ASS object when it has been fully parsed
*/
static fromUrl(url: string): Promise<ASS> {
static fromUrl(url: string, type: Format = Format.ASS): Promise<ASS> {
if ((<{ [index: string]: any }><any>Format)[Format[type]] !== type) {
throw new Error("Illegal value of type: " + type);
}
var xhr = new XMLHttpRequest();
var result = ASS.fromStream(new parser.XhrStream(xhr));
var result = ASS.fromStream(new parser.XhrStream(xhr), type);
xhr.open("GET", url, true);
xhr.send();
return result;