Skip to content

Commit

Permalink
fix(CEA): Fix multi byte language support in CEA-708 (#7837)
Browse files Browse the repository at this point in the history
Fixes #7829
  • Loading branch information
avelad authored Jan 7, 2025
1 parent 8c6def4 commit 5b13332
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
29 changes: 27 additions & 2 deletions lib/cea/cea708_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ shaka.cea.Cea708Service = class {
// Control codes are in 1 of 4 logical groups:
// CL (C0, C2), CR (C1, C3), GL (G0, G2), GR (G1, G2).
if (controlCode >= 0x00 && controlCode <= 0x1f) {
return this.handleC0_(controlCode, pts);
return this.handleC0_(dtvccPacket, controlCode, pts);
} else if (controlCode >= 0x80 && controlCode <= 0x9f) {
return this.handleC1_(dtvccPacket, controlCode, pts);
} else if (controlCode >= 0x1000 && controlCode <= 0x101f) {
Expand Down Expand Up @@ -155,17 +155,42 @@ shaka.cea.Cea708Service = class {

/**
* Handles C0 group data.
* @param {!shaka.cea.DtvccPacket} dtvccPacket
* @param {number} controlCode
* @param {number} pts
* @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
* @private
*/
handleC0_(controlCode, pts) {
handleC0_(dtvccPacket, controlCode, pts) {
// All these commands pertain to the current window, so ensure it exists.
if (!this.currentWindow_) {
return null;
}

if (controlCode == 0x18) {
const firstByte = dtvccPacket.readByte().value;
const secondByte = dtvccPacket.readByte().value;

const isTextBlock = (b) => {
return (b >= 0x20 && b <= 0x7f) || (b >= 0xa0 && b <= 0xff);
};

if (isTextBlock(firstByte) && isTextBlock(secondByte)) {
const toHexString = (byteArray) => {
return byteArray.map((byte) => {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
};
const unicode = toHexString([firstByte, secondByte]);
// Takes a unicode hex string and creates a single character.
const char = String.fromCharCode(parseInt(unicode, 16));
this.currentWindow_.setCharacter(char);
return null;
} else {
dtvccPacket.rewind(2);
}
}

const window = this.currentWindow_;
let parsedClosedCaption = null;

Expand Down
15 changes: 15 additions & 0 deletions lib/cea/dtvcc_packet_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,21 @@ shaka.cea.DtvccPacket = class {
}
this.pos_ += numBlocks;
}

/**
* Rewinds the provided number of blocks in the buffer.
* @param {number} numBlocks
* @throws {!shaka.util.Error}
*/
rewind(numBlocks) {
if (this.pos_ - numBlocks < 0) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.BUFFER_READ_OUT_OF_BOUNDS);
}
this.pos_ -= numBlocks;
}
};

/**
Expand Down
29 changes: 29 additions & 0 deletions test/cea/cea708_service_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,35 @@ describe('Cea708Service', () => {
expect(captions).toEqual(expectedCaptions);
});


it('decodes multibyte unstyled caption text', () => {
const controlCodes = [
...defineWindow,
// Series of C0 control codes that add multi-byte text.
0x18, 0xb9, 0xd9, 0x18, 0xb7, 0xce, // 맙, 럎
];

const packet1 = createCea708PacketFromBytes(controlCodes, startTime);
const packet2 = createCea708PacketFromBytes(hideWindow, endTime);

const text = '맙럎';
const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '',
serviceNumber, windowId, rowCount, colCount, anchorId);
topLevelCue.nestedCues = [
CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text),
];

const expectedCaptions = [
{
stream,
cue: topLevelCue,
},
];

const captions = getCaptionsFromPackets(service, packet1, packet2);
expect(captions).toEqual(expectedCaptions);
});

it('setPenLocation sets the pen location correctly', () => {
const controlCodes = [
...defineWindow,
Expand Down

0 comments on commit 5b13332

Please sign in to comment.