diff --git a/708-caption-logger.js b/708-caption-logger.js new file mode 100644 index 00000000..7850f41c --- /dev/null +++ b/708-caption-logger.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const mp4 = require('./lib/mp4'); +const file = fs.readFileSync(process.argv[2] || './bad-708.ts'); + +// https://github.com/google/ExoPlayer/blob/16b51d689c6a1a5ec327d1977a5fe8de9fefe41e/library/core/src/main/java/com/google/android/exoplayer2/text/cea/Cea708Decoder.java#L529 +const transmuxer = new mp4.Transmuxer(); + +// Setting the BMDT to ensure that captions and id3 tags are not +// time-shifted by this value when they are output and instead are +// zero-based +transmuxer.setBaseMediaDecodeTime(100000); + +transmuxer.on('data', function(data) { + if (data.captions) { + console.log(data.captions); + } +}); + +transmuxer.push(file); +transmuxer.flush(); diff --git a/lib/m2ts/caption-stream.js b/lib/m2ts/caption-stream.js index 0a6f472f..cca49fcd 100644 --- a/lib/m2ts/caption-stream.js +++ b/lib/m2ts/caption-stream.js @@ -17,6 +17,97 @@ // Link To Transport // ----------------- +const handleC0 = function(command) { + // Nul + if (command === 0x00) { + // do nothing + //console.log('c0 NUL') + } else if (command === 0x03) { + //console.log('c0 EXT'); + } else if (command === 0x08) { + //console.log('c0 Backspace'); + process.stdout.moveCursor(-1); + process.stdout.clearLine(1) + } else if (command === 0x0C) { + //console.log('c0 Formfeed'); + } else if (command === 0x0D) { + //console.log('c0 CarriageReturn'); + } else if (command === 0x0E) { + //console.log('c0 ClearLine'); + } else { + if (command >= 0x11 && command <= 0x17) { + //console.log('c0 EXT1'); + } else if (command >= 0x18 && command <= 0x1f) { + //console.log('c0 P16'); + } else { + //console.log('c0 invalid command') + } + } + +}; + +const handleC1 = function(command) { + if (command >= 0x80 && command <= 0x87) { + //console.log('C1 set current window CW' + (command - 0x80)); + } else if (command === 0x88) { + //console.log('C1 CLW (clearWindows)'); + return 1; + } else if (command === 0x89) { + //console.log('C1 DSW (displayWindows)'); + return 1; + } else if (command === 0x8A) { + //console.log('C1 HDW (hideWindows)'); + return 1; + } else if (command === 0x8b) { + //console.log('C1 TGW (toggleWindows)'); + return 1; + } else if (command === 0x8c) { + //console.log('C1 DLW (deleteWindows)'); + return 1; + } else if (command === 0x8D) { + //console.log('C1 DLY (delay)'); + return 1; + } else if (command === 0x8E) { + //console.log('C1 DLC (delay cancel)'); + } else if (command === 0x8F) { + //console.log('C1 RST (reset)'); + } else if (command === 0x90) { + //console.log('C1 SPA (set pen attributes)'); + return 2; + } else if (command === 0x91) { + // console.log('C1 SPC (set pen color)') + return 3; + } else if (command === 0x92) { + // console.log('C1 SPL (set pen location)'); + return 2; + } else if (command === 0x97) { + // console.log('C1 SWA (set window attributes)'); + return 4; + } else if (command >= 0x98 && command <= 0x9F) { + // console.log('C1 DF' + (command - 0x98) + '(define window)'); + return 6; + } else { + // console.log('C1 invalid command'); + } +}; + +const handleG0 = function(command) { + let str = String.fromCharCode(command & 0xFF); + + if (command === 0x7F) { + str = String.fromCharCode(0x266a); + } + + //console.log('G0 ' + command.toString(16) + ' ' + str ); + process.stdout.write(str); +}; + +const handleG1 = function(command) { + //console.log('G1 ' + command.toString(16) + ' ' + String.fromCharCode(command)); + process.stdout.write(String.fromCharCode(command)); +}; + + var Stream = require('../utils/stream'); var cea708Parser = require('../tools/caption-packet-parser'); @@ -493,6 +584,7 @@ Cea708Stream.prototype.add708Bytes = function(packet) { var data = packet.ccData; var byte0 = data >>> 8; var byte1 = data & 0xff; + debugger; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec // that service blocks will always line up with byte pairs. @@ -513,7 +605,7 @@ Cea708Stream.prototype.push708Packet = function() { var b = packetData[i++]; packet708.seq = b >> 6; - packet708.sizeCode = b & 0x3f; // 0b00111111; + packet708.sizeCode = (b & 0x3f) * 2 - 1; // 0b00111111; for (; i < packetData.length; i++) { b = packetData[i++]; @@ -529,7 +621,7 @@ Cea708Stream.prototype.push708Packet = function() { this.pushServiceBlock(serviceNum, i, blockSize); if (blockSize > 0) { - i += blockSize - 1; + i += blockSize; } } }; @@ -548,16 +640,46 @@ Cea708Stream.prototype.push708Packet = function() { */ Cea708Stream.prototype.pushServiceBlock = function(serviceNum, start, size) { var b; + var dw; var i = start; var packetData = this.current708Packet.data; var service = this.services[serviceNum]; + debugger; + if (!service) { service = this.initService(serviceNum, i); } for (; i < start + size && i < packetData.length; i++) { b = packetData[i]; + let skip = 0; + + // extended commands + if (b === 0x10) { + b = packetData[i++]; + if (b <= 0x1f) { + console.log('cl group c2', dw.toString(16)); + } else if (b >= 0x20 && b <= 0x7f) { + console.log('gl group g2', dw.toString(16)); + } else if (b >= 0x80 && dw <= 0x9f) { + console.log('cr group c3', dw.toString(16)); + } else if (dw >= 0xa0 && dw <= 0xff) { + console.log('gr group g3', dw.toString(16)); + } + } else if (b <= 0x1f) { + skip = handleC0(b); + } else if (b <= 0x7f) { + skip = handleG0(b); + } else if (b <= 0x9f) { + skip = handleC1(b); + } else if (b <= 0xff) { + skip = handleG1(b); + } + if (skip) { + i += skip; + } + continue; if (within708TextBlock(b)) { i = this.handleText(i, service); diff --git a/lib/tools/caption-packet-parser.js b/lib/tools/caption-packet-parser.js index b188c478..0e5ed91e 100644 --- a/lib/tools/caption-packet-parser.js +++ b/lib/tools/caption-packet-parser.js @@ -122,6 +122,7 @@ var parseUserData = function(sei) { var parseCaptionPackets = function(pts, userData) { var results = [], i, count, offset, data; + // if this is just filler, return immediately if (!(userData[0] & 0x40)) { return results; @@ -142,6 +143,7 @@ var parseCaptionPackets = function(pts, userData) { results.push(data); } } + return results; };