diff --git a/src/inheritAttributes.js b/src/inheritAttributes.js index 3085f2f9..e534f75b 100644 --- a/src/inheritAttributes.js +++ b/src/inheritAttributes.js @@ -202,7 +202,8 @@ export const parseCaptionServiceMetadata = (service) => { const values = service.value.split(';'); return values.map((value) => { - let channel; let language; + let channel; + let language; // default language to value language = value; @@ -215,6 +216,68 @@ export const parseCaptionServiceMetadata = (service) => { return {channel, language}; }); + } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') { + const values = service.value.split(';'); + + return values.map((value) => { + const flags = { + // service or channel number 1-63 + 'channel': undefined, + + // language is a 3ALPHA per ISO 639.2/B + // field is required + 'language': undefined, + + // BIT 1/0 or ? + // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown + 'aspectRatio': 1, + + // BIT 1/0 + // easy reader flag indicated the text is tailed to the needs of beginning readers + // default 0, or off + 'easyReader': 0, + + // BIT 1/0 + // If 3d metadata is present (CEA-708.1) then 1 + // default 0 + '3D': 0 + }; + + if (/=/.test(value)) { + + const [channel, opts = ''] = value.split('='); + + flags.channel = channel; + flags.language = value; + + opts.split(',').forEach((opt) => { + const [name, val] = opt.split(':'); + + if (name === 'lang') { + flags.language = val; + + // er for easyReadery + } else if (name === 'er') { + flags.easyReader = Number(val); + + // war for wide aspect ratio + } else if (name === 'war') { + flags.aspectRatio = Number(val); + + } else if (name === '3D') { + flags['3D'] = Number(val); + } + }); + } else { + flags.language = value; + } + + if (flags.channel) { + flags.channel = 'SERVICE' + flags.channel; + } + + return flags; + }); } }; diff --git a/src/toM3u8.js b/src/toM3u8.js index 00cfe5fb..970b0ae9 100644 --- a/src/toM3u8.js +++ b/src/toM3u8.js @@ -206,13 +206,29 @@ const organizeCaptionServices = (captionServices) => captionServices.reduce((svc return svcObj; } - svc.forEach(({channel, language}) => { + svc.forEach((service) => { + const { + channel, + language + } = service; + svcObj[language] = { autoselect: false, default: false, instreamId: channel, language }; + + if (service.hasOwnProperty('aspectRatio')) { + svcObj[language].aspectRatio = service.aspectRatio; + } + if (service.hasOwnProperty('easyReader')) { + svcObj[language].easyReader = service.easyReader; + } + if (service.hasOwnProperty('3D')) { + svcObj[language]['3D'] = service['3D']; + } + }); return svcObj; diff --git a/test/index.test.js b/test/index.test.js index 64324789..694405b1 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -9,6 +9,7 @@ import maatVttSegmentTemplate from './manifests/maat_vtt_segmentTemplate.mpd'; import segmentBaseTemplate from './manifests/segmentBase.mpd'; import segmentListTemplate from './manifests/segmentList.mpd'; import cc608CaptionsTemplate from './manifests/608-captions.mpd'; +import cc708CaptionsTemplate from './manifests/708-captions.mpd'; import locationTemplate from './manifests/location.mpd'; import locationsTemplate from './manifests/locations.mpd'; import multiperiod from './manifests/multiperiod.mpd'; @@ -27,6 +28,9 @@ import { import { parsedManifest as cc608CaptionsManifest } from './manifests/608-captions.js'; +import { + parsedManifest as cc708CaptionsManifest +} from './manifests/708-captions.js'; import { parsedManifest as multiperiodManifest } from './manifests/multiperiod.js'; @@ -77,6 +81,10 @@ QUnit.test('has parse', function(assert) { name: '608-captions', input: cc608CaptionsTemplate, expected: cc608CaptionsManifest +}, { + name: '708-captions', + input: cc708CaptionsTemplate, + expected: cc708CaptionsManifest }, { name: 'multiperiod', input: multiperiod, diff --git a/test/inheritAttributes.test.js b/test/inheritAttributes.test.js index 20cff470..70317d2a 100644 --- a/test/inheritAttributes.test.js +++ b/test/inheritAttributes.test.js @@ -240,6 +240,199 @@ QUnit.test('parsed 608 metadata', function(assert) { }], 'eng;CC3'); }); +QUnit.test('parsed 708 metadata', function(assert) { + const getmd = (value) => ({ + schemeIdUri: 'urn:scte:dash:cc:cea-708:2015', + value + }); + + const assertServices = (services, expected, message) => { + if (!services) { + assert.notOk(expected, message); + return; + } + + services.forEach((service, i) => { + assert.deepEqual(service, expected[i], message); + }); + }; + + assertServices(parseCaptionServiceMetadata({ + schemeIdUri: 'random scheme', + value: 'eng' + }), undefined, 'dont parse incorrect scheme for 708'); + + assertServices(parseCaptionServiceMetadata(getmd('eng')), [{ + 'channel': undefined, + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }], 'simple eng'); + + assertServices(parseCaptionServiceMetadata(getmd('eng;swe')), [{ + 'channel': undefined, + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'swe', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }], 'eng;swe'); + + assertServices(parseCaptionServiceMetadata(getmd('1=lang:eng;2=lang:swe')), [{ + 'channel': 'SERVICE1', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': 'SERVICE2', + 'language': 'swe', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }], '1=lang:eng;2=lang:swe'); + + assertServices(parseCaptionServiceMetadata(getmd('1=lang:eng;swe')), [{ + 'channel': 'SERVICE1', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'swe', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }], 'mixed 1=lang:eng;swe'); + + assertServices(parseCaptionServiceMetadata(getmd('1=lang:eng;2=lang:eng,war:1,er:1')), [{ + 'channel': 'SERVICE1', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': 'SERVICE2', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 1, + '3D': 0 + }], '1=lang:eng;2=lang:eng,war:1,er:1'); + + assertServices(parseCaptionServiceMetadata(getmd('1=lang:eng,war:0;2=lang:eng,3D:1,er:1')), [{ + 'channel': 'SERVICE1', + 'language': 'eng', + 'aspectRatio': 0, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': 'SERVICE2', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 1, + '3D': 1 + }], '1=lang:eng,war:0;2=lang:eng,3D:1,er:1'); + + assertServices(parseCaptionServiceMetadata(getmd('eng;fre;spa;jpn;deu;swe;kor;lat;zho;heb;rus;ara;hin;por;tur')), [{ + 'channel': undefined, + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'fre', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'spa', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'jpn', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'deu', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'swe', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'kor', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'lat', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'zho', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'heb', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'rus', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'ara', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'hin', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'por', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }, { + 'channel': undefined, + 'language': 'tur', + 'aspectRatio': 1, + 'easyReader': 0, + '3D': 0 + }], 'make sure that parsing 15 services works'); +}); + QUnit.module('inheritAttributes'); QUnit.test('needs at least one Period', function(assert) { diff --git a/test/manifests/608-captions.mpd b/test/manifests/608-captions.mpd index 1f050698..499f72e4 100644 --- a/test/manifests/608-captions.mpd +++ b/test/manifests/608-captions.mpd @@ -7,7 +7,7 @@ - + 1080p.ts diff --git a/test/manifests/708-captions.js b/test/manifests/708-captions.js new file mode 100644 index 00000000..eae5dfd0 --- /dev/null +++ b/test/manifests/708-captions.js @@ -0,0 +1,71 @@ +export const parsedManifest = { + allowCache: true, + discontinuityStarts: [], + duration: 6, + endList: true, + mediaGroups: { + 'AUDIO': {}, + 'CLOSED-CAPTIONS': { + cc: { + // eng: { + // autoselect: false, + // default: false, + // instreamId: '1', + // language: 'eng', + // aspectRatio: 1, + // easyReader: 0, + // '3D': 0 + // }, + // TODO only this one ends up being represented and not both + eng: { + 'autoselect': false, + 'default': false, + 'instreamId': 'SERVICE2', + 'language': 'eng', + 'aspectRatio': 1, + 'easyReader': 1, + '3D': 0 + } + } + }, + 'SUBTITLES': {}, + 'VIDEO': {} + }, + playlists: [ + { + attributes: { + 'AUDIO': 'audio', + 'BANDWIDTH': 449000, + 'CODECS': 'avc1.420015', + 'NAME': '482', + 'PROGRAM-ID': 1, + 'RESOLUTION': { + height: 270, + width: 482 + }, + 'SUBTITLES': 'subs' + }, + endList: true, + resolvedUri: '', + targetDuration: 6, + mediaSequence: 0, + segments: [ + { + duration: 6, + timeline: 0, + number: 0, + map: { + uri: '', + resolvedUri: 'https://www.example.com/1080p.ts' + }, + resolvedUri: 'https://www.example.com/1080p.ts', + uri: 'https://www.example.com/1080p.ts' + } + ], + timeline: 0, + uri: '' + } + ], + segments: [], + uri: '' +}; diff --git a/test/manifests/708-captions.mpd b/test/manifests/708-captions.mpd new file mode 100644 index 00000000..fc71f2bb --- /dev/null +++ b/test/manifests/708-captions.mpd @@ -0,0 +1,17 @@ + + + https://www.example.com/base + + + + + + + + 1080p.ts + + + + + +