Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Jesper Haug Karsrud <jesper.karsrud@gmail.com>
Johan Sundström <oyasumi@gmail.com>
Jonas Birmé <jonas.birme@eyevinn.se>
Jozef Chúťka <jozefchutka@gmail.com>
Juliane Holzt <juliane@box.fqdn.org>
Jun Hong Chong <chongjunhong@gmail.com>
Jürgen Kartnaller <kartnaller@lovelysystems.com>
Justin Swaney <justin.mark.swaney@gmail.com>
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Jono Ward <jonoward@gmail.com>
Jozef Chúťka <jozefchutka@gmail.com>
Juan Manuel Tomás <juant@qualabs.com>
Julian Domingo <juliandomingo@google.com>
Juliane Holzt <juliane@box.fqdn.org>
Jun Hong Chong <chongjunhong@gmail.com>
Jürgen Kartnaller <kartnaller@lovelysystems.com>
Justin Swaney <justin.mark.swaney@gmail.com>
Expand Down
110 changes: 81 additions & 29 deletions lib/text/mp4_ttml_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

goog.provide('shaka.text.Mp4TtmlParser');

goog.require('goog.asserts');
goog.require('shaka.text.TextEngine');
goog.require('shaka.text.TtmlTextParser');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.Error');
goog.require('shaka.util.Mp4BoxParsers');
goog.require('shaka.util.Mp4Parser');
goog.require('shaka.util.Uint8ArrayUtils');

