Skip to content

Commit 5b13332

Browse files
authored
fix(CEA): Fix multi byte language support in CEA-708 (#7837)
Fixes #7829
1 parent 8c6def4 commit 5b13332

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

lib/cea/cea708_service.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ shaka.cea.Cea708Service = class {
6262
// Control codes are in 1 of 4 logical groups:
6363
// CL (C0, C2), CR (C1, C3), GL (G0, G2), GR (G1, G2).
6464
if (controlCode >= 0x00 && controlCode <= 0x1f) {
65-
return this.handleC0_(controlCode, pts);
65+
return this.handleC0_(dtvccPacket, controlCode, pts);
6666
} else if (controlCode >= 0x80 && controlCode <= 0x9f) {
6767
return this.handleC1_(dtvccPacket, controlCode, pts);
6868
} else if (controlCode >= 0x1000 && controlCode <= 0x101f) {
@@ -155,17 +155,42 @@ shaka.cea.Cea708Service = class {
155155

156156
/**
157157
* Handles C0 group data.
158+
* @param {!shaka.cea.DtvccPacket} dtvccPacket
158159
* @param {number} controlCode
159160
* @param {number} pts
160161
* @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
161162
* @private
162163
*/
163-
handleC0_(controlCode, pts) {
164+
handleC0_(dtvccPacket, controlCode, pts) {
164165
// All these commands pertain to the current window, so ensure it exists.
165166
if (!this.currentWindow_) {
166167
return null;
167168
}
168169

170+
if (controlCode == 0x18) {
171+
const firstByte = dtvccPacket.readByte().value;
172+
const secondByte = dtvccPacket.readByte().value;
173+
174+
const isTextBlock = (b) => {
175+
return (b >= 0x20 && b <= 0x7f) || (b >= 0xa0 && b <= 0xff);
176+
};
177+
178+
if (isTextBlock(firstByte) && isTextBlock(secondByte)) {
179+
const toHexString = (byteArray) => {
180+
return byteArray.map((byte) => {
181+
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
182+
}).join('');
183+
};
184+
const unicode = toHexString([firstByte, secondByte]);
185+
// Takes a unicode hex string and creates a single character.
186+
const char = String.fromCharCode(parseInt(unicode, 16));
187+
this.currentWindow_.setCharacter(char);
188+
return null;
189+
} else {
190+
dtvccPacket.rewind(2);
191+
}
192+
}
193+
169194
const window = this.currentWindow_;
170195
let parsedClosedCaption = null;
171196

lib/cea/dtvcc_packet_builder.js

+15
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,21 @@ shaka.cea.DtvccPacket = class {
161161
}
162162
this.pos_ += numBlocks;
163163
}
164+
165+
/**
166+
* Rewinds the provided number of blocks in the buffer.
167+
* @param {number} numBlocks
168+
* @throws {!shaka.util.Error}
169+
*/
170+
rewind(numBlocks) {
171+
if (this.pos_ - numBlocks < 0) {
172+
throw new shaka.util.Error(
173+
shaka.util.Error.Severity.CRITICAL,
174+
shaka.util.Error.Category.TEXT,
175+
shaka.util.Error.Code.BUFFER_READ_OUT_OF_BOUNDS);
176+
}
177+
this.pos_ -= numBlocks;
178+
}
164179
};
165180

166181
/**

test/cea/cea708_service_unit.js

+29
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,35 @@ describe('Cea708Service', () => {
123123
expect(captions).toEqual(expectedCaptions);
124124
});
125125

126+
127+
it('decodes multibyte unstyled caption text', () => {
128+
const controlCodes = [
129+
...defineWindow,
130+
// Series of C0 control codes that add multi-byte text.
131+
0x18, 0xb9, 0xd9, 0x18, 0xb7, 0xce, // 맙, 럎
132+
];
133+
134+
const packet1 = createCea708PacketFromBytes(controlCodes, startTime);
135+
const packet2 = createCea708PacketFromBytes(hideWindow, endTime);
136+
137+
const text = '맙럎';
138+
const topLevelCue = CeaUtils.createWindowedCue(startTime, endTime, '',
139+
serviceNumber, windowId, rowCount, colCount, anchorId);
140+
topLevelCue.nestedCues = [
141+
CeaUtils.createDefaultCue(startTime, endTime, /* payload= */ text),
142+
];
143+
144+
const expectedCaptions = [
145+
{
146+
stream,
147+
cue: topLevelCue,
148+
},
149+
];
150+
151+
const captions = getCaptionsFromPackets(service, packet1, packet2);
152+
expect(captions).toEqual(expectedCaptions);
153+
});
154+
126155
it('setPenLocation sets the pen location correctly', () => {
127156
const controlCodes = [
128157
...defineWindow,

0 commit comments

Comments
 (0)