Skip to content

Commit

Permalink
Issue/88-outstanding-608-prs (#92)
Browse files Browse the repository at this point in the history
* Create extractCea608DataFromSample.ts
* Create seiHelpers.ts
* Update extractCea608DataFromSample.ts
* Update seiHelpers.ts
* Update getCharForByte.ts
* ported changes from video-dev/hls.js#6427
* Create SccParser.ts
* Update Cta608Parser.ts

---------

Signed-off-by: Agajan Jumakuliyev <[email protected]>
  • Loading branch information
agajassi authored Jun 5, 2024
1 parent 1212f80 commit 2e5a554
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Implement CEA 608/708 parser [#62](https://github.com/streaming-video-technology-alliance/common-media-library/issues/62)
- Integrate outstanding 608 PR [#88](https://github.com/streaming-video-technology-alliance/common-media-library/issues/88)


## [0.6.4] - 2024-03-04
Expand Down
119 changes: 65 additions & 54 deletions lib/src/cta/608/Cta608Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class Cta608Parser {
private currentChannel: Channels = 0;
private cmdHistory: CmdHistory = createCmdHistory();
private logger: CaptionsLogger;
private lastTime: number | null = null;

constructor(field: SupportedField, out1: any, out2: any) {
const logger = (this.logger = new CaptionsLogger());
Expand All @@ -83,41 +84,68 @@ export class Cta608Parser {
* @param byteList - The list of bytes
*/
addData(time: number | null, byteList: number[]): void {
let cmdFound: boolean;
let a: number;
let b: number;
let charsFound: number[] | boolean | null = false;

this.lastTime = time;
this.logger.time = time;

for (let i = 0; i < byteList.length; i += 2) {
a = byteList[i] & 0x7f;
b = byteList[i + 1] & 0x7f;
const a = byteList[i] & 0x7f;
const b = byteList[i + 1] & 0x7f;
let cmdFound: boolean = false;
let charsFound: number[] | null = null;

if (this.lastTime !== null) {
time = this.lastTime + 0.5 * i * 1001 / 30000;
this.logger.time = time;
}

if (a === 0 && b === 0) {
continue;
}
else {
this.logger.log(
VerboseLevel.DATA,
'[' +
numArrayToHexArray([byteList[i], byteList[i + 1]]) +
'] -> (' +
numArrayToHexArray([a, b]) +
')',
() =>
'[' +
numArrayToHexArray([byteList[i], byteList[i + 1]]) +
'] -> (' +
numArrayToHexArray([a, b]) +
')',
);
}
cmdFound = this.parseCmd(a, b);

if (!cmdFound) {
cmdFound = this.parseMidrow(a, b);
}
const cmdHistory = this.cmdHistory;
const isControlCode = a >= 0x10 && a <= 0x1f;
if (isControlCode) {
// Skip redundant control codes
if (hasCmdRepeated(a, b, cmdHistory)) {
setLastCmd(null, null, cmdHistory);
this.logger.log(
VerboseLevel.DEBUG,
() =>
'Repeated command (' +
numArrayToHexArray([a, b]) +
') is dropped',
);
continue;
}
setLastCmd(a, b, this.cmdHistory);

if (!cmdFound) {
cmdFound = this.parsePAC(a, b);
}
cmdFound = this.parseCmd(a, b);

if (!cmdFound) {
cmdFound = this.parseBackgroundAttributes(a, b);
if (!cmdFound) {
cmdFound = this.parseMidrow(a, b);
}

if (!cmdFound) {
cmdFound = this.parsePAC(a, b);
}

if (!cmdFound) {
cmdFound = this.parseBackgroundAttributes(a, b);
}
}
else {
setLastCmd(null, null, cmdHistory);
}

if (!cmdFound) {
Expand All @@ -139,10 +167,11 @@ export class Cta608Parser {
if (!cmdFound && !charsFound) {
this.logger.log(
VerboseLevel.WARNING,
"Couldn't parse cleaned data " +
numArrayToHexArray([a, b]) +
' orig: ' +
numArrayToHexArray([byteList[i], byteList[i + 1]]),
() =>
"Couldn't parse cleaned data " +
numArrayToHexArray([a, b]) +
' orig: ' +
numArrayToHexArray([byteList[i], byteList[i + 1]]),
);
}
}
Expand All @@ -156,7 +185,7 @@ export class Cta608Parser {
* @returns True if a command was found
*/
private parseCmd(a: number, b: number): boolean {
const { cmdHistory } = this;

const cond1 =
(a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) &&
b >= 0x20 &&
Expand All @@ -166,15 +195,6 @@ export class Cta608Parser {
return false;
}

if (hasCmdRepeated(a, b, cmdHistory)) {
setLastCmd(null, null, cmdHistory);
this.logger.log(
VerboseLevel.DEBUG,
'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped',
);
return true;
}

const chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2;
const channel = this.channels[chNr] as Cta608Channel;

Expand Down Expand Up @@ -232,7 +252,7 @@ export class Cta608Parser {
// a == 0x17 || a == 0x1F
channel.ccTO(b - 0x20);
}
setLastCmd(a, b, cmdHistory);

this.currentChannel = chNr;
return true;
}
Expand Down Expand Up @@ -269,7 +289,7 @@ export class Cta608Parser {
channel.ccMIDROW(b);
this.logger.log(
VerboseLevel.DEBUG,
'MIDROW (' + numArrayToHexArray([a, b]) + ')',
() => 'MIDROW (' + numArrayToHexArray([a, b]) + ')',
);
return true;
}
Expand All @@ -285,7 +305,6 @@ export class Cta608Parser {
*/
private parsePAC(a: number, b: number): boolean {
let row: number;
const cmdHistory = this.cmdHistory;

const case1 =
((a >= 0x11 && a <= 0x17) || (a >= 0x19 && a <= 0x1f)) &&
Expand All @@ -296,11 +315,6 @@ export class Cta608Parser {
return false;
}

if (hasCmdRepeated(a, b, cmdHistory)) {
setLastCmd(null, null, cmdHistory);
return true; // Repeated commands are dropped (once)
}

const chNr: Channels = a <= 0x17 ? 1 : 2;

if (b >= 0x40 && b <= 0x5f) {
Expand All @@ -315,7 +329,6 @@ export class Cta608Parser {
return false;
}
channel.setPAC(this.interpretPAC(row, b));
setLastCmd(a, b, cmdHistory);
this.currentChannel = chNr;
return true;
}
Expand Down Expand Up @@ -389,7 +402,7 @@ export class Cta608Parser {
}
if (charCode1 >= 0x11 && charCode1 <= 0x13) {
// Special character
let oneCode;
let oneCode: number;
if (charCode1 === 0x11) {
oneCode = b + 0x50;
}
Expand All @@ -402,23 +415,22 @@ export class Cta608Parser {

this.logger.log(
VerboseLevel.INFO,
"Special char '" +
getCharForByte(oneCode) +
"' in channel " +
channelNr,
() =>
"Special char '" +
getCharForByte(oneCode) +
"' in channel " +
channelNr,
);
charCodes = [oneCode];
}
else if (a >= 0x20 && a <= 0x7f) {
charCodes = b === 0 ? [a] : [a, b];
}
if (charCodes) {
const hexCodes = numArrayToHexArray(charCodes);
this.logger.log(
VerboseLevel.DEBUG,
'Char codes = ' + hexCodes.join(','),
() => 'Char codes = ' + numArrayToHexArray(charCodes).join(','),
);
setLastCmd(a, b, this.cmdHistory);
}
return charCodes;
}
Expand Down Expand Up @@ -457,7 +469,6 @@ export class Cta608Parser {
const chNr: Channels = a <= 0x17 ? 1 : 2;
const channel: Cta608Channel = this.channels[chNr] as Cta608Channel;
channel.setBkgData(bkgData);
setLastCmd(a, b, this.cmdHistory);
return true;
}

Expand All @@ -471,7 +482,7 @@ export class Cta608Parser {
channel.reset();
}
}
this.cmdHistory = createCmdHistory();
setLastCmd(null, null, this.cmdHistory);
}

/**
Expand Down
109 changes: 109 additions & 0 deletions lib/src/cta/608/SccParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2015-2016, DASH Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* 2. Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
function SccParser(processor: any, field: number | any = 1) {
let hasHeader = false;
let nrLinesParsed = 0;

function parse(text: string): void {
const lines = text.split(/\r?\n/);
nrLinesParsed = 0;

if (lines[0] === 'Scenarist_SCC V1.0') {
hasHeader = true;
nrLinesParsed++;
}

for (let l = 1; l < lines.length; l += 2) {
if (lines[l] !== '') {
break; // Every second line should be empty
}
nrLinesParsed++;
const lineData = parseDataLine(lines[l + 1]);
if (lineData === null) {
break;
}
nrLinesParsed++;
processor.addData(lineData[0], lineData[1]);
}
}

function parseDataLine(line: string): [number, number[]] | null {
if (!line) {
return null;
}

const lineParts = line.split(/\s+/);
const timeData = lineParts[0];
const ceaData: number[] = [];

for (let i = 1; i < lineParts.length; i++) {
const fourHexChars = lineParts[i];
const a = parseInt(fourHexChars.substring(0, 2), 16);
const b = parseInt(fourHexChars.substring(2, 4), 16);
ceaData.push(a, b);
}

const time = timeConverter(timeData);
return [time, ceaData];
}

function timeConverter(smpteTs: string): number {
const parts = smpteTs.split(':');
if (parts.length === 3) {
const lastParts = parts[2].split(';');
parts[2] = lastParts[0];
const frames = parseInt(lastParts[1], 10);
return (30 * (60 * (60 * parseInt(parts[0], 10) + parseInt(parts[1], 10)) + parseInt(parts[2], 10)) + frames) * 1001 / 30000;
}
return 0; // in case if format is incorrect
}

function getHeaderStatus() {
return hasHeader;
}

function getField() {
return field;
}

function getLinesParsed() {
return nrLinesParsed;
}

return {
parse,
getHeaderStatus,
getField,
getLinesParsed,
};
}

export { SccParser };
Loading

0 comments on commit 2e5a554

Please sign in to comment.