| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380 |
- 'use strict';
- var segments = require('data-files!segments');
- var
- window = require('global/window'),
- captionStream,
- m2ts = require('../lib/m2ts'),
- mp4 = require('../lib/mp4'),
- QUnit = require('qunit'),
- seiNalUnitGenerator = require('./utils/sei-nal-unit-generator'),
- makeSeiFromCaptionPacket = seiNalUnitGenerator.makeSeiFromCaptionPacket,
- makeSeiFromMultipleCaptionPackets = seiNalUnitGenerator.makeSeiFromMultipleCaptionPackets,
- characters = seiNalUnitGenerator.characters,
- packetHeader708 = seiNalUnitGenerator.packetHeader708,
- displayWindows708 = seiNalUnitGenerator.displayWindows708,
- cc708PinkUnderscore = require('./utils/cc708-pink-underscore'),
- cc708Korean = require('./utils/cc708-korean'),
- sintelCaptions = segments['sintel-captions.ts'](),
- mixed608708Captions = require('./utils/mixed-608-708-captions.js'),
- multiChannel608Captions = segments['multi-channel-608-captions.ts']();
- QUnit.module('Caption Stream', {
- beforeEach: function() {
- captionStream = new m2ts.CaptionStream();
- }
- });
- QUnit.test('parses SEIs messages larger than 255 bytes', function(assert) {
- var packets = [], data;
- captionStream.ccStreams_[0].push = function(packet) {
- packets.push(packet);
- };
- // set data channel 1 active for field 1
- captionStream.activeCea608Channel_[0] = 0;
- data = new Uint8Array(268);
- data[0] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[1] = 0xff; // payload_size
- data[2] = 0x0d; // payload_size
- data[3] = 181; // itu_t_t35_country_code
- data[4] = 0x00;
- data[5] = 0x31; // itu_t_t35_provider_code
- data[6] = 0x47;
- data[7] = 0x41;
- data[8] = 0x39;
- data[9] = 0x34; // user_identifier, "GA94"
- data[10] = 0x03; // user_data_type_code, 0x03 is cc_data
- data[11] = 0xc1; // process_cc_data, cc_count
- data[12] = 0xff; // reserved
- data[13] = 0xfc; // cc_valid, cc_type (608, field 1)
- data[14] = 0xff; // cc_data_1 with parity bit set
- data[15] = 0x0e; // cc_data_2 without parity bit set
- data[16] = 0xff; // marker_bits
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: data
- });
- captionStream.flush();
- assert.equal(packets.length, 1, 'parsed a caption');
- });
- QUnit.test('parses SEIs containing multiple messages', function(assert) {
- var packets = [], data;
- captionStream.ccStreams_[0].push = function(packet) {
- packets.push(packet);
- };
- // set data channel 1 active for field 1
- captionStream.activeCea608Channel_[0] = 0;
- data = new Uint8Array(22);
- data[0] = 0x01; // payload_type !== user_data_registered_itu_t_t35
- data[1] = 0x04; // payload_size
- data[6] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[7] = 0x0d; // payload_size
- data[8] = 181; // itu_t_t35_country_code
- data[9] = 0x00;
- data[10] = 0x31; // itu_t_t35_provider_code
- data[11] = 0x47;
- data[12] = 0x41;
- data[13] = 0x39;
- data[14] = 0x34; // user_identifier, "GA94"
- data[15] = 0x03; // user_data_type_code, 0x03 is cc_data
- data[16] = 0xc1; // process_cc_data, cc_count
- data[17] = 0xff; // reserved
- data[18] = 0xfc; // cc_valid, cc_type (608, field 1)
- data[19] = 0xff; // cc_data_1 with parity bit set
- data[20] = 0x0e; // cc_data_2 without parity bit set
- data[21] = 0xff; // marker_bits
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: data
- });
- captionStream.flush();
- assert.equal(packets.length, 1, 'parsed a caption');
- });
- QUnit.test('parses SEIs containing multiple messages of type user_data_registered_itu_t_t35', function(assert) {
- var packets = [], data;
- captionStream.ccStreams_[0].push = function(packet) {
- packets.push(packet);
- };
- // set data channel 1 active for field 1
- captionStream.activeCea608Channel_[0] = 0;
- data = new Uint8Array(33);
- data[0] = 0x01; // payload_type !== user_data_registered_itu_t_t35
- data[1] = 0x04; // payload_size
- // https://www.etsi.org/deliver/etsi_ts/101100_101199/101154/01.11.01_60/ts_101154v011101p.pdf#page=117
- data[6] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[7] = 0x09; // payload_size
- data[8] = 181; // itu_t_t35_country_code
- data[9] = 0x00;
- data[10] = 0x31; // itu_t_t35_provider_code
- data[11] = 0x44;
- data[12] = 0x54;
- data[13] = 0x47;
- data[14] = 0x31; // user_identifier, "DTG1"
- data[15] = 0x11; // zero_bit (b7), active_format_flag (b6), reserved (b5-b0)
- data[16] = 0xF0; // reserved (b7-b4), active_format (b3-b0)
- data[17] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[18] = 0x0d; // payload_size
- data[19] = 181; // itu_t_t35_country_code
- data[20] = 0x00;
- data[21] = 0x31; // itu_t_t35_provider_code
- data[22] = 0x47;
- data[23] = 0x41;
- data[24] = 0x39;
- data[25] = 0x34; // user_identifier, "GA94"
- data[26] = 0x03; // user_data_type_code, 0x03 is cc_data
- data[27] = 0xc1; // process_cc_data, cc_count
- data[28] = 0xff; // reserved
- data[29] = 0xfc; // cc_valid, cc_type (608, field 1)
- data[30] = 0xff; // cc_data_1 with parity bit set
- data[31] = 0x0e; // cc_data_2 without parity bit set
- data[32] = 0xff; // marker_bits
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: data
- });
- captionStream.flush();
- assert.equal(packets.length, 1, 'ignored DTG1 payload, parsed a GA94 caption');
- });
- QUnit.test('does not throw error if only invalid payloads', function(assert) {
- var packets = [], data;
- captionStream.ccStreams_[0].push = function(packet) {
- packets.push(packet);
- };
- // set data channel 1 active for field 1
- captionStream.activeCea608Channel_[0] = 0;
- data = new Uint8Array(33);
- data[0] = 0x01; // payload_type !== user_data_registered_itu_t_t35
- data[1] = 0x04; // payload_size
- // https://www.etsi.org/deliver/etsi_ts/101100_101199/101154/01.11.01_60/ts_101154v011101p.pdf#page=117
- data[6] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[7] = 0x09; // payload_size
- data[8] = 181; // itu_t_t35_country_code
- data[9] = 0x00;
- data[10] = 0x31; // itu_t_t35_provider_code
- data[11] = 0x44;
- data[12] = 0x54;
- data[13] = 0x47;
- data[14] = 0x31; // user_identifier, "DTG1"
- data[15] = 0x11; // zero_bit (b7), active_format_flag (b6), reserved (b5-b0)
- data[16] = 0xF0; // reserved (b7-b4), active_format (b3-b0)
- data[17] = 0x04; // payload_type === user_data_registered_itu_t_t35
- data[18] = 0x0d; // payload_size
- data[19] = 181; // itu_t_t35_country_code
- data[20] = 0x00;
- data[21] = 0x31; // itu_t_t35_provider_code
- data[22] = 0x47;
- data[23] = 0x41;
- data[24] = 0x39;
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: data
- });
- captionStream.flush();
- assert.equal(packets.length, 0, 'ignored DTG1 payload');
- });
- QUnit.test('ignores SEIs that do not have type user_data_registered_itu_t_t35', function(assert) {
- var captions = [];
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: new Uint8Array([
- 0x05 // payload_type !== user_data_registered_itu_t_t35
- ])
- });
- assert.equal(captions.length, 0, 'ignored the unknown payload type');
- });
- QUnit.test('parses a minimal example of caption data', function(assert) {
- var packets = [];
- captionStream.ccStreams_[0].push = function(packet) {
- packets.push(packet);
- };
- // set data channel 1 active for field 1
- captionStream.activeCea608Channel_[0] = 0;
- captionStream.push({
- nalUnitType: 'sei_rbsp',
- escapedRBSP: new Uint8Array([
- 0x04, // payload_type === user_data_registered_itu_t_t35
- 0x0d, // payload_size
- 181, // itu_t_t35_country_code
- 0x00, 0x31, // itu_t_t35_provider_code
- 0x47, 0x41, 0x39, 0x34, // user_identifier, "GA94"
- 0x03, // user_data_type_code, 0x03 is cc_data
- // 110 00001
- 0xc1, // process_cc_data, cc_count
- 0xff, // reserved
- // 1111 1100
- 0xfc, // cc_valid, cc_type (608, field 1)
- 0xff, // cc_data_1 with parity bit set
- 0x0e, // cc_data_2 without parity bit set
- 0xff // marker_bits
- ])
- });
- captionStream.flush();
- assert.equal(packets.length, 1, 'parsed a caption packet');
- });
- QUnit.test('can be parsed from a segment', function(assert) {
- var transmuxer = new mp4.Transmuxer(),
- captions = [];
- // 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) {
- captions = captions.concat(data.captions);
- }
- });
- transmuxer.push(sintelCaptions);
- transmuxer.flush();
- assert.equal(captions.length, 2, 'parsed two captions');
- assert.equal(captions[0].content[0].text.indexOf('ASUKA'), 0, 'parsed the start of the first caption');
- assert.ok(captions[0].content[0].text.indexOf('Japanese') > 0, 'parsed the end of the first caption');
- assert.equal(captions[0].startTime, 1, 'parsed the start time');
- assert.equal(captions[0].endTime, 4, 'parsed the end time');
- });
- QUnit.test('dispatches caption track information', function(assert) {
- var transmuxer = new mp4.Transmuxer(),
- captions = [],
- captionStreams = {};
- // 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) {
- captions = captions.concat(data.captions);
- for (var trackId in data.captionStreams) {
- captionStreams[trackId] = true;
- }
- }
- });
- transmuxer.push(multiChannel608Captions);
- transmuxer.flush();
- assert.deepEqual(captionStreams, {CC1: true, CC3: true}, 'found captions in CC1 and CC3');
- assert.equal(captions.length, 4, 'parsed eight captions');
- assert.equal(captions[0].content[0].text, 'être une période de questions', 'parsed the text of the first caption in CC3');
- assert.equal(captions[1].content[0].text, 'PERIOD, FOLKS.', 'parsed the text of the first caption in CC1');
- });
- QUnit.test('sorting is fun', function(assert) {
- var packets, captions, seiNals;
- packets = [
- // Send another command so that the second EOC isn't ignored
- { pts: 10 * 1000, ccData: 0x1420, type: 0 },
- // RCL, resume caption loading
- { pts: 1000, ccData: 0x1420, type: 0 },
- // 'test string #1'
- { pts: 1000, ccData: characters('te'), type: 0 },
- { pts: 1000, ccData: characters('st'), type: 0 },
- { pts: 1000, ccData: characters(' s'), type: 0 },
- // 'test string #2'
- { pts: 10 * 1000, ccData: characters('te'), type: 0 },
- { pts: 10 * 1000, ccData: characters('st'), type: 0 },
- { pts: 10 * 1000, ccData: characters(' s'), type: 0 },
- // 'test string #1' continued
- { pts: 1000, ccData: characters('tr'), type: 0 },
- { pts: 1000, ccData: characters('in'), type: 0 },
- { pts: 1000, ccData: characters('g '), type: 0 },
- { pts: 1000, ccData: characters('#1'), type: 0 },
- // 'test string #2' continued
- { pts: 10 * 1000, ccData: characters('tr'), type: 0 },
- { pts: 10 * 1000, ccData: characters('in'), type: 0 },
- { pts: 10 * 1000, ccData: characters('g '), type: 0 },
- { pts: 10 * 1000, ccData: characters('#2'), type: 0 },
- // EOC, End of Caption. End display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 },
- // EOC, End of Caption. Finished transmitting, begin display
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { pts: 20 * 1000, ccData: 0x1420, type: 0 },
- // EOC, End of Caption. End display
- { pts: 20 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- seiNals = packets.map(makeSeiFromCaptionPacket);
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].content[0].text, 'test string #1', 'parsed caption 1');
- assert.equal(captions[1].content[0].text, 'test string #2', 'parsed caption 2');
- });
- QUnit.test('drops duplicate segments', function(assert) {
- var packets, captions, seiNals;
- packets = [
- {
- pts: 1000, dts: 1000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL (resume caption loading)
- {ccData: 0x1420, type: 0 }, // RCL, duplicate as per spec
- {ccData: characters('te'), type: 0 },
- {ccData: characters('st'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters(' s'), type: 0 },
- {ccData: characters('tr'), type: 0 },
- {ccData: characters('in'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('g '), type: 0 },
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters(' s'), type: 0 },
- {ccData: characters('tr'), type: 0 },
- {ccData: characters('in'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('g '), type: 0 },
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- },
- {
- pts: 4000, dts: 4000, captions: [
- {ccData: 0x142f, type: 0 }, // EOC (end of caption), mark display start
- {ccData: 0x142f, type: 0 }, // EOC, duplicate as per spec
- {ccData: 0x142f, type: 0 }, // EOC, mark display end and flush
- {ccData: 0x142f, type: 0 } // EOC, duplicate as per spec
- ]
- }
- ];
- captions = [];
- seiNals = packets.map(makeSeiFromMultipleCaptionPackets);
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 1, 'detected one caption');
- assert.equal(captions[0].content[0].text, 'test string data', 'parsed caption properly');
- });
- QUnit.test('drops duplicate segments with multi-segment DTS values', function(assert) {
- var packets, captions, seiNals;
- packets = [
- {
- pts: 1000, dts: 1000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL (resume caption loading)
- {ccData: 0x1420, type: 0 }, // RCL, duplicate as per spec
- {ccData: characters('te'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters('st'), type: 0 },
- {ccData: characters(' s'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters('tr'), type: 0 },
- {ccData: characters('in'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('g '), type: 0 },
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters(' s'), type: 0 },
- {ccData: characters('tr'), type: 0 },
- {ccData: characters('in'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('g '), type: 0 },
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters(' s'), type: 0 },
- {ccData: characters('tu'), type: 0 },
- {ccData: characters('ff'), type: 0 }
- ]
- },
- {
- pts: 4000, dts: 4000, captions: [
- {ccData: 0x142f, type: 0 }, // EOC (end of caption)
- // EOC not duplicated for robustness testing
- {ccData: 0x1420, type: 0 } // RCL (resume caption loading)
- ]
- },
- {
- pts: 5000, dts: 5000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL, duplicated as per spec
- {ccData: characters(' a'), type: 0 },
- {ccData: characters('nd'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters(' e'), type: 0 },
- {ccData: characters('ve'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('n '), type: 0 },
- {ccData: characters('mo'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('re'), type: 0 },
- {ccData: characters(' t'), type: 0 }
- ]
- },
- {
- pts: 5000, dts: 5000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL, duplicated as per spec
- {ccData: characters(' a'), type: 0 },
- {ccData: characters('nd'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters(' e'), type: 0 },
- {ccData: characters('ve'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('n '), type: 0 },
- {ccData: characters('mo'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('re'), type: 0 },
- {ccData: characters(' t'), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('ex'), type: 0 },
- {ccData: characters('t '), type: 0 }
- ]
- },
- {
- pts: 6000, dts: 6000, captions: [
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- },
- {
- pts: 7000, dts: 7000, captions: [
- {ccData: characters(' h'), type: 0 },
- {ccData: characters('er'), type: 0 }
- ]
- },
- {
- pts: 8000, dts: 8000, captions: [
- {ccData: characters('e!'), type: 0 },
- {ccData: 0x142f, type: 0 }, // EOC (end of caption), mark display start
- {ccData: 0x142f, type: 0 }, // EOC, duplicated as per spec
- {ccData: 0x142f, type: 0 } // EOC, mark display end and flush
- // EOC not duplicated for robustness testing
- ]
- }
- ];
- captions = [];
- seiNals = packets.map(makeSeiFromMultipleCaptionPackets);
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].content[0].text, 'test string data stuff', 'parsed caption properly');
- assert.equal(captions[1].content[0].text, 'and even more text data here!', 'parsed caption properly');
- });
- QUnit.test('doesn\'t ignore older segments if reset', function(assert) {
- var firstPackets, secondPackets, captions, seiNals1, seiNals2;
- firstPackets = [
- {
- pts: 11000, dts: 11000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL (resume caption loading)
- {ccData: 0x1420, type: 0 }, // RCL, duplicated as per spec
- {ccData: characters('te'), type: 0 }
- ]
- },
- {
- pts: 12000, dts: 12000, captions: [
- {ccData: characters('st'), type: 0 },
- {ccData: characters(' s'), type: 0 }
- ]
- },
- {
- pts: 12000, dts: 12000, captions: [
- {ccData: characters('tr'), type: 0 },
- {ccData: characters('in'), type: 0 }
- ]
- },
- {
- pts: 13000, dts: 13000, captions: [
- {ccData: characters('g '), type: 0 },
- {ccData: characters('da'), type: 0 },
- {ccData: characters('ta'), type: 0 }
- ]
- }
- ];
- secondPackets = [
- {
- pts: 1000, dts: 1000, captions: [
- {ccData: 0x1420, type: 0 }, // RCL (resume caption loading)
- {ccData: 0x1420, type: 0 }, // RCL, duplicated as per spec
- {ccData: characters('af'), type: 0 }
- ]
- },
- {
- pts: 2000, dts: 2000, captions: [
- {ccData: characters('te'), type: 0 },
- {ccData: characters('r '), type: 0 },
- {ccData: characters('re'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('se'), type: 0 },
- {ccData: characters('t '), type: 0 },
- {ccData: characters('da'), type: 0 }
- ]
- },
- {
- pts: 3000, dts: 3000, captions: [
- {ccData: characters('ta'), type: 0 },
- {ccData: characters('!!'), type: 0 }
- ]
- },
- {
- pts: 4000, dts: 4000, captions: [
- {ccData: 0x142f, type: 0 }, // EOC (end of caption), mark display start
- {ccData: 0x142f, type: 0 }, // EOC, duplicated as per spec
- {ccData: 0x142f, type: 0 } // EOC, mark display end and flush
- // EOC not duplicated for robustness testing
- ]
- }
- ];
- captions = [];
- seiNals1 = firstPackets.map(makeSeiFromMultipleCaptionPackets);
- seiNals2 = secondPackets.map(makeSeiFromMultipleCaptionPackets);
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals1.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.latestDts_, 13000, 'DTS is tracked correctly');
- captionStream.reset();
- assert.equal(captionStream.latestDts_, null, 'DTS tracking was reset');
- seiNals2.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.latestDts_, 4000, 'DTS is tracked correctly');
- assert.equal(captions.length, 1, 'detected one caption');
- assert.equal(captions[0].content[0].text, 'after reset data!!', 'parsed caption properly');
- });
- QUnit.test('extracts all theoretical caption channels', function(assert) {
- var captions = [];
- captionStream.ccStreams_.forEach(function(cc) {
- cc.on('data', function(caption) {
- captions.push(caption);
- });
- });
- // RU2 = roll-up, 2 rows
- // CR = carriage return
- var packets = [
- { pts: 1000, type: 0, ccData: 0x1425 }, // RU2 (sets CC1)
- { pts: 2000, type: 0, ccData: characters('1a') }, // CC1
- { pts: 3000, type: 0, ccData: 0x1c25 }, // RU2 (sets CC2)
- { pts: 4000, type: 1, ccData: 0x1525 }, // RU2 (sets CC3)
- { pts: 5000, type: 1, ccData: characters('3a') }, // CC3
- // this next one tests if active channel is tracked per-field
- // instead of globally
- { pts: 6000, type: 0, ccData: characters('2a') }, // CC2
- { pts: 7000, type: 1, ccData: 0x1d25 }, // RU2 (sets CC4)
- { pts: 8000, type: 1, ccData: characters('4a') }, // CC4
- { pts: 9000, type: 1, ccData: characters('4b') }, // CC4
- { pts: 10000, type: 0, ccData: 0x142d }, // CR (sets + flushes CC1)
- { pts: 11000, type: 0, ccData: 0x1c2d }, // CR (sets + flushes CC2)
- { pts: 12000, type: 0, ccData: 0x1425 }, // RU2 (sets CC1)
- { pts: 13000, type: 0, ccData: characters('1b') }, // CC1
- { pts: 14000, type: 0, ccData: characters('1c') }, // CC1
- { pts: 15000, type: 0, ccData: 0x142d }, // CR (sets + flushes CC1)
- { pts: 16000, type: 1, ccData: 0x152d }, // CR (sets + flushes CC3)
- { pts: 17000, type: 1, ccData: 0x1d2d }, // CR (sets + flushes CC4)
- { pts: 18000, type: 0, ccData: 0x1c25 }, // RU2 (sets CC2)
- { pts: 19000, type: 0, ccData: characters('2b') }, // CC2
- { pts: 20000, type: 0, ccData: 0x1c2d } // CR (sets + flushes CC2)
- ];
- var seiNals = packets.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 6, 'got all captions');
- assert.equal(captions[0].content[0].text, '1a', 'cc1 first row');
- assert.equal(captions[1].content[0].text, '2a', 'cc2 first row');
- assert.equal(captions[2].content[0].text, '1a', 'cc1 first row');
- assert.equal(captions[2].content[1].text, '1b1c', 'cc1 second row');
- assert.equal(captions[3].content[0].text, '3a', 'cc3 first row');
- assert.equal(captions[4].content[0].text, '4a4b', 'cc4 first row');
- assert.equal(captions[5].content[0].text, '2a', 'cc2 first row');
- assert.equal(captions[5].content[1].text, '2b', 'cc2 second row');
- });
- QUnit.test('drops data until first command that sets activeChannel for a field', function(assert) {
- var captions = [];
- captionStream.ccStreams_.forEach(function(cc) {
- cc.on('data', function(caption) {
- captions.push(caption);
- });
- });
- var packets = [
- // test that packets in same field and same data channel are dropped
- // before a control code that sets the data channel
- { pts: 0 * 1000, ccData: characters('no'), type: 0 },
- { pts: 0 * 1000, ccData: characters('t '), type: 0 },
- { pts: 0 * 1000, ccData: characters('th'), type: 0 },
- { pts: 0 * 1000, ccData: characters('is'), type: 0 },
- // EOC (end of caption), sets CC1
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL (resume caption loading)
- { pts: 1 * 1000, ccData: 0x1420, type: 0 },
- // EOC, if data wasn't dropped this would dispatch a caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 },
- // RCL
- { pts: 3 * 1000, ccData: 0x1420, type: 0 },
- { pts: 4 * 1000, ccData: characters('fi'), type: 0 },
- { pts: 4 * 1000, ccData: characters('el'), type: 0 },
- { pts: 4 * 1000, ccData: characters('d0'), type: 0 },
- // EOC, mark display start
- { pts: 5 * 1000, ccData: 0x142f, type: 0 },
- // EOC, duplicated as per spec
- { pts: 5 * 1000, ccData: 0x142f, type: 0 },
- // EOC, mark display end and flush
- { pts: 6 * 1000, ccData: 0x142f, type: 0 },
- // EOC not duplicated cuz not necessary
- // now switch to field 1 and test that packets in the same field
- // but DIFFERENT data channel are dropped
- { pts: 7 * 1000, ccData: characters('or'), type: 1 },
- { pts: 7 * 1000, ccData: characters(' t'), type: 1 },
- { pts: 7 * 1000, ccData: characters('hi'), type: 1 },
- { pts: 7 * 1000, ccData: characters('s.'), type: 1 },
- // EOC (end of caption, sets CC4)
- { pts: 8 * 1000, ccData: 0x1d2f, type: 1 },
- // RCL (resume caption loading)
- { pts: 8 * 1000, ccData: 0x1d20, type: 1 },
- // EOC, if data wasn't dropped this would dispatch a caption
- { pts: 9 * 1000, ccData: 0x1d2f, type: 1 },
- // RCL
- { pts: 10 * 1000, ccData: 0x1d20, type: 1 },
- { pts: 11 * 1000, ccData: characters('fi'), type: 1 },
- { pts: 11 * 1000, ccData: characters('el'), type: 1 },
- { pts: 11 * 1000, ccData: characters('d1'), type: 1 },
- // EOC, mark display start
- { pts: 12 * 1000, ccData: 0x1d2f, type: 1 },
- // EOC, duplicated as per spec
- { pts: 12 * 1000, ccData: 0x1d2f, type: 1 },
- // EOC, mark display end and flush
- { pts: 13 * 1000, ccData: 0x1d2f, type: 1 }
- // EOC not duplicated cuz not necessary
- ];
- var seiNals = packets.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 2, 'received 2 captions');
- assert.equal(captions[0].content[0].text, 'field0', 'received only confirmed field0 data');
- assert.equal(captions[0].stream, 'CC1', 'caption went to right channel');
- assert.equal(captions[1].content[0].text, 'field1', 'received only confirmed field1 data');
- assert.equal(captions[1].stream, 'CC4', 'caption went to right channel');
- });
- QUnit.test('clears buffer and drops data until first command that sets activeChannel after reset', function(assert) {
- var firstPackets, secondPackets, captions, seiNals1, seiNals2;
- captions = [];
- firstPackets = [
- // RCL (resume caption loading), CC1
- { pts: 1 * 1000, ccData: 0x1420, type: 0 },
- { pts: 2 * 1000, ccData: characters('fi'), type: 0 },
- { pts: 2 * 1000, ccData: characters('el'), type: 0 },
- { pts: 2 * 1000, ccData: characters('d0'), type: 0 },
- // EOC (end of caption), swap text to displayed memory
- { pts: 3 * 1000, ccData: 0x142f, type: 0 },
- { pts: 4 * 1000, ccData: characters('fi'), type: 0 },
- { pts: 4 * 1000, ccData: characters('el'), type: 0 },
- { pts: 4 * 1000, ccData: characters('d0'), type: 0 },
- // RCL (resume caption loading), CC4
- { pts: 5 * 1000, ccData: 0x1d20, type: 1 },
- { pts: 6 * 1000, ccData: characters('fi'), type: 1 },
- { pts: 6 * 1000, ccData: characters('el'), type: 1 },
- { pts: 6 * 1000, ccData: characters('d1'), type: 1 },
- // EOC (end of caption), swap text to displayed memory
- { pts: 7 * 1000, ccData: 0x1d2f, type: 1 },
- { pts: 8 * 1000, ccData: characters('fi'), type: 1 },
- { pts: 8 * 1000, ccData: characters('el'), type: 1 },
- { pts: 8 * 1000, ccData: characters('d1'), type: 1 }
- ];
- secondPackets = [
- // following packets are dropped
- { pts: 9 * 1000, ccData: characters('no'), type: 0 },
- { pts: 9 * 1000, ccData: characters('t '), type: 0 },
- { pts: 9 * 1000, ccData: characters('th'), type: 0 },
- { pts: 9 * 1000, ccData: characters('is'), type: 0 },
- { pts: 10 * 1000, ccData: characters('or'), type: 1 },
- { pts: 10 * 1000, ccData: characters(' t'), type: 1 },
- { pts: 10 * 1000, ccData: characters('hi'), type: 1 },
- { pts: 10 * 1000, ccData: characters('s.'), type: 1 },
- // EOC (end of caption), sets CC1
- { pts: 11 * 1000, ccData: 0x142f, type: 0 },
- // RCL (resume caption loading), CC1
- { pts: 11 * 1000, ccData: 0x1420, type: 0 },
- // EOC, sets CC4
- { pts: 12 * 1000, ccData: 0x1d2f, type: 1 },
- // RCL, CC4
- { pts: 12 * 1000, ccData: 0x1d20, type: 1 },
- // EOC, CC1, would dispatch caption if packets weren't ignored
- { pts: 13 * 1000, ccData: 0x142f, type: 0 },
- // RCL, CC1
- { pts: 13 * 1000, ccData: 0x1420, type: 0 },
- // EOC, CC4, would dispatch caption if packets weren't ignored
- { pts: 14 * 1000, ccData: 0x1d2f, type: 1 },
- // RCL, CC4
- { pts: 14 * 1000, ccData: 0x1d20, type: 1 },
- { pts: 18 * 1000, ccData: characters('bu'), type: 0 },
- { pts: 18 * 1000, ccData: characters('t '), type: 0 },
- { pts: 18 * 1000, ccData: characters('th'), type: 0 },
- { pts: 18 * 1000, ccData: characters('is'), type: 0 },
- { pts: 19 * 1000, ccData: characters('an'), type: 1 },
- { pts: 19 * 1000, ccData: characters('d '), type: 1 },
- { pts: 19 * 1000, ccData: characters('th'), type: 1 },
- { pts: 19 * 1000, ccData: characters('is'), type: 1 },
- // EOC (end of caption), CC1, mark caption 1 start
- { pts: 20 * 1000, ccData: 0x142f, type: 0 },
- // EOC, CC1, duplicated as per spec
- { pts: 20 * 1000, ccData: 0x142f, type: 0 },
- // EOC, CC1, mark caption 1 end and dispatch
- { pts: 21 * 1000, ccData: 0x142f, type: 0 },
- // No duplicate EOC cuz not necessary
- // EOC, CC4, mark caption 2 start
- { pts: 22 * 1000, ccData: 0x1d2f, type: 1 },
- // EOC, CC4, duplicated as per spec
- { pts: 22 * 1000, ccData: 0x1d2f, type: 1 },
- // EOC, CC4, mark caption 2 end and dispatch
- { pts: 23 * 1000, ccData: 0x1d2f, type: 1 }
- // No duplicate EOC cuz not necessary
- ];
- seiNals1 = firstPackets.map(makeSeiFromCaptionPacket);
- seiNals2 = secondPackets.map(makeSeiFromCaptionPacket);
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals1.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14].text, 'field0',
- 'there is data in non-displayed memory for field 0 before reset');
- assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14].text, 'field1',
- 'there is data in non-displayed memory for field 1 before reset');
- assert.equal(captionStream.ccStreams_[0].displayed_[14].text, 'field0',
- 'there is data in displayed memory for field 0 before reset');
- assert.equal(captionStream.ccStreams_[3].displayed_[14].text, 'field1',
- 'there is data in displayed memory for field 1 before reset');
- captionStream.reset();
- assert.equal(captionStream.ccStreams_[0].nonDisplayed_[14].text, '',
- 'there is no data in non-displayed memory for field 0 after reset');
- assert.equal(captionStream.ccStreams_[3].nonDisplayed_[14].text, '',
- 'there is no data in non-displayed memory for field 1 after reset');
- assert.equal(captionStream.ccStreams_[0].displayed_[14].text, '',
- 'there is no data in displayed memory for field 0 after reset');
- assert.equal(captionStream.ccStreams_[3].displayed_[14].text, '',
- 'there is no data in displayed memory for field 1 after reset');
- seiNals2.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 2, 'detected two captions');
- assert.equal(captions[0].content[0].text, 'but this', 'parsed caption properly');
- assert.equal(captions[0].stream, 'CC1', 'caption went to right channel');
- assert.equal(captions[1].content[0].text, 'and this', 'parsed caption properly');
- assert.equal(captions[1].stream, 'CC4', 'caption went to right channel');
- });
- QUnit.test("don't mess up 608 captions when 708 are present", function(assert) {
- var captions = [];
- captionStream.ccStreams_.forEach(function(cc) {
- cc.on('data', function(caption) {
- captions.push(caption);
- });
- });
- var seiNals = mixed608708Captions.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 3, 'parsed three captions');
- // first caption stream
- assert.equal(captions[0].content[0].text, 'BUT IT\'S NOT SUFFERING', 'first stream: parsed first content text correctly');
- assert.equal(captions[0].content[1].text, 'RIGHW.', 'first stream: parsed second content text correctly');
- // there is also bad data in the captions, but the null ascii character is removed
- // second caption stream
- assert.equal(captions[1].content[0].text, 'IT\'S NOT A THREAT TO ANYBODY.', 'second stream: parsed content text correctly');
- // third stream
- assert.equal(captions[2].content[0].text, 'WE TRY NOT TO PUT AN ANIMAL DOWN', 'third stream: parsed first content text correctly');
- assert.equal(captions[2].content[1].text, 'IF WE DON\'T HAVE TO.', 'third stream: parsed second content text correctly');
- });
- QUnit.test("both 608 and 708 captions are available by default", function(assert) {
- var cc608 = [];
- var cc708 = [];
- captionStream.on('data', function(caption) {
- if (caption.stream === 'CC1') {
- cc608.push(caption);
- } else {
- cc708.push(caption);
- }
- });
- var seiNals = mixed608708Captions.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(cc608.length, 3, 'parsed three 608 cues');
- assert.equal(cc708.length, 3, 'parsed three 708 cues');
- });
- QUnit.test("708 parsing can be turned off", function(assert) {
- captionStream.reset();
- captionStream = new m2ts.CaptionStream({
- parse708captions: false
- });
- var cc608 = [];
- var cc708 = [];
- captionStream.on('data', function(caption) {
- if (caption.stream === 'CC1') {
- cc608.push(caption);
- } else {
- cc708.push(caption);
- }
- });
- var seiNals = mixed608708Captions.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(cc608.length, 3, 'parsed three 608 cues');
- assert.equal(cc708.length, 0, 'did not parse any 708 cues');
- });
- QUnit.test('ignores XDS and Text packets', function(assert) {
- var captions = [];
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RCL, resume caption loading, CC3
- { pts: 1000, ccData: 0x1520, type: 1 },
- { pts: 2000, ccData: characters('hi'), type: 1 },
- // EOC, End of Caption
- { pts: 3000, ccData: 0x152f, type: 1 },
- // Send another command so that the second EOC isn't ignored
- { pts: 3000, ccData: 0x152f, type: 1 },
- // EOC, End of Caption
- { pts: 4000, ccData: 0x152f, type: 1 },
- // ENM, Erase Non-Displayed Memory
- { pts: 4000, ccData: 0x152e, type: 1 },
- // RCL, resume caption loading, CC1
- { pts: 5000, ccData: 0x1420, type: 0 }
- ].map(makeSeiFromCaptionPacket).forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.activeCea608Channel_[0], 0, 'field 1: CC1 is active');
- assert.equal(captionStream.activeCea608Channel_[1], 0, 'field 2: CC3 is active');
- [
- // TR, text restart, CC1
- { pts: 5000, ccData: 0x142a, type: 0 },
- { pts: 6000, ccData: characters('tx'), type: 0 }
- ].map(makeSeiFromCaptionPacket).forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.activeCea608Channel_[0], null, 'field 1: disabled');
- assert.equal(captionStream.activeCea608Channel_[1], 0, 'field 2: CC3 is active');
- [
- // EOC, End of Caption
- { pts: 7000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { pts: 7000, ccData: 0x142f, type: 0 },
- // EOC, End of Caption
- { pts: 8000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading, CC3
- { pts: 9000, ccData: 0x1520, type: 1 },
- // XDS start, "current" class, program identification number type
- { pts: 10000, ccData: 0x0101, type: 1 },
- { pts: 11000, ccData: characters('oh'), type: 1 },
- // XDS end
- { pts: 12000, ccData: 0x0f00, type: 1 }
- ].map(makeSeiFromCaptionPacket).forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captionStream.activeCea608Channel_[0], 0, 'field 1: CC1 is active');
- assert.equal(captionStream.activeCea608Channel_[1], null, 'field 2: disabled');
- [
- // EOC, End of Caption
- { pts: 13000, ccData: 0x152f, type: 1 },
- // Send another command so that the second EOC isn't ignored
- { pts: 13000, ccData: 0x152f, type: 1 }
- ].map(makeSeiFromCaptionPacket).forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.equal(captions.length, 1, 'only parsed real caption');
- assert.equal(captions[0].content[0].text, 'hi', 'caption is correct');
- });
- // Full character translation tests are below for Cea608Stream, they just only
- // test support for CC1. See those tests and the source code for more about the
- // mechanics of special and extended characters.
- QUnit.test('special and extended character codes work regardless of field and data channel', function(assert) {
- var packets, seiNals, captions = [];
- packets = [
- // RU2 (roll-up, 2 rows), CC2
- { ccData: 0x1c25, type: 0 },
- // ®
- { ccData: 0x1930, type: 0 },
- // CR (carriage return), CC2, flush caption
- { ccData: 0x1c2d, type: 0 },
- // RU2, CC3
- { ccData: 0x1525, type: 1 },
- // "
- { ccData: 0x2200, type: 1 },
- // «
- { ccData: 0x123e, type: 1 },
- // CR, CC3, flush caption
- { ccData: 0x152d, type: 1 },
- // RU2, CC4
- { ccData: 0x1d25, type: 1 },
- // "
- { ccData: 0x2200, type: 1 },
- // »
- { ccData: 0x1a3f, type: 1 },
- // CR, CC4, flush caption
- { ccData: 0x1d2d, type: 1 }
- ];
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seiNals = packets.map(makeSeiFromCaptionPacket);
- seiNals.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.deepEqual(captions[0].content[0].text, String.fromCharCode(0xae), 'CC2 special character correct');
- assert.deepEqual(captions[1].content[0].text, String.fromCharCode(0xab), 'CC3 extended character correct');
- assert.deepEqual(captions[2].content[0].text, String.fromCharCode(0xbb), 'CC4 extended character correct');
- });
- QUnit.test('number of roll up rows takes precedence over base row command', function(assert) {
- var captions = [];
- var packets = [
- // RU2 (roll-up, 2 rows), CC1
- { type: 0, ccData: 0x1425 },
- // RU2, CC1
- { type: 0, ccData: 0x1425 },
- // PAC: row 1 (sets base row to row 1)
- { type: 0, ccData: 0x1170 },
- // PAC: row 1
- { type: 0, ccData: 0x1170 },
- // -
- { type: 0, ccData: 0x2d00 },
- // CR
- { type: 0, ccData: 0x14ad },
- // CR
- { type: 0, ccData: 0x14ad },
- // RU3 (roll-up, 3 rows), CC1
- { type: 0, ccData: 0x1426 },
- // RU3, CC1
- { type: 0, ccData: 0x1426 },
- // PAC, row 11
- { type: 0, ccData: 0x13d0 },
- // PAC, row 11
- { type: 0, ccData: 0x13d0 },
- // so
- { type: 0, ccData: 0x736f },
- // CR
- { type: 0, ccData: 0x14ad },
- // CR
- { type: 0, ccData: 0x14ad }
- ];
- var seis;
- captionStream.on('data', function(caption) {
- captions.push(caption);
- });
- seis = packets.map(makeSeiFromCaptionPacket);
- seis.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.deepEqual(captions[0].content[0].text, '-', 'RU2 caption is correct');
- assert.deepEqual(captions[1].content[0].text, '-', 'first RU3 caption is correct');
- assert.deepEqual(captions[1].content[1].text, 'so', 'second RU3 caption is correct');
- packets = [
- // switching from row 11 to 0
- // PAC: row 0 (sets base row to row 0)
- { type: 0, ccData: 0x1140 },
- // PAC: row 0
- { type: 0, ccData: 0x1140 },
- // CR
- { type: 0, ccData: 0x14ad },
- // CR
- { type: 0, ccData: 0x14ad }
- ];
- seis = packets.map(makeSeiFromCaptionPacket);
- seis.forEach(captionStream.push, captionStream);
- captionStream.flush();
- assert.deepEqual(captions[2].content[0].text, '-', 'first RU3 caption is correct');
- assert.deepEqual(captions[2].content[1].text, 'so', 'second RU3 caption is correct');
- });
- var cea608Stream;
- QUnit.module('CEA 608 Stream', {
- beforeEach: function() {
- cea608Stream = new m2ts.Cea608Stream();
- }
- });
- QUnit.skip('filters null data', function(assert) {
- assert.ok(false, 'not implemented');
- });
- QUnit.skip('removes parity bits', function(assert) {
- assert.ok(false, 'not implemented');
- });
- QUnit.test('converts non-ASCII character codes to ASCII', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // ASCII exceptions
- { ccData: 0x2a5c, type: 0 },
- { ccData: 0x5e5f, type: 0 },
- { ccData: 0x607b, type: 0 },
- { ccData: 0x7c7d, type: 0 },
- { ccData: 0x7e7f, type: 0 },
- // EOC, End of Caption
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption, clear the display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text,
- String.fromCharCode(0xe1, 0xe9, 0xed, 0xf3, 0xfa, 0xe7, 0xf7, 0xd1, 0xf1, 0x2588),
- 'translated non-standard characters');
- });
- QUnit.test('properly handles special character codes', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // Special characters as defined by CEA-608
- // see the CHARACTER_TRANSLATION hash in lib/m2ts/caption-stream for the
- // mapping table
- { ccData: 0x1130, type: 0 },
- { ccData: 0x1131, type: 0 },
- { ccData: 0x1132, type: 0 },
- { ccData: 0x1133, type: 0 },
- { ccData: 0x1134, type: 0 },
- { ccData: 0x1135, type: 0 },
- { ccData: 0x1136, type: 0 },
- { ccData: 0x1137, type: 0 },
- { ccData: 0x1138, type: 0 },
- { ccData: 0x1139, type: 0 },
- { ccData: 0x113a, type: 0 },
- { ccData: 0x113b, type: 0 },
- { ccData: 0x113c, type: 0 },
- { ccData: 0x113d, type: 0 },
- { ccData: 0x113e, type: 0 },
- { ccData: 0x113f, type: 0 },
- // EOC, End of Caption
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption, CC1, clear the display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text,
- String.fromCharCode(0xae, 0xb0, 0xbd, 0xbf, 0x2122, 0xa2, 0xa3, 0x266a,
- 0xe0, 0xa0, 0xe8, 0xe2, 0xea, 0xee, 0xf4, 0xfb),
- 'translated special characters');
- });
- QUnit.test('properly handles extended character codes', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // Extended characters are defined in CEA-608 as a standard character,
- // which is followed by an extended character, and the standard character
- // gets deleted.
- // see the CHARACTER_TRANSLATION hash in lib/m2ts/caption-stream for the
- // mapping table
- { ccData: 0x2200, type: 0 },
- { ccData: 0x123e, type: 0 },
- { ccData: 0x4c41, type: 0 },
- { ccData: 0x1230, type: 0 },
- { ccData: 0x2d4c, type: 0 },
- { ccData: 0x4100, type: 0 },
- { ccData: 0x1338, type: 0 },
- { ccData: 0x204c, type: 0 },
- { ccData: 0x417d, type: 0 },
- { ccData: 0x4400, type: 0 },
- { ccData: 0x1137, type: 0 },
- { ccData: 0x2200, type: 0 },
- { ccData: 0x123f, type: 0 },
- // EOC, End of Caption
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption, clear the display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '«LÀ-LÅ LAÑD♪»',
- 'translated special characters');
- });
- QUnit.test('pop-on mode', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // 'hi'
- { ccData: characters('hi'), type: 0 },
- // EOC, End of Caption. Finished transmitting, begin display
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption. End display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.deepEqual(captions[0], {
- startPts: 1000,
- endPts: 10 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: 'hi'
- }],
- stream: 'CC1'
- }, 'parsed the caption');
- });
- QUnit.test('ignores null characters', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // 'mu'
- { ccData: characters('mu'), type: 0 },
- // null characters
- { ccData: 0x0000, type: 0 },
- // ' x'
- { ccData: characters(' x'), type: 0 },
- // EOC, End of Caption. Finished transmitting, begin display
- { pts: 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption. End display
- { pts: 10 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.deepEqual(captions[0], {
- startPts: 1000,
- endPts: 10 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: 'mu x'
- }],
- stream: 'CC1'
- }, 'ignored null characters');
- });
- QUnit.test('recognizes the Erase Displayed Memory command', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // '01'
- { ccData: characters('01'), type: 0 },
- // EOC, End of Caption. Finished transmitting, display '01'
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // EDM, Erase Displayed Memory
- { pts: 1.5 * 1000, ccData: 0x142c, type: 0 },
- // '23'
- { ccData: characters('23'), type: 0 },
- // EOC, End of Caption. Display '23'
- { pts: 2 * 1000, ccData: 0x142f, type: 0 },
- // '34'
- { ccData: characters('34'), type: 0 },
- // EOC, End of Caption. Display '34'
- { pts: 3 * 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0},
- // EOC, End of Caption
- { pts: 4 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 3, 'detected three captions');
- assert.deepEqual(captions[0], {
- startPts: 1 * 1000,
- endPts: 1.5 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '01'
- }],
- stream: 'CC1'
- }, 'parsed the first caption');
- assert.deepEqual(captions[1], {
- startPts: 2 * 1000,
- endPts: 3 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '23'
- }],
- stream: 'CC1'
- }, 'parsed the second caption');
- assert.deepEqual(captions[2], {
- startPts: 3 * 1000,
- endPts: 4 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '34'
- }],
- stream: 'CC1'
- }, 'parsed the third caption');
- });
- QUnit.test('correct content text is added to non-displayed memory for pop-on mode', function(assert) {
- var captions = [], packets;
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // '01'
- { ccData: characters('01'), type: 0 },
- // backspace
- { ccData: 0x1421, type: 0 },
- { ccData: characters('23'), type: 0 },
- // PAC: row 13, no indent
- { pts: 1 * 1000, ccData: 0x1370, type: 0 },
- { pts: 1 * 1000, ccData: characters('32'), type: 0 },
- // backspace
- { pts: 2 * 1000, ccData: 0x1421, type: 0 },
- { pts: 3 * 1000, ccData: characters('10'), type: 0 },
- // EOC, End of Caption
- { pts: 4 * 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption, flush caption
- { pts: 5 * 1000, ccData: 0x142f, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].content[0].text, '310', 'first content text');
- assert.equal(captions[0].content[1].text, '023', 'second content text');
- });
- QUnit.test('backspaces on cleared memory are no-ops', function(assert) {
- var captions = [], packets;
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420 },
- // backspace
- { ccData: 0x1421 },
- // EOC, End of Caption. Finished transmitting, display '01'
- { pts: 1 * 1000, ccData: 0x142f }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 0, 'no captions detected');
- });
- QUnit.test('recognizes the Erase Non-Displayed Memory command', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // '01'
- { ccData: characters('01'), type: 0 },
- // ENM, Erase Non-Displayed Memory
- { ccData: 0x142e, type: 0 },
- { ccData: characters('23'), type: 0 },
- // EOC, End of Caption. Finished transmitting, display '23'
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- packets.forEach(cea608Stream.push, cea608Stream);
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected one caption');
- assert.deepEqual(captions[0], {
- startPts: 1 * 1000,
- endPts: 2 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '23'
- }],
- stream: 'CC1'
- }, 'cleared the non-displayed memory');
- });
- QUnit.test('ignores unrecognized commands', function(assert) {
- var packets, captions;
- packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // a row-9 magenta command, which is not supported
- { ccData: 0x1f4c, type: 0 },
- // '01'
- { ccData: characters('01'), type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '01', 'skipped the unrecognized commands');
- });
- QUnit.skip('applies preamble address codes', function(assert) {
- assert.ok(false, 'not implemented');
- });
- QUnit.skip('applies mid-row colors', function(assert) {
- assert.ok(false, 'not implemented');
- });
- QUnit.test('applies mid-row underline', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- { ccData: characters('no'), type: 0 },
- // mid-row, white underline
- { ccData: 0x1121, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'no <u>yes.</u>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('applies mid-row italics', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- { ccData: characters('no'), type: 0 },
- // mid-row, italics
- { ccData: 0x112e, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'no <i>yes.</i>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('applies mid-row italics underline', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- { ccData: characters('no'), type: 0 },
- // mid-row, italics underline
- { ccData: 0x112f, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'no <i><u>yes.</u></i>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- // NOTE: With the exception of white italics PACs (the following two test
- // cases), PACs only have their underline attribute extracted and used
- QUnit.test('applies PAC underline', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- // PAC: row 15, white underline
- { ccData: 0x1461, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '<u>yes.</u>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('applies PAC white italics', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- // PAC: row 15, white italics
- { ccData: 0x146e, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '<i>yes.</i>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('applies PAC white italics underline', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- // PAC: row 15, white italics underline
- { ccData: 0x146f, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // CR (carriage return), dispatches caption
- { ccData: 0x142d, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '<u><i>yes.</i></u>', 'properly closed by CR');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('includes all caption text at PAC row change', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // PAC: row 14, white italics underlime
- { ccData: 0x144f, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // PAC: row 15, indent 0
- { ccData: 0x1470, type: 0 },
- { ccData: characters('no'), type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '<u><i>yes.</i></u>', 'first content text');
- assert.equal(captions[0].content[1].text, 'no', 'second content text');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('closes formatting at EOC', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // PAC: row 15, white italics underline
- { ccData: 0x146f, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // Send another command so that the second EOC isn't ignored
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, '<u><i>yes.</i></u>', 'properly closed by EOC');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('closes formatting at negating mid-row code', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { ccData: 0x1425, type: 0 },
- { ccData: characters('no'), type: 0 },
- // mid-row: italics underline
- { ccData: 0x112f, type: 0 },
- { ccData: characters('ye'), type: 0 },
- { ccData: characters('s.'), type: 0 },
- // mid-row: white
- { ccData: 0x1120, type: 0 },
- { ccData: characters('no'), type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- cea608Stream.flushDisplayed();
- assert.equal(captions[0].content[0].text, 'no <i><u>yes.</u></i> no', 'properly closed by negating mid-row code');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting is empty');
- });
- QUnit.test('roll-up display mode', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0},
- // '01'
- {
- pts: 1 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // CR, carriage return
- { pts: 3 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected one caption');
- assert.deepEqual(captions[0], {
- startPts: 0 * 1000,
- endPts: 3 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '01'
- }],
- stream: 'CC1'
- }, 'parsed the caption');
- captions = [];
- [ // RU4, roll-up captions 4 rows
- { ccdata: 0x1427, type: 0 },
- // '23'
- {
- pts: 4 * 1000,
- ccData: characters('23'),
- type: 0,
- stream: 'CC1'
- },
- // CR
- { pts: 5 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected another caption');
- assert.deepEqual(captions[0], {
- startPts: 3 * 1000,
- endPts: 5 * 1000,
- content: [
- {
- line: 14,
- position: 10,
- text: '01'
- },
- {
- line: 15,
- position: 10,
- text: '23'
- }
- ],
- stream: 'CC1'
- }, 'parsed the new caption and kept the caption up after the new caption');
- });
- QUnit.test('roll-up displays multiple rows simultaneously', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0,
- stream: 'CC1'
- },
- // CR, carriage return
- { pts: 1 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.deepEqual(captions[0], {
- startPts: 0 * 1000,
- endPts: 1 * 1000,
- content: [{
- line: 15,
- position: 10,
- text: '01'
- }],
- stream: 'CC1'
- }, 'created a caption for the first period');
- captions = [];
- [ // '23'
- {
- pts: 2 * 1000,
- ccData: characters('23'),
- type: 0,
- stream: 'CC1'
- },
- // CR, carriage return
- { pts: 3 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected another caption');
- assert.deepEqual(captions[0], {
- startPts: 1 * 1000,
- endPts: 3 * 1000,
- content: [
- {
- line: 14,
- position: 10,
- text: '01'
- },
- {
- line: 15,
- position: 10,
- text: '23'
- }
- ],
- stream: 'CC1'
- }, 'created the top and bottom rows after the shift up');
- captions = [];
- [ // '45'
- {
- pts: 4 * 1000,
- ccData: characters('45'),
- type: 0,
- stream: 'CC1'
- },
- // CR, carriage return
- { pts: 5 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected third caption');
- assert.deepEqual(captions[0], {
- startPts: 3 * 1000,
- endPts: 5 * 1000,
- "content": [
- {
- line: 14,
- position: 10,
- text: '23'
- },
- {
- line: 15,
- position: 10,
- text: '45'
- }
- ],
- stream: 'CC1'
- }, 'created the top and bottom rows after the shift up');
- });
- QUnit.test('the roll-up count can be changed on-the-fly', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0,
- stream: 'CC1'
- },
- // CR, carriage return
- { pts: 1 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- captions = [];
- [ // RU3, roll-up captions 3 rows
- { ccData: 0x1426, type: 0 },
- // CR, carriage return
- { pts: 2 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'still displaying a caption');
- captions = [];
- [ // RU4, roll-up captions 4 rows
- { ccData: 0x1427, type: 0 },
- // CR, carriage return
- { pts: 3 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'still displaying a caption');
- captions = [];
- // RU3, roll-up captions 3 rows
- cea608Stream.push({ ccdata: 0x1426, type: 0 });
- assert.equal(captions.length, 0, 'cleared the caption');
- });
- QUnit.test('switching to roll-up from pop-on wipes memories and flushes captions', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RCL (resume caption loading)
- { pts: 0 * 1000, ccData: 0x1420, type: 0 },
- { pts: 0 * 1000, ccData: characters('hi'), type: 0 },
- // EOC (end of caption), mark 1st caption start
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading
- { pts: 1 * 1000, ccData: 0x1420, type: 0 },
- { pts: 2 * 1000, ccData: characters('oh'), type: 0 },
- // EOC, mark 2nd caption start and flush 1st caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 },
- // RU2 (roll-up, 2 rows), flush 2nd caption
- { pts: 3 * 1000, ccData: 0x1425, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- var displayed = cea608Stream.displayed_.reduce(function(acc, val) {
- acc += val.text;
- return acc;
- }, '');
- var nonDisplayed = cea608Stream.nonDisplayed_.reduce(function(acc, val) {
- acc += val.text;
- return acc;
- }, '');
- assert.equal(captions.length, 2, 'both captions flushed');
- assert.equal(displayed, '', 'displayed memory is wiped');
- assert.equal(nonDisplayed, '', 'non-displayed memory is wiped');
- assert.deepEqual(captions[0], {
- startPts: 1000,
- endPts: 2000,
- content: [{
- line: 15,
- position: 10,
- text: 'hi',
- }],
- stream: 'CC1'
- }, 'first caption correct');
- assert.deepEqual(captions[1], {
- startPts: 2000,
- endPts: 3000,
- content: [{
- line: 15,
- position: 10,
- text: 'oh',
- }],
- stream: 'CC1'
- }, 'second caption correct');
- });
- QUnit.test('switching to roll-up from paint-on wipes memories and flushes captions', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RDC (resume direct captioning)
- { pts: 0 * 1000, ccData: 0x1429, type: 0 },
- { pts: 0 * 1000, ccData: characters('hi'), type: 0 },
- // RU2 (roll-up, 2 rows), flush displayed caption
- { pts: 1 * 1000, ccData: 0x1425, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- var displayed = cea608Stream.displayed_.reduce(function(acc, val) {
- acc += val.text;
- return acc;
- }, '');
- var nonDisplayed = cea608Stream.nonDisplayed_.reduce(function(acc, val) {
- acc += val.text;
- return acc;
- }, '');
- assert.equal(captions.length, 1, 'flushed caption');
- assert.equal(displayed, '', 'displayed memory is wiped');
- assert.equal(nonDisplayed, '', 'non-displayed memory is wiped');
- assert.deepEqual(captions[0], {
- startPts: 0,
- endPts: 1000,
- content: [{
- line: 15,
- position: 10,
- text: 'hi',
- }],
- stream: 'CC1'
- }, 'caption correct');
- });
- // NOTE: This should change to not wiping the display when caption
- // positioning is properly implemented
- QUnit.test('switching to paint-on from pop-on flushes display', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RCL (resume caption loading)
- { pts: 0 * 1000, ccData: 0x1420, type: 0 },
- // PAC: row 14, indent 0
- { pts: 0 * 1000, ccData: 0x1450, type: 0 },
- { pts: 0 * 1000, ccData: characters('hi'), type: 0 },
- // EOC (end of caption), mark caption start
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL
- { pts: 1 * 1000, ccData: 0x1420, type: 0 },
- // RDC (resume direct captioning), flush caption
- { pts: 2 * 1000, ccData: 0x1429, type: 0 },
- // PAC: row 14, indent 0
- { pts: 2 * 1000, ccData: 0x1450, type: 0 },
- // TO1 (tab offset 1 column)
- { pts: 2 * 1000, ccData: 0x1721, type: 0 },
- { pts: 3 * 1000, ccData: characters('io'), type: 0 },
- // EDM (erase displayed memory), flush paint-on caption
- { pts: 4 * 1000, ccData: 0x142c, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 2, 'detected 2 captions');
- assert.equal(captions[0].content[0].text, 'hi', 'pop-on caption received');
- assert.equal(captions[0].startPts, 1000, 'proper start pts');
- assert.equal(captions[0].endPts, 2000, 'proper end pts');
- assert.equal(captions[1].content[0].text, 'io', 'paint-on caption received');
- assert.equal(captions[1].startPts, 2000, 'proper start pts');
- assert.equal(captions[1].endPts, 4000, 'proper end pts');
- });
- QUnit.test('backspaces are reflected in the generated captions', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // backspace
- { ccData: 0x1421, type: 0 },
- {
- pts: 1 * 1000,
- ccData: characters('23'),
- type: 0
- },
- // CR, carriage return
- { pts: 1 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].content[0].text, '023', 'applied the backspace');
- });
- QUnit.test('backspaces can remove a caption entirely', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // backspace
- { ccData: 0x1421, type: 0 },
- // Send another command so that the backspace isn't
- // ignored as a duplicate command
- { ccData: 0x1425, type: 0 },
- // backspace
- { ccData: 0x1421, type: 0 },
- // CR, carriage return
- { pts: 1 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 0, 'no caption emitted');
- });
- QUnit.test('a second identical control code immediately following the first is ignored', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // '02'
- {
- pts: 1 * 1000,
- ccData: characters('02'),
- type: 0
- },
- // backspace
- { ccData: 0x1421, type: 0 },
- // backspace
- { ccData: 0x1421, type: 0 }, // duplicate is ignored
- // backspace
- { ccData: 0x1421, type: 0 },
- // CR, carriage return
- { pts: 2 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].content[0].text, '01', 'only two backspaces processed');
- });
- QUnit.test('a second identical control code separated by only padding from the first is ignored', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // '02'
- {
- pts: 1 * 1000,
- ccData: characters('02'),
- type: 0
- },
- // backspace
- { ccData: 0x1421, type: 0 },
- // padding
- { ccData: 0x0000, type: 0 },
- { ccData: 0x0000, type: 0 },
- { ccData: 0x0000, type: 0 },
- // backspace
- { pts: 2 * 1000, ccData: 0x1421, type: 0 }, // duplicate is ignored
- // CR, carriage return
- { pts: 3 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].content[0].text, '010', 'only one backspace processed');
- });
- QUnit.test('preamble address codes on same row are NOT converted into spaces', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // PAC: row 15, indent 0
- { ccData: 0x1470, type: 0 },
- // '02'
- {
- pts: 1 * 1000,
- ccData: characters('02'),
- type: 0
- },
- // CR, carriage return
- { pts: 2 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].content[0].text, '0102', 'PACs were NOT converted to space');
- });
- QUnit.test('generates correct content with PACs in pop-on mode', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // ENM, erase non-displayed memory
- { ccData: 0x142e, type: 0 },
- // PAC: row 12, indent 0
- { ccData: 0x1350, type: 0 },
- // text: TEST
- { ccData: 0x5445, type: 0 },
- { ccData: 0x5354, type: 0 },
- // PAC: row 14, indent 0
- { ccData: 0x1450, type: 0 },
- // text: STRING
- { ccData: 0x5354, type: 0 },
- { ccData: 0x5249, type: 0 },
- { ccData: 0x4e47, type: 0 },
- // PAC: row 15, indent 0
- { ccData: 0x1470, type: 0 },
- // text: DATA
- { ccData: 0x4441, type: 0 },
- { ccData: 0x5441, type: 0 },
- // EOC, end of caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // EOC, duplicated as per spec
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // EOC, dispatch caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].content[0].text, 'TEST', 'first content text');
- assert.equal(captions[0].content[1].text, 'STRING', 'second content text');
- assert.equal(captions[0].content[2].text, 'DATA', 'third content text');
- });
- QUnit.test('extracts real-world cc1 and cc3 channels', function(assert) {
- var cea608Stream1 = cea608Stream;
- var cea608Stream3 = new m2ts.Cea608Stream(1, 0);
- var captions = [];
- cea608Stream1.on('data', function(caption) {
- captions.push(caption);
- });
- cea608Stream3.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- { pts: 425316, type: 0, ccData: 5158 }, // RU3
- { pts: 431322, type: 0, ccData: 5165 }, // CR
- { pts: 440331, type: 0, ccData: 4944 }, // position 11,0
- { pts: 443334, type: 0, ccData: 20549 }, // PE
- { pts: 449340, type: 0, ccData: 21065 }, // RI
- { pts: 449340, type: 0, ccData: 0 }, // padding
- { pts: 452343, type: 0, ccData: 20292 }, // OD
- { pts: 458349, type: 0, ccData: 11264 }, // ,
- { pts: 458349, type: 0, ccData: 0 }, // padding
- { pts: 461352, type: 0, ccData: 0 }, // padding
- { pts: 467358, type: 0, ccData: 8192 }, // (space)
- { pts: 467358, type: 0, ccData: 17920 }, // F
- { pts: 470361, type: 0, ccData: 0 }, // padding
- { pts: 476367, type: 0, ccData: 0 }, // padding
- { pts: 476367, type: 0, ccData: 20300 }, // OL
- { pts: 479370, type: 0, ccData: 19283 }, // KS
- { pts: 485376, type: 0, ccData: 0 }, // padding
- { pts: 485376, type: 0, ccData: 11776 }, // .
- { pts: 674565, type: 0, ccData: 5158 }, // RU3
- { pts: 677568, type: 0, ccData: 5165 }, // CR
- { pts: 371262, type: 1, ccData: 5414 }, // RU3
- { pts: 377268, type: 1, ccData: 0 }, // padding
- { pts: 377268, type: 1, ccData: 4944 }, // position 11,0
- { pts: 380271, type: 1, ccData: 0 }, // padding
- { pts: 386277, type: 1, ccData: 4412 }, // ê
- { pts: 386277, type: 1, ccData: 0 }, // padding
- { pts: 389280, type: 1, ccData: 29810 }, // tr
- { pts: 395286, type: 1, ccData: 25888 }, // e(space)
- { pts: 395286, type: 1, ccData: 30062 }, // un
- { pts: 398289, type: 1, ccData: 25888 }, // e(space)
- { pts: 404295, type: 1, ccData: 28764 }, // pé
- { pts: 404295, type: 1, ccData: 29289 }, // ri
- { pts: 407298, type: 1, ccData: 28516 }, // od
- { pts: 413304, type: 1, ccData: 25856 }, // e
- { pts: 413304, type: 1, ccData: 0 }, // padding
- { pts: 443334, type: 1, ccData: 8292 }, // (space)d
- { pts: 449340, type: 1, ccData: 25888 }, // e(space)
- { pts: 449340, type: 1, ccData: 29045 }, // qu
- { pts: 452343, type: 1, ccData: 25971 }, // es
- { pts: 458349, type: 1, ccData: 29801 }, // ti
- { pts: 458349, type: 1, ccData: 28526 }, // on
- { pts: 461352, type: 1, ccData: 29440 }, // s
- { pts: 467358, type: 1, ccData: 5421 }, // CR
- { pts: 467358, type: 1, ccData: 0 }, // padding
- { pts: 470361, type: 1, ccData: 5414 }, // RU3
- { pts: 476367, type: 1, ccData: 0 } // padding
- ];
- packets.forEach(function(packet) {
- cea608Stream1.push(packet);
- cea608Stream3.push(packet);
- });
- var cc1 = {stream: 'CC1', content: [{ text: 'PERIOD, FOLKS.'}] };
- var cc3 = {stream: 'CC3', content: [{ text: 'être une période de questions' }] };
- assert.equal(captions.length, 2, 'caption emitted');
- assert.equal(captions[0].stream, cc1.stream, 'cc1 stream detected');
- assert.equal(captions[0].content[0].text, cc1.content[0].text, 'cc1 stream extracted successfully');
- assert.equal(captions[1].stream, cc3.stream, 'cc3 stream detected');
- assert.equal(captions[1].content[0].text, cc3.content[0].text, 'cc3 stream extracted successfully');
- });
- QUnit.test('backspaces stop at the beginning of the line', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { ccData: 0x1425, type: 0 },
- // '01'
- {
- pts: 0 * 1000,
- ccData: characters('01'),
- type: 0
- },
- // backspace
- { ccData: 0x1421, type: 0 },
- // Send another command so that the backspace isn't
- // ignored as a duplicate command
- { ccData: 0x1425, type: 0 },
- // backspace
- { ccData: 0x1421, type: 0 },
- // Send another command so that the backspace isn't
- // ignored as a duplicate command
- { ccData: 0x1425, type: 0 },
- // backspace
- { ccData: 0x1421, type: 0 },
- // CR, carriage return
- { pts: 1 * 1000, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 0, 'no caption emitted');
- });
- QUnit.test('reset works', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RU2, roll-up captions 2 rows
- { pts: 0, ccData: 0x1425, type: 0 },
- // mid-row: white underline
- { pts: 0, ccData: 0x1121, type: 0 },
- { pts: 0, ccData: characters('01'), type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- var buffer = cea608Stream.displayed_.map(function(row) {
- return row.text.trim();
- }).join('\n')
- .replace(/^\n+|\n+$/g, '');
- assert.equal(buffer, '<u>01', 'buffer is as expected');
- cea608Stream.reset();
- buffer = cea608Stream.displayed_
- .map(function(row) {
- return row.text.trim();
- })
- .join('\n')
- .replace(/^\n+|\n+$/g, '');
- assert.equal(buffer, '', 'displayed buffer reset successfully');
- assert.equal(cea608Stream.lastControlCode_, null, 'last control code reset successfully');
- assert.deepEqual(cea608Stream.formatting_, [], 'formatting was reset');
- });
- QUnit.test('paint-on mode', function(assert) {
- var packets, captions;
- packets = [
- // RDC, resume direct captioning, begin display
- { pts: 1000, ccData: 0x1429, type: 0 },
- { pts: 2000, ccData: characters('hi'), type: 0 },
- // EDM, erase displayed memory. Finish display, flush caption
- { pts: 3000, ccData: 0x142c, type: 0 }
- ];
- captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.deepEqual(captions[0], {
- startPts: 1000,
- endPts: 3000,
- content: [{
- line: 15,
- position: 10,
- text: 'hi',
- }],
- stream: 'CC1'
- }, 'parsed the caption');
- });
- QUnit.test('generates correct text from PACs in paint-on mode', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RDC, resume direct captioning
- { pts: 1000, ccData: 0x1429, type: 0 },
- // PAC: row 12, indent 0
- { pts: 1000, ccData: 0x1350, type: 0 },
- // text: TEST
- { pts: 2000, ccData: 0x5445, type: 0 },
- { pts: 2000, ccData: 0x5354, type: 0 },
- // PAC: row 14, indent 0
- { pts: 3000, ccData: 0x1450, type: 0 },
- // text: STRING
- { pts: 3000, ccData: 0x5354, type: 0 },
- { pts: 4000, ccData: 0x5249, type: 0 },
- { pts: 4000, ccData: 0x4e47, type: 0 },
- // PAC: row 15, indent 0
- { pts: 5000, ccData: 0x1470, type: 0 },
- // text: DATA
- { pts: 5000, ccData: 0x4441, type: 0 },
- { pts: 6000, ccData: 0x5441, type: 0 },
- // EDM, erase displayed memory. Finish display, flush caption
- { pts: 6000, ccData: 0x142c, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'caption emitted');
- assert.equal(captions[0].content[0].text, 'TEST', 'first content text');
- assert.equal(captions[0].content[1].text, 'STRING', 'second content text');
- assert.equal(captions[0].content[2].text, 'DATA', 'third content text');
- });
- QUnit.test('multiple caption texts are generated (paint-on)', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [ // RDC, resume direct captioning
- { ccData: 0x1429, type: 0 },
- // '01', default row 15
- { pts: 0 * 1000, ccData: characters('01'), type: 0 },
- // backspace
- { pts: 0 * 1000, ccData: 0x1421, type: 0 },
- { pts: 1 * 1000, ccData: characters('23'), type: 0 },
- // PAC: row 13, indent 0
- { pts: 2 * 1000, ccData: 0x1370, type: 0 },
- { pts: 2 * 1000, ccData: characters('32'), type: 0 },
- // backspace
- { pts: 3 * 1000, ccData: 0x1421, type: 0 },
- { pts: 4 * 1000, ccData: characters('10'), type: 0 },
- // EDM, erase displayed memory, flush caption
- { pts: 5 * 1000, ccData: 0x142c, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 1, 'detected a caption');
- assert.equal(captions[0].content[0].text, '310', 'first caption text');
- assert.equal(captions[0].content[1].text, '023', 'second caption text');
- });
- QUnit.test('PAC indent code increases the position', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // PAC indent code representing 4 indentations.
- { ccData: 5240, type: 0 },
- { ccData: characters('te'), type: 0 },
- { ccData: characters('st'), type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'test', 'content text');
- assert.equal(captions[0].content[0].line, 15, 'positions the caption to the bottom of the screen');
- assert.equal(captions[0].content[0].position, 50, 'positions the caption to the right of the screen');
- });
- QUnit.test('PAC offset code increases the position', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // PAC: row 1, indent 0
- { pts: 6750, ccData: 0x1150, type: 0 },
- // TO2 (tab offset 2 columns)
- { pts: 6755, ccData: 0x1722, type: 0 },
- { ccData: characters('te'), type: 0 },
- { ccData: characters('st'), type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'test', 'content text');
- assert.equal(captions[0].content[0].line, 1, 'positions the caption to the bottom of the screen');
- // Two tab offset columns adds 5 to the position (2 * 2.5)
- assert.equal(captions[0].content[0].position, 15, 'positions the caption to the right');
- });
- QUnit.test('PAC row command ensures we have the correct line property for captions', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- var packets = [
- // RU2 (roll-up, 2 rows)
- { pts: 6675, ccData: 0x1425, type: 0 },
- // CR (carriange return), flush nothing
- { pts: 6675, ccData: 0x142d, type: 0 },
- // PAC: row 2, indent 0
- // This should ensure the captions are at the top of the screen.
- { pts: 6675, ccData: 0x1170, type: 0 },
- // text: YEAR.
- { pts: 6676, ccData: 0x5945, type: 0 },
- { pts: 6676, ccData: 0x4152, type: 0 },
- { pts: 6676, ccData: 0x2e00, type: 0 },
- // RU2 (roll-up, 2 rows)
- { pts: 6677, ccData: 0x1425, type: 0 },
- // CR (carriange return), flush 1 row
- { pts: 6677, ccData: 0x142d, type: 0 },
- // EDM (erase displayed memory), flush 2 displayed roll-up rows
- { pts: 6697, ccData: 0x142c, type: 0 },
- // RDC (resume direct captioning), wipes memories, flushes nothing
- { pts: 6749, ccData: 0x1429, type: 0 },
- // PAC: row 1, indent 0
- { pts: 6750, ccData: 0x1150, type: 0 },
- // EOC, End of Caption
- { pts: 1 * 1000, ccData: 0x142f, type: 0 },
- // RCL, resume caption loading
- { ccData: 0x1420, type: 0 },
- // EOC, End of Caption
- { pts: 2 * 1000, ccData: 0x142f, type: 0 }
- ];
- // First caption stream is at the second most bottom row.
- packets.forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions[0].content[0].text, 'YEAR.', 'content text');
- assert.equal(captions[0].content[0].line, 2, 'positions the caption in the second most bottom row');
- assert.equal(captions[0].content[0].position, 10, 'position of the caption');
- // Second caption stream is at the most bottom row.
- assert.equal(captions[1].content[0].text, 'YEAR.', 'content text');
- assert.equal(captions[1].content[0].line, 1, 'positions the caption in the most bottom row');
- assert.equal(captions[1].content[0].position, 10, 'position of the caption');
- });
- QUnit.test('mix of all modes (extract from CNN)', function(assert) {
- var captions = [];
- cea608Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // RU2 (roll-up, 2 rows)
- { pts: 6675, ccData: 0x1425, type: 0 },
- // CR (carriange return), flush nothing
- { pts: 6675, ccData: 0x142d, type: 0 },
- // PAC: row 2, indent 0
- { pts: 6675, ccData: 0x1170, type: 0 },
- // text: YEAR.
- { pts: 6676, ccData: 0x5945, type: 0 },
- { pts: 6676, ccData: 0x4152, type: 0 },
- { pts: 6676, ccData: 0x2e00, type: 0 },
- // RU2 (roll-up, 2 rows)
- { pts: 6677, ccData: 0x1425, type: 0 },
- // CR (carriange return), flush 1 row
- { pts: 6677, ccData: 0x142d, type: 0 },
- // PAC: row 2, indent 0
- { pts: 6677, ccData: 0x1170, type: 0 },
- // text: GO TO CNNHEROS.COM.
- { pts: 6677, ccData: 0x474f, type: 0 },
- { pts: 6678, ccData: 0x2054, type: 0 },
- { pts: 6678, ccData: 0x4f00, type: 0 },
- { pts: 6678, ccData: 0x2043, type: 0 },
- { pts: 6679, ccData: 0x4e4e, type: 0 },
- { pts: 6679, ccData: 0x4845, type: 0 },
- { pts: 6679, ccData: 0x524f, type: 0 },
- { pts: 6680, ccData: 0x532e, type: 0 },
- { pts: 6680, ccData: 0x434f, type: 0 },
- { pts: 6680, ccData: 0x4d2e, type: 0 },
- // EDM (erase displayed memory), flush 2 displayed roll-up rows
- { pts: 6697, ccData: 0x142c, type: 0 },
- // RDC (resume direct captioning), wipes memories, flushes nothing
- { pts: 6749, ccData: 0x1429, type: 0 },
- // PAC: row 1, indent 0
- { pts: 6750, ccData: 0x1150, type: 0 },
- // text: Did your Senator or Congressman
- { pts: 6750, ccData: 0x4469, type: 0 },
- { pts: 6750, ccData: 0x6420, type: 0 },
- { pts: 6750, ccData: 0x796f, type: 0 },
- { pts: 6751, ccData: 0x7572, type: 0 },
- { pts: 6751, ccData: 0x2053, type: 0 },
- { pts: 6751, ccData: 0x656e, type: 0 },
- { pts: 6752, ccData: 0x6174, type: 0 },
- { pts: 6752, ccData: 0x6f72, type: 0 },
- { pts: 6752, ccData: 0x206f, type: 0 },
- { pts: 6753, ccData: 0x7220, type: 0 },
- { pts: 6753, ccData: 0x436f, type: 0 },
- { pts: 6753, ccData: 0x6e67, type: 0 },
- { pts: 6753, ccData: 0x7265, type: 0 },
- { pts: 6754, ccData: 0x7373, type: 0 },
- { pts: 6754, ccData: 0x6d61, type: 0 },
- { pts: 6754, ccData: 0x6e00, type: 0 },
- // PAC: row 2, indent 0
- { pts: 6755, ccData: 0x1170, type: 0 },
- // TO2 (tab offset 2 columns)
- { pts: 6755, ccData: 0x1722, type: 0 },
- // text: get elected by talking tough
- { pts: 6755, ccData: 0x6765, type: 0 },
- { pts: 6756, ccData: 0x7420, type: 0 },
- { pts: 6756, ccData: 0x656c, type: 0 },
- { pts: 6756, ccData: 0x6563, type: 0 },
- { pts: 6756, ccData: 0x7465, type: 0 },
- { pts: 6757, ccData: 0x6420, type: 0 },
- { pts: 6757, ccData: 0x6279, type: 0 },
- { pts: 6757, ccData: 0x2074, type: 0 },
- { pts: 6758, ccData: 0x616c, type: 0 },
- { pts: 6758, ccData: 0x6b69, type: 0 },
- { pts: 6758, ccData: 0x6e67, type: 0 },
- { pts: 6759, ccData: 0x2074, type: 0 },
- { pts: 6759, ccData: 0x6f75, type: 0 },
- { pts: 6759, ccData: 0x6768, type: 0 },
- // RCL (resume caption loading)
- { pts: 6759, ccData: 0x1420, type: 0 },
- // PAC: row 1, indent 4
- { pts: 6760, ccData: 0x1152, type: 0 },
- // TO1 (tab offset 1 column)
- { pts: 6760, ccData: 0x1721, type: 0 },
- // text: on the national debt?
- { pts: 6760, ccData: 0x6f6e, type: 0 },
- { pts: 6761, ccData: 0x2074, type: 0 },
- { pts: 6761, ccData: 0x6865, type: 0 },
- { pts: 6761, ccData: 0x206e, type: 0 },
- { pts: 6762, ccData: 0x6174, type: 0 },
- { pts: 6762, ccData: 0x696f, type: 0 },
- { pts: 6762, ccData: 0x6e61, type: 0 },
- { pts: 6762, ccData: 0x6c20, type: 0 },
- { pts: 6763, ccData: 0x6465, type: 0 },
- { pts: 6763, ccData: 0x6274, type: 0 },
- { pts: 6763, ccData: 0x3f00, type: 0 },
- // RCL (resume caption loading)
- { pts: 6781, ccData: 0x1420, type: 0 },
- // EDM (erase displayed memory), flush paint-on caption
- { pts: 6781, ccData: 0x142c, type: 0 },
- // EOC (end of caption), mark pop-on caption 1 start
- { pts: 6782, ccData: 0x142f, type: 0 },
- // RCL (resume caption loading)
- { pts: 6782, ccData: 0x1420, type: 0 },
- // PAC: row 1, indent 4
- { pts: 6782, ccData: 0x1152, type: 0 },
- // TO2 (tab offset 2 columns)
- { pts: 6783, ccData: 0x1722, type: 0 },
- // text: Will they stay true
- { pts: 6783, ccData: 0x5769, type: 0 },
- { pts: 6783, ccData: 0x6c6c, type: 0 },
- { pts: 6783, ccData: 0x2074, type: 0 },
- { pts: 6784, ccData: 0x6865, type: 0 },
- { pts: 6784, ccData: 0x7920, type: 0 },
- { pts: 6784, ccData: 0x7374, type: 0 },
- { pts: 6785, ccData: 0x6179, type: 0 },
- { pts: 6785, ccData: 0x2074, type: 0 },
- { pts: 6785, ccData: 0x7275, type: 0 },
- { pts: 6786, ccData: 0x6500, type: 0 },
- // PAC: row 2, indent 8
- { pts: 6786, ccData: 0x1174, type: 0 },
- // text: to their words?
- { pts: 6786, ccData: 0x746f, type: 0 },
- { pts: 6786, ccData: 0x2074, type: 0 },
- { pts: 6787, ccData: 0x6865, type: 0 },
- { pts: 6787, ccData: 0x6972, type: 0 },
- { pts: 6787, ccData: 0x2077, type: 0 },
- { pts: 6788, ccData: 0x6f72, type: 0 },
- { pts: 6788, ccData: 0x6473, type: 0 },
- { pts: 6788, ccData: 0x3f00, type: 0 },
- // RCL (resume caption loading)
- { pts: 6797, ccData: 0x1420, type: 0 },
- // EDM (erase displayed memory), mark pop-on caption 1 end and flush
- { pts: 6797, ccData: 0x142c, type: 0 },
- // EOC (end of caption), mark pop-on caption 2 start, flush nothing
- { pts: 6798, ccData: 0x142f, type: 0 },
- // RCL
- { pts: 6799, ccData: 0x1420, type: 0 },
- // EOC, mark pop-on caption 2 end and flush
- { pts: 6838, ccData: 0x142f, type: 0 },
- // RU2 (roll-up, 2 rows), wipes memories
- { pts: 6841, ccData: 0x1425, type: 0 },
- // CR (carriage return), flush nothing
- { pts: 6841, ccData: 0x142d, type: 0 },
- // PAC: row 2, indent 0
- { pts: 6841, ccData: 0x1170, type: 0 },
- // text: NO MORE SPECULATION, NO MORE
- { pts: 6841, ccData: 0x3e3e, type: 0 },
- { pts: 6841, ccData: 0x3e00, type: 0 },
- { pts: 6842, ccData: 0x204e, type: 0 },
- { pts: 6842, ccData: 0x4f00, type: 0 },
- { pts: 6842, ccData: 0x204d, type: 0 },
- { pts: 6842, ccData: 0x4f52, type: 0 },
- { pts: 6842, ccData: 0x4500, type: 0 },
- { pts: 6842, ccData: 0x2000, type: 0 },
- { pts: 6842, ccData: 0x5350, type: 0 },
- { pts: 6843, ccData: 0x4543, type: 0 },
- { pts: 6843, ccData: 0x554c, type: 0 },
- { pts: 6843, ccData: 0x4154, type: 0 },
- { pts: 6843, ccData: 0x494f, type: 0 },
- { pts: 6843, ccData: 0x4e2c, type: 0 },
- { pts: 6843, ccData: 0x204e, type: 0 },
- { pts: 6843, ccData: 0x4f00, type: 0 },
- { pts: 6843, ccData: 0x204d, type: 0 },
- { pts: 6844, ccData: 0x4f52, type: 0 },
- { pts: 6844, ccData: 0x4500, type: 0 },
- // RU2 (roll-up, two rows)
- { pts: 6844, ccData: 0x1425, type: 0 },
- // CR (carriage return), flush 1 roll-up row
- { pts: 6844, ccData: 0x142d, type: 0 },
- // PAC: row 2, indent 0
- { pts: 6844, ccData: 0x1170, type: 0 },
- // text: RUMORS OR GUESSING GAMES.
- { pts: 6844, ccData: 0x5255, type: 0 },
- { pts: 6844, ccData: 0x4d4f, type: 0 },
- { pts: 6844, ccData: 0x5253, type: 0 },
- { pts: 6844, ccData: 0x204f, type: 0 },
- { pts: 6845, ccData: 0x5200, type: 0 },
- { pts: 6845, ccData: 0x2047, type: 0 },
- { pts: 6845, ccData: 0x5545, type: 0 },
- { pts: 6845, ccData: 0x5353, type: 0 },
- { pts: 6845, ccData: 0x494e, type: 0 },
- { pts: 6845, ccData: 0x4700, type: 0 },
- { pts: 6845, ccData: 0x2047, type: 0 },
- { pts: 6845, ccData: 0x414d, type: 0 },
- { pts: 6845, ccData: 0x4553, type: 0 },
- { pts: 6845, ccData: 0x2e00, type: 0 },
- // RU2 (roll-up, 2 rows)
- { pts: 6846, ccData: 0x1425, type: 0 },
- // CR (carriage return), flush 2 roll-up rows
- { pts: 6846, ccData: 0x142d, type: 0 }
- ].forEach(cea608Stream.push, cea608Stream);
- assert.equal(captions.length, 7, 'detected 7 captions of varying types');
- assert.deepEqual(captions[0], {
- content: [{
- line: 2,
- position: 10,
- text: 'YEAR.',
- }],
- startPts: 6675,
- endPts: 6677,
- stream: 'CC1'
- }, 'parsed the 1st roll-up caption');
- assert.deepEqual(captions[1], {
- content: [
- {
- line: 1,
- position: 10,
- text: 'YEAR.',
- },
- {
- line: 2,
- position: 10,
- text: 'GO TO CNNHEROS.COM.',
- }
- ],
- startPts: 6677,
- endPts: 6697,
- stream: 'CC1'
- }, 'parsed the 2nd roll-up caption');
- assert.deepEqual(captions[2], {
- content: [
- {
- line: 1,
- position: 10,
- text: 'Did your Senator or Congressman',
- },
- {
- line: 2,
- position: 10,
- text: 'get elected by talking tough',
- }
- ],
- startPts: 6749,
- endPts: 6781,
- stream: 'CC1'
- }, 'parsed the paint-on caption');
- assert.deepEqual(captions[3], {
- content: [{
- line: 1,
- position: 22.5,
- text: 'on the national debt?',
- }],
- startPts: 6782,
- endPts: 6797,
- stream: 'CC1'
- }, 'parsed the 1st pop-on caption');
- assert.deepEqual(captions[4], {
- content: [
- {
- line: 1,
- position: 25,
- text: 'Will they stay true',
- },
- {
- line: 2,
- position: 30,
- text: 'to their words?',
- }
- ],
- startPts: 6798,
- endPts: 6838,
- stream: 'CC1'
- }, 'parsed the 2nd pop-on caption');
- assert.deepEqual(captions[5], {
- content: [{
- line: 2,
- position: 10,
- text: '>>> NO MORE SPECULATION, NO MORE',
- }],
- startPts: 6841,
- endPts: 6844,
- stream: 'CC1'
- }, 'parsed the 3rd roll-up caption');
- assert.deepEqual(captions[6], {
- content: [
- {
- line: 1,
- position: 10,
- text: '>>> NO MORE SPECULATION, NO MORE',
- },
- {
- line: 2,
- position: 10,
- text: 'RUMORS OR GUESSING GAMES.',
- }
- ],
- startPts: 6844,
- endPts: 6846,
- stream: 'CC1'
- }, 'parsed the 4th roll-up caption');
- });
- QUnit.test('Cea608Stream will trigger log on malformed captions', function(assert) {
- var result;
- var logs = [];
- cea608Stream.on('log', function(log) {
- logs.push(log);
- })
- // this will force an exception to happen in flushDisplayed
- cea608Stream.displayed_[0] = undefined;
- try {
- cea608Stream.flushDisplayed();
- result = true;
- } catch (e) {
- result = false;
- }
- assert.ok(
- result,
- 'the function does not throw an exception'
- );
- assert.deepEqual(
- logs,
- [
- {level: 'warn', message: 'Skipping a malformed 608 caption at index 0.'}
- ],
- 'logs were triggered'
- );
- });
- var cea708Stream;
- QUnit.module('CEA 708 Stream', {
- beforeEach: function() {
- cea708Stream = new m2ts.Cea708Stream();
- }
- });
- QUnit.test('Filters encoding values out of captionServices option block', function(assert) {
- var expectedServiceEncodings = {
- SERVICE1: 'euc-kr',
- SERVICE2: 'utf-8',
- };
- cea708Stream = new m2ts.Cea708Stream({
- captionServices: {
- SERVICE1: {
- language: 'kr',
- label: 'Korean',
- encoding: 'euc-kr'
- },
- SERVICE2: {
- language: 'en',
- label: 'English',
- encoding: 'utf-8'
- }
- }
- });
- assert.deepEqual(cea708Stream.serviceEncodings, expectedServiceEncodings, 'filtered encodings correctly');
- });
- QUnit.test('parses 708 captions', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- cc708PinkUnderscore.forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 235, 'parsed 235 captions');
- assert.deepEqual(captions[0], {
- startPts: 6723335478,
- endPts: 6723626769,
- text: '\"Pinkalicious_and_Peterrific\"\nis_made_possible_in_part_by:',
- stream: 'cc708_1'
- }, 'parsed first caption correctly');
- assert.deepEqual(captions[1], {
- startPts: 6723740883,
- endPts: 6723945087,
- text: 'GIRL:\nRead_me_the_tale\nof_a_faraway_land.',
- stream: 'cc708_1'
- }, 'parsed second caption correctly');
- assert.deepEqual(captions[2], {
- startPts: 6723948090,
- endPts: 6724200342,
- text: 'Tell_me_of_planets\nwith_oceans_of_sand.',
- stream: 'cc708_1'
- }, 'parsed third caption correctly');
- assert.deepEqual(captions[33], {
- startPts: 6732617751,
- endPts: 6732876009,
- text: '♪_It\'s_a_Pinkalicious_feeling_♪',
- stream: 'cc708_1'
- }, 'parsed caption 33 correctly with music note');
- assert.deepEqual(captions[38], {
- startPts: 6734218350,
- endPts: 6734425557,
- text: 'PINKALICIOUS:\n\"Dream_Salon.\"',
- stream: 'cc708_1'
- }, 'parsed caption 38 correctly');
- assert.deepEqual(captions[234], {
- startPts: 6778809897,
- endPts: 6779104191,
- text: 'I_guess_I\'ll_just_have\nto_duck_a_little_bit.',
- stream: 'cc708_1'
- }, 'parsed caption 234 correctly');
- });
- QUnit.test('Decodes multibyte characters if valid encoding option is provided and TextDecoder is supported', function(assert) {
- var captions = [];
- cea708Stream = new m2ts.Cea708Stream({
- captionServices: {
- SERVICE1: {
- encoding: 'euc-kr'
- }
- }
- });
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- cc708Korean.forEach(cea708Stream.push, cea708Stream);
- cea708Stream.flushDisplayed(4721138662, cea708Stream.services[1]);
- assert.equal(captions.length, 1, 'parsed single caption correctly');
- if (window.TextDecoder) {
- assert.ok(cea708Stream.services[1].textDecoder_, 'TextDecoder created when supported');
- assert.equal(
- captions[0].text,
- '니가 ',
- 'parsed multibyte characters correctly'
- );
- } else {
- assert.notOk(cea708Stream.services[1].textDecoder_, 'TextDecoder not created when unsupported');
- }
- });
- QUnit.test('Decodes multi-byte characters as unicode if no valid encoding option is provided', function(assert) {
- var captions = [];
- cea708Stream = new m2ts.Cea708Stream({
- captionServices: {
- SERVICE1: {}
- }
- });
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- cc708Korean.forEach(cea708Stream.push, cea708Stream);
- cea708Stream.flushDisplayed(4721138662, cea708Stream.services[1]);
- assert.equal(captions.length, 1, 'parsed single caption correctly');
- assert.notOk(cea708Stream.services[1].textDecoder_, 'TextDecoder was not created');
- assert.equal(
- captions[0].text,
- '듏낡 ',
- 'parsed multibyte characters correctly'
- );
- });
- QUnit.test('Creates TextDecoder only if valid encoding value is provided', function(assert) {
- var secondCea708Stream;
- cea708Stream = new m2ts.Cea708Stream({
- captionServices: {
- SERVICE1: {
- encoding: 'euc-kr'
- }
- }
- });
- cc708Korean.forEach(cea708Stream.push, cea708Stream);
- cea708Stream.flushDisplayed(4721138662, cea708Stream.services[1]);
- if (window.TextDecoder) {
- assert.ok(cea708Stream.services[1].textDecoder_, 'TextDecoder created successfully when encoding is valid');
- }
- secondCea708Stream = new m2ts.Cea708Stream({
- captionServices: {
- SERVICE1: {
- encoding: 'invalid'
- }
- }
- });
- cc708Korean.forEach(secondCea708Stream.push, secondCea708Stream);
- secondCea708Stream.flushDisplayed(4721138662, secondCea708Stream.services[1]);
- assert.notOk(secondCea708Stream.services[1].textDecoder_, 'TextDecoder not created when encoding is invalid');
- });
- QUnit.test('reset command', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- { type: 3, pts: 153315036, ccData: 0x8322 },
- { type: 2, pts: 153315036, ccData: 0x4820 },
- { type: 2, pts: 153315036, ccData: 0x0000 },
- { type: 3, pts: 153318039, ccData: 0xc322 },
- { type: 2, pts: 153318039, ccData: 0x4953 },
- { type: 2, pts: 153318039, ccData: 0x0000 },
- { type: 3, pts: 153387108, ccData: 0x0628 },
- { type: 2, pts: 153387108, ccData: 0x0d90 },
- { type: 2, pts: 153387108, ccData: 0x0503 },
- { type: 2, pts: 153387108, ccData: 0x912a },
- { type: 2, pts: 153387108, ccData: 0x002a },
- { type: 2, pts: 153387108, ccData: 0x0000 },
- { type: 3, pts: 153405126, ccData: 0x4da2 },
- { type: 2, pts: 153405126, ccData: 0x8c0f },
- { type: 2, pts: 153405126, ccData: 0x628c },
- { type: 2, pts: 153405126, ccData: 0x0f31 },
- { type: 2, pts: 153405126, ccData: 0x983b },
- { type: 2, pts: 153405126, ccData: 0x912a },
- { type: 2, pts: 153405126, ccData: 0x8f00 },
- { type: 2, pts: 153405126, ccData: 0x611f },
- { type: 2, pts: 153405126, ccData: 0x002a },
- { type: 2, pts: 153405126, ccData: 0x1090 },
- { type: 2, pts: 153405126, ccData: 0x0503 },
- { type: 3, pts: 153408129, ccData: 0x8a31 },
- { type: 2, pts: 153408129, ccData: 0x9201 },
- { type: 2, pts: 153408129, ccData: 0x983b },
- // RST (Reset command)
- { type: 2, pts: 153408129, ccData: 0x8f00 },
- { type: 2, pts: 153408129, ccData: 0x0000 },
- { type: 2, pts: 153408129, ccData: 0x611f },
- { type: 2, pts: 153408129, ccData: 0x1090 },
- { type: 2, pts: 153408129, ccData: 0x0000 },
- { type: 2, pts: 153408129, ccData: 0x0503 },
- { type: 2, pts: 153408129, ccData: 0x912a },
- { type: 2, pts: 153408129, ccData: 0x0000 },
- { type: 2, pts: 153408129, ccData: 0x9201 },
- { type: 3, pts: 153414135, ccData: 0xc322 },
- { type: 2, pts: 153414135, ccData: 0x434f },
- { type: 2, pts: 153414135, ccData: 0x0000 }
- ].forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 1, 'parsed 1 caption');
- assert.deepEqual(captions[0], {
- startPts: 153315036,
- endPts: 153408129,
- text: '*\n;',
- stream: 'cc708_1'
- }, 'parsed the caption correctly');
- });
- QUnit.test('windowing', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- { type: 3, pts: 1000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8000 }, // CW0
- { type: 2, pts: 1000, ccData: characters('w0') },
- { type: 3, pts: 1000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8100 }, // CW1
- { type: 2, pts: 1000, ccData: characters('w1') },
- { type: 3, pts: 1000, ccData: packetHeader708(2, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8200 }, // CW2
- { type: 2, pts: 1000, ccData: characters('w2') },
- { type: 3, pts: 1000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8300 }, // CW3
- { type: 2, pts: 1000, ccData: characters('w3') },
- { type: 3, pts: 1000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8400 }, // CW4
- { type: 2, pts: 1000, ccData: characters('w4') },
- { type: 3, pts: 1000, ccData: packetHeader708(2, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8500 }, // CW5
- { type: 2, pts: 1000, ccData: characters('w5') },
- { type: 3, pts: 1000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8600 }, // CW6
- { type: 2, pts: 1000, ccData: characters('w6') },
- { type: 3, pts: 1000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: 0x8700 }, // CW7
- { type: 2, pts: 1000, ccData: characters('w7') },
- { type: 3, pts: 2000, ccData: packetHeader708(2, 3, 1, 4) },
- { type: 2, pts: 2000, ccData: 0x8aff }, // HDW (Hide all)
- { type: 2, pts: 2000, ccData: displayWindows708([0]) },
- { type: 3, pts: 3000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 3000, ccData: 0x8aff }, // HDW (Hide all)
- { type: 2, pts: 3000, ccData: displayWindows708([1]) },
- { type: 3, pts: 4000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 4000, ccData: 0x8aff }, // HDW (Hide all)
- { type: 2, pts: 4000, ccData: displayWindows708([2, 3]) },
- { type: 3, pts: 5000, ccData: packetHeader708(2, 3, 1, 4) },
- { type: 2, pts: 5000, ccData: 0x8aff }, // HDW (Hide all)
- { type: 2, pts: 5000, ccData: displayWindows708([3, 4]) },
- { type: 3, pts: 6000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 6000, ccData: 0x8aff }, // HDW (Hide all)
- { type: 2, pts: 6000, ccData: displayWindows708([5, 6, 7]) },
- { type: 3, pts: 7000, ccData: packetHeader708(1, 2, 1, 2) },
- { type: 2, pts: 7000, ccData: 0x8aff }, // HDW (Hide all)
- // Indicate end of last packet
- { type: 3, pts: 8000, ccData: packetHeader708(2, 1, 1, 0) }
- ].forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 5, 'parsed 5 captions');
- assert.deepEqual(captions[0], {
- startPts: 2000,
- endPts: 3000,
- text: 'w0',
- stream: 'cc708_1'
- }, 'parsed caption 0 correctly');
- assert.deepEqual(captions[1], {
- startPts: 3000,
- endPts: 4000,
- text: 'w1',
- stream: 'cc708_1'
- }, 'parsed caption 1 correctly');
- assert.deepEqual(captions[2], {
- startPts: 4000,
- endPts: 5000,
- text: 'w2\n\nw3',
- stream: 'cc708_1'
- }, 'parsed caption 2 correctly');
- assert.deepEqual(captions[3], {
- startPts: 5000,
- endPts: 6000,
- text: 'w3\n\nw4',
- stream: 'cc708_1'
- }, 'parsed caption 3 correctly');
- assert.deepEqual(captions[4], {
- startPts: 6000,
- endPts: 7000,
- text: 'w5\n\nw6\n\nw7',
- stream: 'cc708_1'
- }, 'parsed caption 4 correctly');
- });
- QUnit.test('backspace', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- { type: 3, pts: 1000, ccData: packetHeader708(0, 7, 1, 12) },
- { type: 2, pts: 1000, ccData: 0x8000 }, // CW0
- { type: 2, pts: 1000, ccData: characters('ty') },
- { type: 2, pts: 1000, ccData: characters('op') },
- { type: 2, pts: 1000, ccData: 0x0808 }, // BS BS: Backspace twice
- { type: 2, pts: 1000, ccData: characters('po') },
- { type: 2, pts: 1000, ccData: displayWindows708([0]) },
- { type: 3, pts: 2000, ccData: packetHeader708(1, 2, 1, 2) },
- { type: 2, pts: 2000, ccData: 0x8aff },
- // Indicate end of last packet
- { type: 3, pts: 3000, ccData: packetHeader708(2, 1, 1, 0) }
- ].forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 1, 'parsed 1 caption');
- assert.equal(captions[0].text, 'typo', 'parsed caption with backspaces correctly');
- });
- QUnit.test('extended character set', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- { type: 3, pts: 1000, ccData: packetHeader708(0, 7, 1, 12) },
- { type: 2, pts: 1000, ccData: displayWindows708([0]) },
- { type: 2, pts: 1000, ccData: 0x8000 }, // CW0
- { type: 2, pts: 1000, ccData: 0x103f }, // Ÿ
- { type: 2, pts: 1000, ccData: 0x1035 }, // •
- { type: 2, pts: 1000, ccData: 0x103f }, // Ÿ
- { type: 2, pts: 1000, ccData: 0x0020 },
- { type: 3, pts: 1000, ccData: packetHeader708(1, 7, 1, 12) },
- { type: 2, pts: 1000, ccData: 0x103c }, // œ
- { type: 2, pts: 1000, ccData: 0x102a }, // Š
- { type: 2, pts: 1000, ccData: 0x1025 }, // …
- { type: 2, pts: 1000, ccData: 0x102a }, // Š
- { type: 2, pts: 1000, ccData: 0x103c }, // œ
- { type: 2, pts: 1000, ccData: 0x0020 },
- { type: 3, pts: 1000, ccData: packetHeader708(2, 5, 1, 8) },
- { type: 2, pts: 1000, ccData: 0x1033 }, // “
- { type: 2, pts: 1000, ccData: 0x103d }, // ℠
- { type: 2, pts: 1000, ccData: 0x1034 }, // ”
- { type: 2, pts: 1000, ccData: 0x1039 }, // ™
- { type: 3, pts: 2000, ccData: packetHeader708(0, 2, 1, 2) },
- { type: 2, pts: 2000, ccData: 0x8aff },
- // Indicate end of last packet
- { type: 3, pts: 3000, ccData: packetHeader708(1, 1, 1, 0) }
- ].forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 1, 'parsed 1 caption');
- assert.equal(captions[0].text, 'Ÿ•Ÿ œŠ…Šœ “℠”™', 'parsed extended characters correctly');
- });
- QUnit.test('roll up', function(assert) {
- var captions = [];
- cea708Stream.on('data', function(caption) {
- captions.push(caption);
- });
- [
- // Define window with two virtual rows (rowCount = 1)
- { type: 3, pts: 1000, ccData: packetHeader708(0, 4, 1, 6) },
- { type: 2, pts: 1000, ccData: 0x983b },
- { type: 2, pts: 1000, ccData: 0x8f00 },
- { type: 2, pts: 1000, ccData: 0x611f },
- { type: 3, pts: 1000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 1000, ccData: characters('L1') },
- { type: 2, pts: 1000, ccData: 0x0d00 }, // CR
- { type: 3, pts: 2000, ccData: packetHeader708(2, 3, 1, 4) },
- { type: 2, pts: 2000, ccData: characters('L2') },
- { type: 2, pts: 2000, ccData: 0x0d00 }, // CR
- { type: 3, pts: 3000, ccData: packetHeader708(0, 3, 1, 4) },
- { type: 2, pts: 3000, ccData: characters('L3') },
- { type: 2, pts: 3000, ccData: 0x0d00 }, // CR
- { type: 3, pts: 4000, ccData: packetHeader708(1, 3, 1, 4) },
- { type: 2, pts: 4000, ccData: characters('L4') },
- { type: 2, pts: 4000, ccData: 0x0d00 }, // CR
- { type: 3, pts: 5000, ccData: packetHeader708(2, 2, 1, 2) },
- { type: 2, pts: 5000, ccData: 0x8aff },
- // Indicate end of last packet
- { type: 3, pts: 6000, ccData: packetHeader708(0, 1, 1, 0) }
- ].forEach(cea708Stream.push, cea708Stream);
- assert.equal(captions.length, 3, 'parsed 3 captions');
- assert.equal(captions[0].text, 'L1\nL2', 'parsed caption 1 correctly');
- assert.equal(captions[1].text, 'L2\nL3', 'parsed caption 2 correctly');
- assert.equal(captions[2].text, 'L3\nL4', 'parsed caption 3 correctly');
- });
|