diff --git a/lib/flv/coalesce-stream.js b/lib/flv/coalesce-stream.js
index 927aa2f2..4ac9632b 100644
--- a/lib/flv/coalesce-stream.js
+++ b/lib/flv/coalesce-stream.js
@@ -35,7 +35,7 @@ var CoalesceStream = function(options) {
this.push = function(output) {
// buffer incoming captions until the associated video segment
// finishes
- if (output.text) {
+ if (output.content || output.text) {
return this.pendingCaptions.push(output);
}
// buffer incoming id3 tags until the final flush
diff --git a/lib/m2ts/caption-stream.js b/lib/m2ts/caption-stream.js
index 0a6f472f..ef633349 100644
--- a/lib/m2ts/caption-stream.js
+++ b/lib/m2ts/caption-stream.js
@@ -1231,10 +1231,12 @@ var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620,
// CEA-608 captions are rendered onto a 34x15 matrix of character
// cells. The "bottom" row is the last element in the outer array.
+// We keep track of positioning information as we go by storing the
+// number of indentations and the tab offset in this buffer.
var createDisplayBuffer = function() {
var result = [], i = BOTTOM_ROW + 1;
while (i--) {
- result.push('');
+ result.push({ text: '', indent: 0, offset: 0 });
}
return result;
};
@@ -1312,9 +1314,9 @@ var Cea608Stream = function(field, dataChannel) {
} else if (data === this.BACKSPACE_) {
if (this.mode_ === 'popOn') {
- this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
+ this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
} else {
- this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
+ this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
}
} else if (data === this.ERASE_DISPLAYED_MEMORY_) {
this.flushDisplayed(packet.pts);
@@ -1352,9 +1354,9 @@ var Cea608Stream = function(field, dataChannel) {
// Delete the previous character
if (this.mode_ === 'popOn') {
- this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
+ this.nonDisplayed_[this.row_].text = this.nonDisplayed_[this.row_].text.slice(0, -1);
} else {
- this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
+ this.displayed_[this.row_].text = this.displayed_[this.row_].text.slice(0, -1);
}
// Bitmask char0 so that we can apply character transformations
@@ -1390,7 +1392,13 @@ var Cea608Stream = function(field, dataChannel) {
// increments, with an additional offset code of 1-3 to reach any
// of the 32 columns specified by CEA-608. So all we need to do
// here is increment the column cursor by the given offset.
- this.column_ += (char1 & 0x03);
+ const offset = (char1 & 0x03);
+
+ // For an offest value 1-3, set the offset for that caption
+ // in the non-displayed array.
+ this.nonDisplayed_[this.row_].offset = offset;
+
+ this.column_ += offset;
// Detect PACs (Preamble Address Codes)
} else if (this.isPAC(char0, char1)) {
@@ -1427,7 +1435,11 @@ var Cea608Stream = function(field, dataChannel) {
// increments the column cursor by 4, so we can get the desired
// column position by bit-shifting to the right (to get n/2)
// and multiplying by 4.
- this.column_ = ((data & 0xe) >> 1) * 4;
+ const indentations = ((data & 0xe) >> 1);
+
+ this.column_ = indentations * 4;
+ // add to the number of indentations for positioning
+ this.nonDisplayed_[this.row_].indent += indentations;
}
if (this.isColorPAC(char1)) {
@@ -1458,32 +1470,51 @@ Cea608Stream.prototype = new Stream();
// Trigger a cue point that captures the current state of the
// display buffer
Cea608Stream.prototype.flushDisplayed = function(pts) {
- var content = this.displayed_
- // remove spaces from the start and end of the string
- .map(function(row, index) {
+ const logWarning = (index) => {
+ this.trigger('log', {
+ level: 'warn',
+ message: 'Skipping a malformed 608 caption at index ' + index + '.'
+ });
+ };
+ const content = [];
+
+ this.displayed_.forEach((row, i) => {
+ if (row && row.text && row.text.length) {
+
try {
- return row.trim();
+ // remove spaces from the start and end of the string
+ row.text = row.text.trim();
} catch (e) {
// Ordinarily, this shouldn't happen. However, caption
// parsing errors should not throw exceptions and
// break playback.
- this.trigger('log', {
- level: 'warn',
- message: 'Skipping a malformed 608 caption at index ' + index + '.'
+ logWarning(i);
+ }
+ // See the below link for more details on the following fields:
+ // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
+ if (row.text.length) {
+ content.push({
+ // The text to be displayed in the caption from this specific row, with whitespace removed.
+ text: row.text,
+ // Value between 1 and 15 representing the PAC row used to calculate line height.
+ line: i + 1,
+ // A number representing the indent position by percentage (CEA-608 PAC indent code).
+ // The value will be a number between 10 and 80. Offset is used to add an aditional
+ // value to the position if necessary.
+ position: 10 + Math.min(70, row.indent * 10) + (row.offset * 2.5),
});
- return '';
}
- }, this)
- // combine all text rows to display in one cue
- .join('\n')
- // and remove blank rows from the start and end, but not the middle
- .replace(/^\n+|\n+$/g, '');
+ }
+ else if (row === undefined || row === null) {
+ logWarning(i);
+ }
+ });
if (content.length) {
this.trigger('data', {
startPts: this.startPts_,
endPts: pts,
- text: content,
+ content,
stream: this.name_
});
}
@@ -1686,7 +1717,7 @@ Cea608Stream.prototype.setRollUp = function(pts, newBaseRow) {
// move currently displayed captions (up or down) to the new base row
for (var i = 0; i < this.rollUpRows_; i++) {
this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
- this.displayed_[this.row_ - i] = '';
+ this.displayed_[this.row_ - i] = { text: '', indent: 0, offset: 0 };
}
}
@@ -1722,18 +1753,18 @@ Cea608Stream.prototype.clearFormatting = function(pts) {
// Mode Implementations
Cea608Stream.prototype.popOn = function(pts, text) {
- var baseRow = this.nonDisplayed_[this.row_];
+ var baseRow = this.nonDisplayed_[this.row_].text;
// buffer characters
baseRow += text;
- this.nonDisplayed_[this.row_] = baseRow;
+ this.nonDisplayed_[this.row_].text = baseRow;
};
Cea608Stream.prototype.rollUp = function(pts, text) {
- var baseRow = this.displayed_[this.row_];
+ var baseRow = this.displayed_[this.row_].text;
baseRow += text;
- this.displayed_[this.row_] = baseRow;
+ this.displayed_[this.row_].text = baseRow;
};
@@ -1741,24 +1772,24 @@ Cea608Stream.prototype.shiftRowsUp_ = function() {
var i;
// clear out inactive rows
for (i = 0; i < this.topRow_; i++) {
- this.displayed_[i] = '';
+ this.displayed_[i] = { text: '', indent: 0, offset: 0 };
}
for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
- this.displayed_[i] = '';
+ this.displayed_[i] = { text: '', indent: 0, offset: 0 };
}
// shift displayed rows up
for (i = this.topRow_; i < this.row_; i++) {
this.displayed_[i] = this.displayed_[i + 1];
}
// clear out the bottom row
- this.displayed_[this.row_] = '';
+ this.displayed_[this.row_] = { text: '', indent: 0, offset: 0 };
};
Cea608Stream.prototype.paintOn = function(pts, text) {
- var baseRow = this.displayed_[this.row_];
+ var baseRow = this.displayed_[this.row_].text;
baseRow += text;
- this.displayed_[this.row_] = baseRow;
+ this.displayed_[this.row_].text = baseRow;
};
// exports
diff --git a/lib/mp4/caption-parser.js b/lib/mp4/caption-parser.js
index 77a9a8e9..8c4e3957 100644
--- a/lib/mp4/caption-parser.js
+++ b/lib/mp4/caption-parser.js
@@ -245,7 +245,10 @@ var parseCaptionNals = function(segment, videoTrackId) {
* @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
* @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
* @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
- * @return {String} parsedCaptions[].text - The visible content of the caption
+ * @return {Object[]} parsedCaptions[].content - A list of individual caption segments
+ * @return {String} parsedCaptions[].content.text - The visible content of the caption segment
+ * @return {Number} parsedCaptions[].content.line - The line height from 1-15 for positioning of the caption segment
+ * @return {Number} parsedCaptions[].content.position - The column indent percentage for cue positioning from 10-80
**/
var parseEmbeddedCaptions = function(segment, trackId, timescale) {
var captionNals;
diff --git a/lib/mp4/transmuxer.js b/lib/mp4/transmuxer.js
index 58d64482..a2485858 100644
--- a/lib/mp4/transmuxer.js
+++ b/lib/mp4/transmuxer.js
@@ -727,7 +727,7 @@ CoalesceStream = function(options, metadataStream) {
this.push = function(output) {
// buffer incoming captions until the associated video segment
// finishes
- if (output.text) {
+ if (output.content || output.text) {
return this.pendingCaptions.push(output);
}
// buffer incoming id3 tags until the final flush
diff --git a/test/caption-parser.test.js b/test/caption-parser.test.js
index 77c3e2e3..c02365ab 100644
--- a/test/caption-parser.test.js
+++ b/test/caption-parser.test.js
@@ -49,7 +49,7 @@ QUnit.test('parse captions from real segment', function(assert) {
cc = captionParser.parse(dashSegment, trackIds, timescales);
assert.equal(cc.captions.length, 1);
- assert.equal(cc.captions[0].text, '00:00:00',
+ assert.equal(cc.captions[0].content[0].text, '00:00:00',
'real segment caption has correct text');
assert.equal(cc.captions[0].stream, 'CC1',
'real segment caption has correct stream');
@@ -86,7 +86,7 @@ QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) {
{ 1: 90000 }); // timescales);
assert.equal(v0Captions.captions.length, 1, 'got 1 version0 caption');
- assert.equal(v0Captions.captions[0].text, 'test string #1',
+ assert.equal(v0Captions.captions[0].content[0].text, 'test string #1',
'got the expected version0 caption text');
assert.equal(v0Captions.captions[0].stream, 'CC1',
'returned the correct caption stream CC1');
@@ -108,7 +108,7 @@ QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) {
{ 2: 90000 }); // timescales
assert.equal(v1Captions.captions.length, 1, 'got version1 caption');
- assert.equal(v1Captions.captions[0].text, 'test string #2',
+ assert.equal(v1Captions.captions[0].content[0].text, 'test string #2',
'got the expected version1 caption text');
assert.equal(v1Captions.captions[0].stream, 'CC4',
'returned the correct caption stream CC4');
diff --git a/test/caption-stream.test.js b/test/caption-stream.test.js
index 8d790569..b60041a8 100644
--- a/test/caption-stream.test.js
+++ b/test/caption-stream.test.js
@@ -261,8 +261,8 @@ QUnit.test('can be parsed from a segment', function(assert) {
transmuxer.flush();
assert.equal(captions.length, 2, 'parsed two captions');
- assert.equal(captions[0].text.indexOf('ASUKA'), 0, 'parsed the start of the first caption');
- assert.ok(captions[0].text.indexOf('Japanese') > 0, 'parsed the end of the first caption');
+ assert.equal(captions[0].content[0].text.indexOf('ASUKA'), 0, 'parsed the start of the first caption');
+ assert.ok(captions[0].content[0].text.indexOf('Japanese') > 0, 'parsed the end of the first caption');
assert.equal(captions[0].startTime, 1, 'parsed the start time');
assert.equal(captions[0].endTime, 4, 'parsed the end time');
});
@@ -291,8 +291,8 @@ QUnit.test('dispatches caption track information', function(assert) {
assert.deepEqual(captionStreams, {CC1: true, CC3: true}, 'found captions in CC1 and CC3');
assert.equal(captions.length, 4, 'parsed eight captions');
- assert.equal(captions[0].text, 'être une période de questions', 'parsed the text of the first caption in CC3');
- assert.equal(captions[1].text, 'PERIOD, FOLKS.', 'parsed the text of the first caption in CC1');
+ assert.equal(captions[0].content[0].text, 'être une période de questions', 'parsed the text of the first caption in CC3');
+ assert.equal(captions[1].content[0].text, 'PERIOD, FOLKS.', 'parsed the text of the first caption in CC1');
});
QUnit.test('sorting is fun', function(assert) {
@@ -341,8 +341,8 @@ QUnit.test('sorting is fun', function(assert) {
captionStream.flush();
assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].text, 'test string #1', 'parsed caption 1');
- assert.equal(captions[1].text, 'test string #2', 'parsed caption 2');
+ assert.equal(captions[0].content[0].text, 'test string #1', 'parsed caption 1');
+ assert.equal(captions[1].content[0].text, 'test string #2', 'parsed caption 2');
});
QUnit.test('drops duplicate segments', function(assert) {
@@ -405,7 +405,7 @@ QUnit.test('drops duplicate segments', function(assert) {
captionStream.flush();
assert.equal(captions.length, 1, 'detected one caption');
- assert.equal(captions[0].text, 'test string data', 'parsed caption properly');
+ assert.equal(captions[0].content[0].text, 'test string data', 'parsed caption properly');
});
QUnit.test('drops duplicate segments with multi-segment DTS values', function(assert) {
@@ -555,8 +555,8 @@ QUnit.test('drops duplicate segments with multi-segment DTS values', function(as
captionStream.flush();
assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].text, 'test string data stuff', 'parsed caption properly');
- assert.equal(captions[1].text, 'and even more text data here!', 'parsed caption properly');
+ assert.equal(captions[0].content[0].text, 'test string data stuff', 'parsed caption properly');
+ assert.equal(captions[1].content[0].text, 'and even more text data here!', 'parsed caption properly');
});
QUnit.test('doesn\'t ignore older segments if reset', function(assert) {
@@ -647,7 +647,7 @@ QUnit.test('doesn\'t ignore older segments if reset', function(assert) {
assert.equal(captionStream.latestDts_, 4000, 'DTS is tracked correctly');
assert.equal(captions.length, 1, 'detected one caption');
- assert.equal(captions[0].text, 'after reset data!!', 'parsed caption properly');
+ assert.equal(captions[0].content[0].text, 'after reset data!!', 'parsed caption properly');
});
QUnit.test('extracts all theoretical caption channels', function(assert) {
@@ -690,13 +690,14 @@ QUnit.test('extracts all theoretical caption channels', function(assert) {
captionStream.flush();
assert.equal(captions.length, 6, 'got all captions');
- assert.equal(captions[0].text, '1a', 'cc1 first row');
- assert.equal(captions[1].text, '2a', 'cc2 first row');
- assert.equal(captions[2].text, '1a\n1b1c', 'cc1 first and second row');
- assert.equal(captions[3].text, '3a', 'cc3 first row');
- assert.equal(captions[4].text, '4a4b', 'cc4 first row');
- assert.equal(captions[5].text, '2a\n2b', 'cc2 first and second row');
-
+ assert.equal(captions[0].content[0].text, '1a', 'cc1 first row');
+ assert.equal(captions[1].content[0].text, '2a', 'cc2 first row');
+ assert.equal(captions[2].content[0].text, '1a', 'cc1 first row');
+ assert.equal(captions[2].content[1].text, '1b1c', 'cc1 second row');
+ assert.equal(captions[3].content[0].text, '3a', 'cc3 first row');
+ assert.equal(captions[4].content[0].text, '4a4b', 'cc4 first row');
+ assert.equal(captions[5].content[0].text, '2a', 'cc2 first row');
+ assert.equal(captions[5].content[1].text, '2b', 'cc2 second row');
});
QUnit.test('drops data until first command that sets activeChannel for a field', function(assert) {
@@ -763,9 +764,9 @@ QUnit.test('drops data until first command that sets activeChannel for a field',
captionStream.flush();
assert.equal(captions.length, 2, 'received 2 captions');
- assert.equal(captions[0].text, 'field0', 'received only confirmed field0 data');
+ assert.equal(captions[0].content[0].text, 'field0', 'received only confirmed field0 data');
assert.equal(captions[0].stream, 'CC1', 'caption went to right channel');
- assert.equal(captions[1].text, 'field1', 'received only confirmed field1 data');
+ assert.equal(captions[1].content[0].text, 'field1', 'received only confirmed field1 data');
assert.equal(captions[1].stream, 'CC4', 'caption went to right channel');
});
@@ -855,33 +856,33 @@ QUnit.test('clears buffer and drops data until first command that sets activeCha
seiNals1.forEach(captionStream.push, captionStream);
captionStream.flush();
- assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14], 'field0',
+ assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14].text, 'field0',
'there is data in non-displayed memory for field 0 before reset');
- assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14], 'field1',
+ assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14].text, 'field1',
'there is data in non-displayed memory for field 1 before reset');
- assert.equal(captionStream.ccStreams_[0].displayed_[14], 'field0',
+ assert.equal(captionStream.ccStreams_[0].displayed_[14].text, 'field0',
'there is data in displayed memory for field 0 before reset');
- assert.equal(captionStream.ccStreams_[3].displayed_[14], 'field1',
+ assert.equal(captionStream.ccStreams_[3].displayed_[14].text, 'field1',
'there is data in displayed memory for field 1 before reset');
captionStream.reset();
- assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14], '',
+ assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14].text, '',
'there is no data in non-displayed memory for field 0 after reset');
- assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14], '',
+ assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14].text, '',
'there is no data in non-displayed memory for field 1 after reset');
- assert.equal(captionStream.ccStreams_[0].displayed_[14], '',
+ assert.equal(captionStream.ccStreams_[0].displayed_[14].text, '',
'there is no data in displayed memory for field 0 after reset');
- assert.equal(captionStream.ccStreams_[3].displayed_[14], '',
+ assert.equal(captionStream.ccStreams_[3].displayed_[14].text, '',
'there is no data in displayed memory for field 1 after reset');
seiNals2.forEach(captionStream.push, captionStream);
captionStream.flush();
assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].text, 'but this', 'parsed caption properly');
+ assert.equal(captions[0].content[0].text, 'but this', 'parsed caption properly');
assert.equal(captions[0].stream, 'CC1', 'caption went to right channel');
- assert.equal(captions[1].text, 'and this', 'parsed caption properly');
+ assert.equal(captions[1].content[0].text, 'and this', 'parsed caption properly');
assert.equal(captions[1].stream, 'CC4', 'caption went to right channel');
});
@@ -898,10 +899,15 @@ QUnit.test("don't mess up 608 captions when 708 are present", function(assert) {
captionStream.flush();
assert.equal(captions.length, 3, 'parsed three captions');
- assert.equal(captions[0].text, 'BUT IT\'S NOT SUFFERING\nRIGHW.', 'parsed first caption correctly');
+ // first caption stream
+ assert.equal(captions[0].content[0].text, 'BUT IT\'S NOT SUFFERING', 'first stream: parsed first content text correctly');
+ assert.equal(captions[0].content[1].text, 'RIGHW.', 'first stream: parsed second content text correctly');
// there is also bad data in the captions, but the null ascii character is removed
- assert.equal(captions[1].text, 'IT\'S NOT A THREAT TO ANYBODY.', 'parsed second caption correctly');
- assert.equal(captions[2].text, 'WE TRY NOT TO PUT AN ANIMAL DOWN\nIF WE DON\'T HAVE TO.', 'parsed third caption correctly');
+ // second caption stream
+ assert.equal(captions[1].content[0].text, 'IT\'S NOT A THREAT TO ANYBODY.', 'second stream: parsed content text correctly');
+ // third stream
+ assert.equal(captions[2].content[0].text, 'WE TRY NOT TO PUT AN ANIMAL DOWN', 'third stream: parsed first content text correctly');
+ assert.equal(captions[2].content[1].text, 'IF WE DON\'T HAVE TO.', 'third stream: parsed second content text correctly');
});
QUnit.test("both 608 and 708 captions are available by default", function(assert) {
@@ -1009,7 +1015,7 @@ QUnit.test('ignores XDS and Text packets', function(assert) {
captionStream.flush();
assert.equal(captions.length, 1, 'only parsed real caption');
- assert.equal(captions[0].text, 'hi', 'caption is correct');
+ assert.equal(captions[0].content[0].text, 'hi', 'caption is correct');
});
@@ -1051,9 +1057,9 @@ QUnit.test('special and extended character codes work regardless of field and da
seiNals.forEach(captionStream.push, captionStream);
captionStream.flush();
- assert.deepEqual(captions[0].text, String.fromCharCode(0xae), 'CC2 special character correct');
- assert.deepEqual(captions[1].text, String.fromCharCode(0xab), 'CC3 extended character correct');
- assert.deepEqual(captions[2].text, String.fromCharCode(0xbb), 'CC4 extended character correct');
+ assert.deepEqual(captions[0].content[0].text, String.fromCharCode(0xae), 'CC2 special character correct');
+ assert.deepEqual(captions[1].content[0].text, String.fromCharCode(0xab), 'CC3 extended character correct');
+ assert.deepEqual(captions[2].content[0].text, String.fromCharCode(0xbb), 'CC4 extended character correct');
});
QUnit.test('number of roll up rows takes precedence over base row command', function(assert) {
@@ -1100,8 +1106,9 @@ QUnit.test('number of roll up rows takes precedence over base row command', func
seis.forEach(captionStream.push, captionStream);
captionStream.flush();
- assert.deepEqual(captions[0].text, '-', 'RU2 caption is correct');
- assert.deepEqual(captions[1].text, '-\nso', 'RU3 caption is correct');
+ assert.deepEqual(captions[0].content[0].text, '-', 'RU2 caption is correct');
+ assert.deepEqual(captions[1].content[0].text, '-', 'first RU3 caption is correct');
+ assert.deepEqual(captions[1].content[1].text, 'so', 'second RU3 caption is correct');
packets = [
// switching from row 11 to 0
@@ -1119,7 +1126,8 @@ QUnit.test('number of roll up rows takes precedence over base row command', func
seis.forEach(captionStream.push, captionStream);
captionStream.flush();
- assert.deepEqual(captions[2].text, '-\nso', 'RU3 caption is correct');
+ assert.deepEqual(captions[2].content[0].text, '-', 'first RU3 caption is correct');
+ assert.deepEqual(captions[2].content[1].text, 'so', 'second RU3 caption is correct');
});
var cea608Stream;
@@ -1162,7 +1170,7 @@ QUnit.test('converts non-ASCII character codes to ASCII', function(assert) {
});
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text,
+ assert.equal(captions[0].content[0].text,
String.fromCharCode(0xe1, 0xe9, 0xed, 0xf3, 0xfa, 0xe7, 0xf7, 0xd1, 0xf1, 0x2588),
'translated non-standard characters');
});
@@ -1205,7 +1213,7 @@ QUnit.test('properly handles special character codes', function(assert) {
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text,
+ assert.equal(captions[0].content[0].text,
String.fromCharCode(0xae, 0xb0, 0xbd, 0xbf, 0x2122, 0xa2, 0xa3, 0x266a,
0xe0, 0xa0, 0xe8, 0xe2, 0xea, 0xee, 0xf4, 0xfb),
'translated special characters');
@@ -1248,7 +1256,7 @@ QUnit.test('properly handles extended character codes', function(assert) {
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, '«LÀ-LÅ LAÑD♪»',
+ assert.equal(captions[0].content[0].text, '«LÀ-LÅ LAÑD♪»',
'translated special characters');
});
@@ -1278,7 +1286,11 @@ QUnit.test('pop-on mode', function(assert) {
assert.deepEqual(captions[0], {
startPts: 1000,
endPts: 10 * 1000,
- text: 'hi',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'hi'
+ }],
stream: 'CC1'
}, 'parsed the caption');
});
@@ -1313,7 +1325,11 @@ QUnit.test('ignores null characters', function(assert) {
assert.deepEqual(captions[0], {
startPts: 1000,
endPts: 10 * 1000,
- text: 'mu x',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'mu x'
+ }],
stream: 'CC1'
}, 'ignored null characters');
});
@@ -1354,24 +1370,36 @@ QUnit.test('recognizes the Erase Displayed Memory command', function(assert) {
assert.deepEqual(captions[0], {
startPts: 1 * 1000,
endPts: 1.5 * 1000,
- text: '01',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '01'
+ }],
stream: 'CC1'
}, 'parsed the first caption');
assert.deepEqual(captions[1], {
startPts: 2 * 1000,
endPts: 3 * 1000,
- text: '23',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '23'
+ }],
stream: 'CC1'
}, 'parsed the second caption');
assert.deepEqual(captions[2], {
startPts: 3 * 1000,
endPts: 4 * 1000,
- text: '34',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '34'
+ }],
stream: 'CC1'
}, 'parsed the third caption');
});
-QUnit.test('backspaces are applied to non-displayed memory for pop-on mode', function(assert) {
+QUnit.test('correct content text is added to non-displayed memory for pop-on mode', function(assert) {
var captions = [], packets;
cea608Stream.on('data', function(caption) {
captions.push(caption);
@@ -1402,7 +1430,8 @@ QUnit.test('backspaces are applied to non-displayed memory for pop-on mode', fun
packets.forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].text, '310\n\n023', 'applied the backspaces');
+ assert.equal(captions[0].content[0].text, '310', 'first content text');
+ assert.equal(captions[0].content[1].text, '023', 'second content text');
});
QUnit.test('backspaces on cleared memory are no-ops', function(assert) {
@@ -1455,7 +1484,11 @@ QUnit.test('recognizes the Erase Non-Displayed Memory command', function(assert)
assert.deepEqual(captions[0], {
startPts: 1 * 1000,
endPts: 2 * 1000,
- text: '23',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '23'
+ }],
stream: 'CC1'
}, 'cleared the non-displayed memory');
});
@@ -1483,7 +1516,7 @@ QUnit.test('ignores unrecognized commands', function(assert) {
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, '01', 'skipped the unrecognized commands');
+ assert.equal(captions[0].content[0].text, '01', 'skipped the unrecognized commands');
});
QUnit.skip('applies preamble address codes', function(assert) {
@@ -1513,7 +1546,7 @@ QUnit.test('applies mid-row underline', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'no yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'no yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1536,7 +1569,7 @@ QUnit.test('applies mid-row italics', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'no yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'no yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1559,7 +1592,7 @@ QUnit.test('applies mid-row italics underline', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'no yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'no yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1583,7 +1616,7 @@ QUnit.test('applies PAC underline', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1605,7 +1638,7 @@ QUnit.test('applies PAC white italics', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1627,11 +1660,11 @@ QUnit.test('applies PAC white italics underline', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'yes.', 'properly closed by CR');
+ assert.equal(captions[0].content[0].text, 'yes.', 'properly closed by CR');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
-QUnit.test('closes formatting at PAC row change', function(assert) {
+QUnit.test('includes all caption text at PAC row change', function(assert) {
var captions = [];
cea608Stream.on('data', function(caption) {
captions.push(caption);
@@ -1656,7 +1689,8 @@ QUnit.test('closes formatting at PAC row change', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'yes.\nno', 'properly closed by PAC row change');
+ assert.equal(captions[0].content[0].text, 'yes.', 'first content text');
+ assert.equal(captions[0].content[1].text, 'no', 'second content text');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1682,7 +1716,7 @@ QUnit.test('closes formatting at EOC', function(assert) {
];
packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].text, 'yes.', 'properly closed by EOC');
+ assert.equal(captions[0].content[0].text, 'yes.', 'properly closed by EOC');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1707,7 +1741,7 @@ QUnit.test('closes formatting at negating mid-row code', function(assert) {
packets.forEach(cea608Stream.push, cea608Stream);
cea608Stream.flushDisplayed();
- assert.equal(captions[0].text, 'no yes. no', 'properly closed by negating mid-row code');
+ assert.equal(captions[0].content[0].text, 'no yes. no', 'properly closed by negating mid-row code');
assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
});
@@ -1733,7 +1767,11 @@ QUnit.test('roll-up display mode', function(assert) {
assert.deepEqual(captions[0], {
startPts: 0 * 1000,
endPts: 3 * 1000,
- text: '01',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '01'
+ }],
stream: 'CC1'
}, 'parsed the caption');
captions = [];
@@ -1755,7 +1793,18 @@ QUnit.test('roll-up display mode', function(assert) {
assert.deepEqual(captions[0], {
startPts: 3 * 1000,
endPts: 5 * 1000,
- text: '01\n23',
+ content: [
+ {
+ line: 14,
+ position: 10,
+ text: '01'
+ },
+ {
+ line: 15,
+ position: 10,
+ text: '23'
+ }
+ ],
stream: 'CC1'
}, 'parsed the new caption and kept the caption up after the new caption');
});
@@ -1783,7 +1832,11 @@ QUnit.test('roll-up displays multiple rows simultaneously', function(assert) {
assert.deepEqual(captions[0], {
startPts: 0 * 1000,
endPts: 1 * 1000,
- text: '01',
+ content: [{
+ line: 15,
+ position: 10,
+ text: '01'
+ }],
stream: 'CC1'
}, 'created a caption for the first period');
captions = [];
@@ -1803,7 +1856,18 @@ QUnit.test('roll-up displays multiple rows simultaneously', function(assert) {
assert.deepEqual(captions[0], {
startPts: 1 * 1000,
endPts: 3 * 1000,
- text: '01\n23',
+ content: [
+ {
+ line: 14,
+ position: 10,
+ text: '01'
+ },
+ {
+ line: 15,
+ position: 10,
+ text: '23'
+ }
+ ],
stream: 'CC1'
}, 'created the top and bottom rows after the shift up');
captions = [];
@@ -1823,7 +1887,18 @@ QUnit.test('roll-up displays multiple rows simultaneously', function(assert) {
assert.deepEqual(captions[0], {
startPts: 3 * 1000,
endPts: 5 * 1000,
- text: '23\n45',
+ "content": [
+ {
+ line: 14,
+ position: 10,
+ text: '23'
+ },
+ {
+ line: 15,
+ position: 10,
+ text: '45'
+ }
+ ],
stream: 'CC1'
}, 'created the top and bottom rows after the shift up');
});
@@ -1894,13 +1969,13 @@ QUnit.test('switching to roll-up from pop-on wipes memories and flushes captions
].forEach(cea608Stream.push, cea608Stream);
var displayed = cea608Stream.displayed_.reduce(function(acc, val) {
- acc += val;
+ acc += val.text;
return acc;
- });
+ }, '');
var nonDisplayed = cea608Stream.nonDisplayed_.reduce(function(acc, val) {
- acc += val;
+ acc += val.text;
return acc;
- });
+ }, '');
assert.equal(captions.length, 2, 'both captions flushed');
assert.equal(displayed, '', 'displayed memory is wiped');
@@ -1908,13 +1983,21 @@ QUnit.test('switching to roll-up from pop-on wipes memories and flushes captions
assert.deepEqual(captions[0], {
startPts: 1000,
endPts: 2000,
- text: 'hi',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'hi',
+ }],
stream: 'CC1'
}, 'first caption correct');
assert.deepEqual(captions[1], {
startPts: 2000,
endPts: 3000,
- text: 'oh',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'oh',
+ }],
stream: 'CC1'
}, 'second caption correct');
});
@@ -1934,13 +2017,13 @@ QUnit.test('switching to roll-up from paint-on wipes memories and flushes captio
].forEach(cea608Stream.push, cea608Stream);
var displayed = cea608Stream.displayed_.reduce(function(acc, val) {
- acc += val;
+ acc += val.text;
return acc;
- });
+ }, '');
var nonDisplayed = cea608Stream.nonDisplayed_.reduce(function(acc, val) {
- acc += val;
+ acc += val.text;
return acc;
- });
+ }, '');
assert.equal(captions.length, 1, 'flushed caption');
assert.equal(displayed, '', 'displayed memory is wiped');
@@ -1948,7 +2031,11 @@ QUnit.test('switching to roll-up from paint-on wipes memories and flushes captio
assert.deepEqual(captions[0], {
startPts: 0,
endPts: 1000,
- text: 'hi',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'hi',
+ }],
stream: 'CC1'
}, 'caption correct');
});
@@ -1983,10 +2070,10 @@ QUnit.test('switching to paint-on from pop-on flushes display', function(assert)
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 2, 'detected 2 captions');
- assert.equal(captions[0].text, 'hi', 'pop-on caption received');
+ assert.equal(captions[0].content[0].text, 'hi', 'pop-on caption received');
assert.equal(captions[0].startPts, 1000, 'proper start pts');
assert.equal(captions[0].endPts, 2000, 'proper end pts');
- assert.equal(captions[1].text, 'io', 'paint-on caption received');
+ assert.equal(captions[1].content[0].text, 'io', 'paint-on caption received');
assert.equal(captions[1].startPts, 2000, 'proper start pts');
assert.equal(captions[1].endPts, 4000, 'proper end pts');
});
@@ -2017,7 +2104,7 @@ QUnit.test('backspaces are reflected in the generated captions', function(assert
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].text, '023', 'applied the backspace');
+ assert.equal(captions[0].content[0].text, '023', 'applied the backspace');
});
QUnit.test('backspaces can remove a caption entirely', function(assert) {
@@ -2079,7 +2166,7 @@ QUnit.test('a second identical control code immediately following the first is i
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].text, '01', 'only two backspaces processed');
+ assert.equal(captions[0].content[0].text, '01', 'only two backspaces processed');
});
QUnit.test('a second identical control code separated by only padding from the first is ignored', function(assert) {
@@ -2115,7 +2202,7 @@ QUnit.test('a second identical control code separated by only padding from the f
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].text, '010', 'only one backspace processed');
+ assert.equal(captions[0].content[0].text, '010', 'only one backspace processed');
});
QUnit.test('preamble address codes on same row are NOT converted into spaces', function(assert) {
@@ -2145,10 +2232,10 @@ QUnit.test('preamble address codes on same row are NOT converted into spaces', f
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].text, '0102', 'PACs were NOT converted to space');
+ assert.equal(captions[0].content[0].text, '0102', 'PACs were NOT converted to space');
});
-QUnit.test('preserves newlines from PACs in pop-on mode', function(assert) {
+QUnit.test('generates correct content with PACs in pop-on mode', function(assert) {
var captions = [];
cea608Stream.on('data', function(caption) {
captions.push(caption);
@@ -2184,7 +2271,9 @@ QUnit.test('preserves newlines from PACs in pop-on mode', function(assert) {
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].text, 'TEST\n\nSTRING\nDATA', 'Position PACs were converted to newlines');
+ assert.equal(captions[0].content[0].text, 'TEST', 'first content text');
+ assert.equal(captions[0].content[1].text, 'STRING', 'second content text');
+ assert.equal(captions[0].content[2].text, 'DATA', 'third content text');
});
QUnit.test('extracts real-world cc1 and cc3 channels', function(assert) {
@@ -2252,14 +2341,14 @@ QUnit.test('extracts real-world cc1 and cc3 channels', function(assert) {
cea608Stream3.push(packet);
});
- var cc1 = {stream: 'CC1', text: 'PERIOD, FOLKS.'};
- var cc3 = {stream: 'CC3', text: 'être une période de questions'};
+ var cc1 = {stream: 'CC1', content: [{ text: 'PERIOD, FOLKS.'}] };
+ var cc3 = {stream: 'CC3', content: [{ text: 'être une période de questions' }] };
assert.equal(captions.length, 2, 'caption emitted');
assert.equal(captions[0].stream, cc1.stream, 'cc1 stream detected');
- assert.equal(captions[0].text, cc1.text, 'cc1 stream extracted successfully');
+ assert.equal(captions[0].content[0].text, cc1.content[0].text, 'cc1 stream extracted successfully');
assert.equal(captions[1].stream, cc3.stream, 'cc3 stream detected');
- assert.equal(captions[1].text, cc3.text, 'cc3 stream extracted successfully');
+ assert.equal(captions[1].content[0].text, cc3.content[0].text, 'cc3 stream extracted successfully');
});
QUnit.test('backspaces stop at the beginning of the line', function(assert) {
@@ -2307,7 +2396,7 @@ QUnit.test('reset works', function(assert) {
{ pts: 0, ccData: characters('01'), type: 0 }
].forEach(cea608Stream.push, cea608Stream);
var buffer = cea608Stream.displayed_.map(function(row) {
- return row.trim();
+ return row.text.trim();
}).join('\n')
.replace(/^\n+|\n+$/g, '');
@@ -2316,7 +2405,7 @@ QUnit.test('reset works', function(assert) {
cea608Stream.reset();
buffer = cea608Stream.displayed_
.map(function(row) {
- return row.trim();
+ return row.text.trim();
})
.join('\n')
.replace(/^\n+|\n+$/g, '');
@@ -2348,12 +2437,16 @@ QUnit.test('paint-on mode', function(assert) {
assert.deepEqual(captions[0], {
startPts: 1000,
endPts: 3000,
- text: 'hi',
+ content: [{
+ line: 15,
+ position: 10,
+ text: 'hi',
+ }],
stream: 'CC1'
}, 'parsed the caption');
});
-QUnit.test('preserves newlines from PACs in paint-on mode', function(assert) {
+QUnit.test('generates correct text from PACs in paint-on mode', function(assert) {
var captions = [];
cea608Stream.on('data', function(caption) {
captions.push(caption);
@@ -2383,10 +2476,12 @@ QUnit.test('preserves newlines from PACs in paint-on mode', function(assert) {
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].text, 'TEST\n\nSTRING\nDATA', 'Position PACs were converted to newlines');
+ assert.equal(captions[0].content[0].text, 'TEST', 'first content text');
+ assert.equal(captions[0].content[1].text, 'STRING', 'second content text');
+ assert.equal(captions[0].content[2].text, 'DATA', 'third content text');
});
-QUnit.test('backspaces are reflected in the generated captions (paint-on)', function(assert) {
+QUnit.test('multiple caption texts are generated (paint-on)', function(assert) {
var captions = [];
cea608Stream.on('data', function(caption) {
captions.push(caption);
@@ -2410,7 +2505,113 @@ QUnit.test('backspaces are reflected in the generated captions (paint-on)', func
].forEach(cea608Stream.push, cea608Stream);
assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].text, '310\n\n023', 'applied the backspaces');
+ assert.equal(captions[0].content[0].text, '310', 'first caption text');
+ assert.equal(captions[0].content[1].text, '023', 'second caption text');
+});
+
+QUnit.test('PAC indent code increases the position', function(assert) {
+ var captions = [];
+ cea608Stream.on('data', function(caption) {
+ captions.push(caption);
+ });
+
+ var packets = [
+ // RCL, resume caption loading
+ { ccData: 0x1420, type: 0 },
+ // PAC indent code representing 4 indentations.
+ { ccData: 5240, type: 0 },
+ { ccData: characters('te'), type: 0 },
+ { ccData: characters('st'), type: 0 },
+ // EOC, End of Caption
+ { pts: 1 * 1000, ccData: 0x142f, type: 0 },
+ // RCL, resume caption loading
+ { ccData: 0x1420, type: 0 },
+ // EOC, End of Caption
+ { pts: 2 * 1000, ccData: 0x142f, type: 0 }
+ ];
+
+ packets.forEach(cea608Stream.push, cea608Stream);
+ assert.equal(captions[0].content[0].text, 'test', 'content text');
+ assert.equal(captions[0].content[0].line, 15, 'positions the caption to the bottom of the screen');
+ assert.equal(captions[0].content[0].position, 50, 'positions the caption to the right of the screen');
+});
+
+QUnit.test('PAC offset code increases the position', function(assert) {
+ var captions = [];
+ cea608Stream.on('data', function(caption) {
+ captions.push(caption);
+ });
+
+ var packets = [
+ // RCL, resume caption loading
+ { ccData: 0x1420, type: 0 },
+ // PAC: row 1, indent 0
+ { pts: 6750, ccData: 0x1150, type: 0 },
+ // TO2 (tab offset 2 columns)
+ { pts: 6755, ccData: 0x1722, type: 0 },
+ { ccData: characters('te'), type: 0 },
+ { ccData: characters('st'), type: 0 },
+ // EOC, End of Caption
+ { pts: 1 * 1000, ccData: 0x142f, type: 0 },
+ // RCL, resume caption loading
+ { ccData: 0x1420, type: 0 },
+ // EOC, End of Caption
+ { pts: 2 * 1000, ccData: 0x142f, type: 0 }
+ ];
+
+ packets.forEach(cea608Stream.push, cea608Stream);
+ assert.equal(captions[0].content[0].text, 'test', 'content text');
+ assert.equal(captions[0].content[0].line, 1, 'positions the caption to the bottom of the screen');
+ // Two tab offset columns adds 5 to the position (2 * 2.5)
+ assert.equal(captions[0].content[0].position, 15, 'positions the caption to the right');
+});
+
+QUnit.test('PAC row command ensures we have the correct line property for captions', function(assert) {
+ var captions = [];
+ cea608Stream.on('data', function(caption) {
+ captions.push(caption);
+ });
+
+ var packets = [
+ // RU2 (roll-up, 2 rows)
+ { pts: 6675, ccData: 0x1425, type: 0 },
+ // CR (carriange return), flush nothing
+ { pts: 6675, ccData: 0x142d, type: 0 },
+ // PAC: row 2, indent 0
+ // This should ensure the captions are at the top of the screen.
+ { pts: 6675, ccData: 0x1170, type: 0 },
+ // text: YEAR.
+ { pts: 6676, ccData: 0x5945, type: 0 },
+ { pts: 6676, ccData: 0x4152, type: 0 },
+ { pts: 6676, ccData: 0x2e00, type: 0 },
+ // RU2 (roll-up, 2 rows)
+ { pts: 6677, ccData: 0x1425, type: 0 },
+ // CR (carriange return), flush 1 row
+ { pts: 6677, ccData: 0x142d, type: 0 },
+ // EDM (erase displayed memory), flush 2 displayed roll-up rows
+ { pts: 6697, ccData: 0x142c, type: 0 },
+ // RDC (resume direct captioning), wipes memories, flushes nothing
+ { pts: 6749, ccData: 0x1429, type: 0 },
+ // PAC: row 1, indent 0
+ { pts: 6750, ccData: 0x1150, type: 0 },
+ // EOC, End of Caption
+ { pts: 1 * 1000, ccData: 0x142f, type: 0 },
+ // RCL, resume caption loading
+ { ccData: 0x1420, type: 0 },
+ // EOC, End of Caption
+ { pts: 2 * 1000, ccData: 0x142f, type: 0 }
+ ];
+
+ // First caption stream is at the second most bottom row.
+ packets.forEach(cea608Stream.push, cea608Stream);
+ assert.equal(captions[0].content[0].text, 'YEAR.', 'content text');
+ assert.equal(captions[0].content[0].line, 2, 'positions the caption in the second most bottom row');
+ assert.equal(captions[0].content[0].position, 10, 'position of the caption');
+
+ // Second caption stream is at the most bottom row.
+ assert.equal(captions[1].content[0].text, 'YEAR.', 'content text');
+ assert.equal(captions[1].content[0].line, 1, 'positions the caption in the most bottom row');
+ assert.equal(captions[1].content[0].position, 10, 'position of the caption');
});
QUnit.test('mix of all modes (extract from CNN)', function(assert) {
@@ -2606,45 +2807,101 @@ QUnit.test('mix of all modes (extract from CNN)', function(assert) {
assert.equal(captions.length, 7, 'detected 7 captions of varying types');
assert.deepEqual(captions[0], {
+ content: [{
+ line: 2,
+ position: 10,
+ text: 'YEAR.',
+ }],
startPts: 6675,
endPts: 6677,
- text: 'YEAR.',
stream: 'CC1'
}, 'parsed the 1st roll-up caption');
assert.deepEqual(captions[1], {
+ content: [
+ {
+ line: 1,
+ position: 10,
+ text: 'YEAR.',
+ },
+ {
+ line: 2,
+ position: 10,
+ text: 'GO TO CNNHEROS.COM.',
+ }
+ ],
startPts: 6677,
endPts: 6697,
- text: 'YEAR.\nGO TO CNNHEROS.COM.',
stream: 'CC1'
}, 'parsed the 2nd roll-up caption');
assert.deepEqual(captions[2], {
+ content: [
+ {
+ line: 1,
+ position: 10,
+ text: 'Did your Senator or Congressman',
+ },
+ {
+ line: 2,
+ position: 10,
+ text: 'get elected by talking tough',
+ }
+ ],
startPts: 6749,
endPts: 6781,
- text: 'Did your Senator or Congressman\nget elected by talking tough',
stream: 'CC1'
}, 'parsed the paint-on caption');
assert.deepEqual(captions[3], {
+ content: [{
+ line: 1,
+ position: 22.5,
+ text: 'on the national debt?',
+ }],
startPts: 6782,
endPts: 6797,
- text: 'on the national debt?',
stream: 'CC1'
}, 'parsed the 1st pop-on caption');
assert.deepEqual(captions[4], {
+ content: [
+ {
+ line: 1,
+ position: 25,
+ text: 'Will they stay true',
+ },
+ {
+ line: 2,
+ position: 30,
+ text: 'to their words?',
+ }
+ ],
startPts: 6798,
endPts: 6838,
- text: 'Will they stay true\nto their words?',
stream: 'CC1'
}, 'parsed the 2nd pop-on caption');
assert.deepEqual(captions[5], {
+ content: [{
+ line: 2,
+ position: 10,
+ text: '>>> NO MORE SPECULATION, NO MORE',
+ }],
startPts: 6841,
endPts: 6844,
- text: '>>> NO MORE SPECULATION, NO MORE',
stream: 'CC1'
}, 'parsed the 3rd roll-up caption');
assert.deepEqual(captions[6], {
+ content: [
+ {
+ line: 1,
+ position: 10,
+ text: '>>> NO MORE SPECULATION, NO MORE',
+ },
+ {
+ line: 2,
+ position: 10,
+ text: 'RUMORS OR GUESSING GAMES.',
+ }
+ ],
startPts: 6844,
endPts: 6846,
- text: '>>> NO MORE SPECULATION, NO MORE\nRUMORS OR GUESSING GAMES.',
stream: 'CC1'
}, 'parsed the 4th roll-up caption');