-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.js
141 lines (131 loc) · 5.84 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
var split = require('split2')
var pumpify = require('pumpify')
var through = require('through2')
module.exports = function() {
var re_ass = new RegExp("Dialogue:\\s\\d," + // get time and subtitle
"(\\d+:\\d\\d:\\d\\d.\\d\\d)," + // start time
"(\\d+:\\d\\d:\\d\\d.\\d\\d)," + // end time
"([^,]*)," + // object
"([^,]*)," + // actor
"(?:[^,]*,){4}" +
"(.*)$", "i"); // subtitle
var re_newline = /\\N/g; // replace \N with newline
var re_softbreak = /\\n/g; // There's no equivalent function in WebVTT.
var re_hardspace = /\\h/g; // Replace with
var re_style = /\{([^}]+)\}/; // replace style
var i = 1;
var write = function(line, enc, cb) {
var m = line.match(re_ass);
if (!m) {
return cb();
}
var start = m[1], end = m[2], what = m[3], actor = m[4], text = m[5];
// Support for special characters in WebVTT.
// For obvious reasons, the ampersand one *must* be first.
text = text.replace( /&/g, "&").replace( /</g, "<").replace( />/g, ">");
var style, pos_style = "", tagsToClose = []; // Places to stash style info.
// Subtitles may contain any number of override tags, so we'll loop through
// to find them all.
while ( (style = text.match(re_style)) ) {
var tagsToOpen = [], replaceString = '';
if ( style[1] && style[1].split ) { // Stop throwing errors on empty tags.
style = style[1].split("\\"); // Get an array of override commands.
for (var j = 1; j < style.length; j++) {
// Extract the current tag name.
var tagCommand = style[j].match( /[a-zA-Z]+/ )[0];
// Give special reckognition to one-letter tags.
var oneLetter = ( tagCommand.length == 1 ) ? tagCommand : "";
// "New" position commands. It is assumed that bottom center position is the default.
if ( tagCommand === "an" ) {
var posNum = Number(style[j].substring(2,3));
if ( Math.floor((posNum-1)/3) == 1) {
pos_style += ' line:50%';
} else if ( Math.floor((posNum-1)/3) == 2) {
pos_style += ' line:0';
}
if ( posNum % 3 == 1 ) {
pos_style += ' align:start';
} else if ( posNum % 3 == 0 ) {
pos_style += ' align:end';
}
// Legacy position commands.
} else if ( oneLetter === "a" && !Number.isNaN(Number(style[j].substring(1,2))) ) {
var posNum = Number(style[j].substring(1,2));
if ( posNum > 8 ) {
pos_style += ' line:50%';
} else if ( posNum > 4 ) {
pos_style += ' line:0';
}
if ( (posNum-1) % 4 == 0 ) {
pos_style += ' align:start';
} else if ( (posNum-1) % 4 == 2 ) {
pos_style += ' align:end';
}
// Map simple text decoration commands to equivalent WebVTT text tags.
// NOTE: Strikethrough (the 's' tag) is not supported in WebVTT.
} else if ( ['b', 'i', 'u', 's'].includes(oneLetter) ) {
if ( Number(style[j].substring(1,2)) === 0
// The more elaborate 'b-tag', which we will treat as an on-off selector.
|| ( style[j].match( /b\d{3}/ )
&& Number(style[j].match( /b(\d{3})/ )[1]) < 500 )
) {
// Closing a tag.
if ( tagsToClose.includes(oneLetter) ) {
// Nothing needs to be done if this tag isn't already open.
// HTML tags must be nested, so we must ensure that any tag nested inside
// the tag being closed are also closed, and then opened again once the
// current tag is closed.
while ( tagsToClose.length > 0 ) {
var nowClosing = tagsToClose.pop();
replaceString += '</' + nowClosing + '>';
if ( nowClosing !== oneLetter ) {
tagsToOpen.push(nowClosing);
} else {
// There's no need to close the tags that the current tag
// is nested within.
break;
}
}
}
} else {
// Opening a tag.
if ( !tagsToClose.includes( oneLetter ) ) {
// Nothing needs to be done if the tag is already open.
// If no, place the tag on the bottom of the stack of tags being opened.
tagsToOpen.splice(0, 0, oneLetter);
}
}
} else if (oneLetter === 'r') {
// Resetting override tags, by closing all open tags.
// TODO: The 'r' tag can also be used to switch to a different named style,
// however, named styles haven't been implemented.
while ( tagsToClose.length > 0) {
replaceString += '</' + tagsToClose.pop() + '>';
}
}
// Insert open-tags for tags in the to-open list.
while ( tagsToOpen.length > 0 ) {
var nowOpening = tagsToOpen.pop();
replaceString += '<' + nowOpening + '>';
tagsToClose.push(nowOpening);
}
}
}
text = text.replace(re_style, replaceString); // Replace override tag.
}
text = text.replace(re_newline, "\r\n").replace(re_softbreak, " ").replace(
re_hardspace, " ");
var content = i + "\r\n"
content += "0" + start + "0 --> 0" + end + "0" + pos_style + "\r\n"
content += "<v " + what + " " + actor + ">" + text
while ( tagsToClose.length > 0 ) {
content += '</' + tagsToClose.pop() + '>';
}
content += "\r\n\r\n";
i++;
cb(null, content)
}
var parse = through.obj(write)
parse.push('WEBVTT\r\n\r\n')
return pumpify(split(), parse)
}