Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(CEA): Fix multi byte language support in CEA-708 #7837

Merged
merged 1 commit into from
Jan 7, 2025
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
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
Loading