Expand Down Expand Up @@ -80,67 +82,117 @@ shaka.text.Mp4TtmlParser = class {
parseMedia(data, time, uri) {
const Mp4Parser = shaka.util.Mp4Parser;

let sawMDAT = false;
let payload = [];
let defaultSampleSize = null;

/** @type {!Array<Uint8Array>} */
const mdats = [];

/* @type {!Map<number,!Array<number>>} */
const subSampleSizesPerSample = new Map();

/** @type {!Array<number>} */
let subSizes = [];
const sampleSizes = [];

const parser = new Mp4Parser()
.box('moof', Mp4Parser.children)
.box('traf', Mp4Parser.children)
.fullBox('tfhd', (box) => {
goog.asserts.assert(
box.flags != null,
'A TFHD box should have a valid flags value');
const parsedTFHDBox = shaka.util.Mp4BoxParsers.parseTFHD(
box.reader, box.flags);
defaultSampleSize = parsedTFHDBox.defaultSampleSize;
})
.fullBox('trun', (box) => {
goog.asserts.assert(
box.version != null,
'A TRUN box should have a valid version value');
goog.asserts.assert(
box.flags != null,
'A TRUN box should have a valid flags value');

const parsedTRUNBox = shaka.util.Mp4BoxParsers.parseTRUN(
box.reader, box.version, box.flags);

for (const sample of parsedTRUNBox.sampleData) {
const sampleSize =
sample.sampleSize || defaultSampleSize || 0;
sampleSizes.push(sampleSize);
}
})
.fullBox('subs', (box) => {
subSizes = [];
const reader = box.reader;
const entryCount = reader.readUint32();
let currentSampleNum = -1;
for (let i = 0; i < entryCount; i++) {
reader.readUint32(); // sample_delta
const sampleDelta = reader.readUint32();
currentSampleNum += sampleDelta;
const subsampleCount = reader.readUint16();
const subsampleSizes = [];
for (let j = 0; j < subsampleCount; j++) {
if (box.version == 1) {
subSizes.push(reader.readUint32());
subsampleSizes.push(reader.readUint32());
} else {
subSizes.push(reader.readUint16());
subsampleSizes.push(reader.readUint16());
}
reader.readUint8(); // priority
reader.readUint8(); // discardable
reader.readUint32(); // codec_specific_parameters
}
subSampleSizesPerSample.set(currentSampleNum, subsampleSizes);
}
})
.box('mdat', Mp4Parser.allData((data) => {
sawMDAT = true;
// Join this to any previous payload, in case the mp4 has multiple
// mdats.
if (subSizes.length) {
const contentData =
shaka.util.BufferUtils.toUint8(data, 0, subSizes[0]);
const images = [];
let offset = subSizes[0];
for (let i = 1; i < subSizes.length; i++) {
const imageData =
shaka.util.BufferUtils.toUint8(data, offset, subSizes[i]);
const raw =
shaka.util.Uint8ArrayUtils.toStandardBase64(imageData);
images.push('data:image/png;base64,' + raw);
offset += subSizes[i];
}
payload = payload.concat(
this.parser_.parseMedia(contentData, time, uri, images));
} else {
payload = payload.concat(
this.parser_.parseMedia(data, time, uri, /* images= */ []));
}
// We collect all of the mdats first, before parsing any of them.
// This is necessary in case the mp4 has multiple mdats.
mdats.push(data);
}));
parser.parse(data, /* partialOkay= */ false);

if (!sawMDAT) {
if (mdats.length == 0) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_MP4_TTML);
}

const fullData =
shaka.util.Uint8ArrayUtils.concat(...mdats);

let sampleOffset = 0;
for (let sampleNum = 0; sampleNum < sampleSizes.length; sampleNum++) {
const sampleData =
shaka.util.BufferUtils.toUint8(fullData, sampleOffset,
sampleSizes[sampleNum]);
sampleOffset += sampleSizes[sampleNum];

const subSampleSizes = subSampleSizesPerSample.get(sampleNum);

if (subSampleSizes && subSampleSizes.length) {
const contentData =
shaka.util.BufferUtils.toUint8(sampleData, 0, subSampleSizes[0]);
const images = [];
let subOffset = subSampleSizes[0];
for (let i = 1; i < subSampleSizes.length; i++) {
const imageData =
shaka.util.BufferUtils.toUint8(data, subOffset,
subSampleSizes[i]);
const raw =
shaka.util.Uint8ArrayUtils.toStandardBase64(imageData);
images.push('data:image/png;base64,' + raw);
subOffset += subSampleSizes[i];
}
payload = payload.concat(
this.parser_.parseMedia(contentData, time, uri, images));
} else {
payload = payload.concat(
this.parser_.parseMedia(sampleData, time, uri,
/* images= */ []));
}
}

return payload;
}
};
Expand Down
Binary file modified test/test/assets/ttml-segment-multiple-mdat.mp4
Binary file not shown.
Binary file added test/test/assets/ttml-segment-multiple-sample.mp4
Binary file not shown.
32 changes: 27 additions & 5 deletions test/text/mp4_ttml_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ describe('Mp4TtmlParser', () => {
const ttmlSegmentUri = '/base/test/test/assets/ttml-segment.mp4';
const ttmlSegmentMultipleMDATUri =
'/base/test/test/assets/ttml-segment-multiple-mdat.mp4';
const ttmlSegmentMultipleSampleUri =
'/base/test/test/assets/ttml-segment-multiple-sample.mp4';
const imscImageInitSegmentUri =
'/base/test/test/assets/imsc-image-init.cmft';
const imscImageSegmentUri =
Expand All @@ -22,6 +24,8 @@ describe('Mp4TtmlParser', () => {
/** @type {!Uint8Array} */
let ttmlSegmentMultipleMDAT;
/** @type {!Uint8Array} */
let ttmlSegmentMultipleSample;
/** @type {!Uint8Array} */
let imscImageInitSegment;
/** @type {!Uint8Array} */
let imscImageSegment;
Expand All @@ -33,16 +37,18 @@ describe('Mp4TtmlParser', () => {
shaka.test.Util.fetch(ttmlInitSegmentUri),
shaka.test.Util.fetch(ttmlSegmentUri),
shaka.test.Util.fetch(ttmlSegmentMultipleMDATUri),
shaka.test.Util.fetch(ttmlSegmentMultipleSampleUri),
shaka.test.Util.fetch(imscImageInitSegmentUri),
shaka.test.Util.fetch(imscImageSegmentUri),
shaka.test.Util.fetch(audioInitSegmentUri),
]);
ttmlInitSegment = shaka.util.BufferUtils.toUint8(responses[0]);
ttmlSegment = shaka.util.BufferUtils.toUint8(responses[1]);
ttmlSegmentMultipleMDAT = shaka.util.BufferUtils.toUint8(responses[2]);
imscImageInitSegment = shaka.util.BufferUtils.toUint8(responses[3]);
imscImageSegment = shaka.util.BufferUtils.toUint8(responses[4]);
audioInitSegment = shaka.util.BufferUtils.toUint8(responses[5]);
ttmlSegmentMultipleSample = shaka.util.BufferUtils.toUint8(responses[3]);
imscImageInitSegment = shaka.util.BufferUtils.toUint8(responses[4]);
imscImageSegment = shaka.util.BufferUtils.toUint8(responses[5]);
audioInitSegment = shaka.util.BufferUtils.toUint8(responses[6]);
});

it('parses init segment', () => {
Expand All @@ -62,8 +68,24 @@ describe('Mp4TtmlParser', () => {
expect(ret[0].nestedCues.length).toBe(1);
expect(ret[1].nestedCues.length).toBe(1);
// Cues.
expect(ret[0].nestedCues[0].nestedCues.length).toBe(10);
expect(ret[1].nestedCues[0].nestedCues.length).toBe(10);
expect(ret[0].nestedCues[0].nestedCues.length).toBe(5);
expect(ret[1].nestedCues[0].nestedCues.length).toBe(5);
});

it('handles media segments with multiple sample', () => {
const parser = new shaka.text.Mp4TtmlParser();
parser.parseInit(ttmlInitSegment);
const time =
{periodStart: 0, segmentStart: 0, segmentEnd: 60, vttOffset: 0};
const ret = parser.parseMedia(ttmlSegmentMultipleSample, time, null);
// Bodies.
expect(ret.length).toBe(2);
// Divs.
expect(ret[0].nestedCues.length).toBe(1);
expect(ret[1].nestedCues.length).toBe(1);
// Cues.
expect(ret[0].nestedCues[0].nestedCues.length).toBe(5);
expect(ret[1].nestedCues[0].nestedCues.length).toBe(5);
});

it('accounts for offset', () => {
Expand Down
Loading