Skip to content

Commit d4afee5

Browse files
authored
Property mapping while MPD parsing (Dash-Industry-Forum#4901)
* enable RegEx for Exceptions * move local RegEx into Constants * fixing regression issue with MapNode exceptions * move code into sub-function * rename some variables * remove merge flag form MapNode * enable RegEx for Exceptions * move code into sub-function * rename some variables * remove merge flag form MapNode * - reestablish new file lost - remove duplicated code while merging * Revert changes to clean history * add one unit test for an example of a DASH XML element that can be present only once * rename variables to improve intelligibility of their semantics * implement merging of attributes for non-array Nodes per DASH spec * add type check prior to merging values
1 parent 9ab99e8 commit d4afee5

12 files changed

Lines changed: 316 additions & 242 deletions

src/dash/models/DashManifestModel.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ function DashManifestModel() {
676676
}
677677

678678
// now, only return properties present on all Representations
679-
// repr.legth is always >= 2
679+
// repr.length is always >= 2
680680
return propertiesOfFirstRepresentation.filter(prop => {
681681
return repr.slice(1).every(currRep => {
682682
return currRep.hasOwnProperty(propertyType) && currRep[propertyType].some(e => {
@@ -692,10 +692,18 @@ function DashManifestModel() {
692692
}
693693

694694
let allProperties = _getPropertiesCommonToAllRepresentations(propertyType, adaptation[DashConstants.REPRESENTATION]);
695+
696+
// now, only take those Properties from AdaptationSet which we didn't already get from Representations
695697
if (adaptation.hasOwnProperty(propertyType) && adaptation[propertyType].length) {
696-
allProperties.push(...adaptation[propertyType])
698+
adaptation[propertyType].forEach( adaptationProp => {
699+
const alreadyPresent = allProperties.some(d => {
700+
return d.schemeIdUri === adaptationProp.schemeIdUri && d.value === adaptationProp.value
701+
});
702+
if (!alreadyPresent) {
703+
allProperties.push(adaptationProp);
704+
}
705+
})
697706
}
698-
// we don't check whether there are duplicates on AdaptationSets and Representations
699707

700708
return allProperties.map(essentialProperty => {
701709
const s = new DescriptorType();

src/dash/parser/maps/CommonProperty.js

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/dash/parser/maps/MapNode.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,13 @@
3131
/**
3232
* @classdesc a node at some level in a ValueMap
3333
*/
34-
import CommonProperty from './CommonProperty.js';
3534

3635
class MapNode {
37-
constructor(name, properties, children) {
36+
constructor(name, properties, exceptions, children) {
3837
this._name = name || '';
39-
this._properties = [];
38+
this._properties = properties || [];
39+
this._exceptions = exceptions || {};
4040
this._children = children || [];
41-
42-
if (Array.isArray(properties)) {
43-
properties.forEach(p => {
44-
this._properties.push(new CommonProperty(p));
45-
});
46-
}
4741
}
4842

4943
get name() {
@@ -57,6 +51,10 @@ class MapNode {
5751
get properties() {
5852
return this._properties;
5953
}
54+
55+
get exceptions() {
56+
return this._exceptions;
57+
}
6058
}
6159

6260
export default MapNode;

src/dash/parser/maps/RepresentationBaseValuesMap.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
*/
3434
import MapNode from './MapNode.js';
3535
import DashConstants from '../../constants/DashConstants.js';
36+
import Constants from '../../../streaming/constants/Constants.js';
3637

3738
class RepresentationBaseValuesMap extends MapNode {
3839
constructor() {
@@ -42,6 +43,7 @@ class RepresentationBaseValuesMap extends MapNode {
4243
DashConstants.CODECS,
4344
DashConstants.CODING_DEPENDENCY,
4445
DashConstants.CONTENT_PROTECTION,
46+
DashConstants.ESSENTIAL_PROPERTY,
4547
DashConstants.FRAMERATE,
4648
DashConstants.FRAME_PACKING,
4749
DashConstants.HEIGHT,
@@ -55,12 +57,34 @@ class RepresentationBaseValuesMap extends MapNode {
5557
DashConstants.SEGMENT_PROFILES,
5658
DashConstants.SEGMENT_SEQUENCE_PROPERTIES,
5759
DashConstants.START_WITH_SAP,
60+
DashConstants.SUPPLEMENTAL_CODECS,
61+
DashConstants.SUPPLEMENTAL_PROPERTY,
5862
DashConstants.WIDTH,
5963
];
6064

61-
super(DashConstants.ADAPTATION_SET, commonProperties, [
62-
new MapNode(DashConstants.REPRESENTATION, commonProperties, [
63-
new MapNode(DashConstants.SUB_REPRESENTATION, commonProperties)
65+
// RegEx are supported
66+
const exceptions = {
67+
[DashConstants.SUPPLEMENTAL_PROPERTY]: {
68+
schemeIdUri: [
69+
Constants.URL_QUERY_INFO_SCHEME,
70+
Constants.EXT_URL_QUERY_INFO_SCHEME,
71+
Constants.ADV_URL_QUERY_INFO_SCHEME,
72+
Constants.URL_QUERY_STATE_PREFIX
73+
]
74+
},
75+
[DashConstants.ESSENTIAL_PROPERTY]: {
76+
schemeIdUri: [
77+
Constants.URL_QUERY_INFO_SCHEME,
78+
Constants.EXT_URL_QUERY_INFO_SCHEME,
79+
Constants.ADV_URL_QUERY_INFO_SCHEME,
80+
Constants.URL_QUERY_STATE_PREFIX
81+
]
82+
}
83+
};
84+
85+
super(DashConstants.ADAPTATION_SET, commonProperties, exceptions, [
86+
new MapNode(DashConstants.REPRESENTATION, commonProperties, exceptions, [
87+
new MapNode(DashConstants.SUB_REPRESENTATION, commonProperties, exceptions)
6488
])
6589
]);
6690
}

src/dash/parser/maps/SegmentValuesMap.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ class SegmentValuesMap extends MapNode {
4040
DashConstants.SEGMENT_BASE, DashConstants.SEGMENT_TEMPLATE, DashConstants.SEGMENT_LIST
4141
];
4242

43-
super(DashConstants.PERIOD, commonProperties, [
44-
new MapNode(DashConstants.ADAPTATION_SET, commonProperties, [
45-
new MapNode(DashConstants.REPRESENTATION, commonProperties)
43+
const exceptions = {};
44+
45+
super(DashConstants.PERIOD, commonProperties, exceptions, [
46+
new MapNode(DashConstants.ADAPTATION_SET, commonProperties, exceptions, [
47+
new MapNode(DashConstants.REPRESENTATION, commonProperties, exceptions)
4648
])
4749
]);
4850
}

src/dash/parser/objectiron.js

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,66 @@ import FactoryMaker from '../../core/FactoryMaker.js';
3232

3333
function ObjectIron(mappers) {
3434

35-
function mergeValues(parentItem, childItem) {
36-
for (let name in parentItem) {
37-
if (!childItem.hasOwnProperty(name)) {
38-
childItem[name] = parentItem[name];
35+
function _mergeValues(parentItem, childItem) {
36+
if (typeof parentItem === 'object') {
37+
for (let name in parentItem) {
38+
if (!childItem.hasOwnProperty(name)) {
39+
childItem[name] = parentItem[name];
40+
}
41+
}
42+
}
43+
}
44+
45+
function _mappingAllowed (element, exception) {
46+
let allowMapping = true;
47+
if (exception) {
48+
for (const [key, values] of Object.entries(exception)) {
49+
let attr = element[key];
50+
if (values.some(v => attr.match(v))) {
51+
allowMapping = false;
52+
}
53+
}
54+
}
55+
56+
return allowMapping;
57+
}
58+
59+
function _conditionallyMapProperty(exception, propertyName, propertyIsArray, propertyElementFromParent, childNode) {
60+
if (_mappingAllowed(propertyElementFromParent, exception)) {
61+
if (childNode[propertyName]) {
62+
// property already exists
63+
if (propertyIsArray) {
64+
childNode[propertyName].push(propertyElementFromParent);
65+
} else {
66+
// non-Array Properties can be:
67+
// - certain elements (e.g. SegmentList, see ISO 23009-1 (6th ed), clause 5.3.9.1) or
68+
// - attributes (e.g. codecs)
69+
_mergeValues(propertyElementFromParent, childNode[propertyName]);
70+
}
71+
} else {
72+
// just add the property
73+
if (propertyIsArray) {
74+
childNode[propertyName] = [propertyElementFromParent];
75+
} else {
76+
childNode[propertyName] = propertyElementFromParent;
77+
}
3978
}
4079
}
4180
}
4281

43-
function mapProperties(properties, parent, child) {
82+
function mapProperties(properties, exceptions, parentNode, childNode) {
4483
for (let i = 0, len = properties.length; i < len; ++i) {
45-
const property = properties[i];
46-
47-
if (parent[property.name]) {
48-
if (child[property.name]) {
49-
// check to see if we should merge
50-
if (property.merge) {
51-
const parentValue = parent[property.name];
52-
const childValue = child[property.name];
53-
54-
// complex objects; merge properties
55-
if (typeof parentValue === 'object' && typeof childValue === 'object') {
56-
mergeValues(parentValue, childValue);
57-
}
58-
// simple objects; merge them together
59-
else {
60-
child[property.name] = parentValue + childValue;
61-
}
62-
}
84+
const propertyName = properties[i];
85+
86+
if (parentNode[propertyName]) {
87+
const propertyFromParentElement = parentNode[propertyName];
88+
89+
if (Array.isArray(propertyFromParentElement)) {
90+
propertyFromParentElement.forEach(propParentEl => {
91+
_conditionallyMapProperty(exceptions[propertyName], propertyName, true, propParentEl, childNode);
92+
});
6393
} else {
64-
// just add the property
65-
child[property.name] = parent[property.name];
94+
_conditionallyMapProperty(exceptions[propertyName], propertyName, false, propertyFromParentElement, childNode);
6695
}
6796
}
6897
}
@@ -76,7 +105,7 @@ function ObjectIron(mappers) {
76105
if (array) {
77106
for (let v = 0, len2 = array.length; v < len2; ++v) {
78107
const childNode = array[v];
79-
mapProperties(item.properties, node, childNode);
108+
mapProperties(item.properties, item.exceptions, node, childNode);
80109
mapItem(childItem, childNode);
81110
}
82111
}

src/streaming/constants/Constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ export default {
275275
COLOUR_PRIMARIES_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:ColourPrimaries',
276276
URL_QUERY_INFO_SCHEME: 'urn:mpeg:dash:urlparam:2014',
277277
EXT_URL_QUERY_INFO_SCHEME: 'urn:mpeg:dash:urlparam:2016',
278+
ADV_URL_QUERY_INFO_SCHEME: 'urn:mpeg:dash:urlparam:2025',
279+
URL_QUERY_STATE_PREFIX: /urn:mpeg:dash:state:/,
278280
MATRIX_COEFFICIENTS_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:MatrixCoefficients',
279281
TRANSFER_CHARACTERISTICS_SCHEME_ID_URI: 'urn:mpeg:mpegB:cicp:TransferCharacteristics',
280282
SEGMENT_SEQUENCE_REPRESENTATION_SCHEME_ID_URI: 'urn:mpeg:dash:ssr:2023',
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<MPD mediaPresentationDuration="PT634.566S" minBufferTime="PT2.00S" profiles="urn:hbbtv:dash:profile:isoff-live:2012,urn:mpeg:dash:profile:isoff-live:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd">
2+
<BaseURL>./</BaseURL>
3+
<Period>
4+
<AdaptationSet id="as_1" codecs="mp4a.40.2" mimeType="audio/mp4" contentType="audio" subsegmentAlignment="true" subsegmentStartsWithSAP="1" lang="es">
5+
<Label lang="en">English Label</Label>
6+
<Label lang="fre">French Label</Label>
7+
<AudioChannelConfiguration schemeIdUri="urn:mpeg:mpegB:cicp:ChannelConfiguration" value="6"/>
8+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="6"/>
9+
<SupplementalProperty schemeIdUri="urn:ABC" value="Instance1" />
10+
<SupplementalProperty schemeIdUri="urn:XYZ" value="OK" />
11+
<SupplementalProperty schemeIdUri="urn:mpeg:dash:state:lang#audio" />
12+
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
13+
<SegmentTemplate duration="200000" timescale="48000" initialization="$RepresentationID$.m4a"/>
14+
<Representation id="bbb_a64k" codecs="mp4a.40.5" bandwidth="67071" audioSamplingRate="48000">
15+
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
16+
<SegmentTemplate duration="300000" timescale="48000" media="$Number$.m4a" startNumber="1"/>
17+
<SupplementalProperty schemeIdUri="urn:ABC" value="Instance2" />
18+
<SupplementalProperty schemeIdUri="urn:ABC" value="Instance3" />
19+
</Representation>
20+
</AdaptationSet>
21+
</Period>
22+
</MPD>

test/unit/test/dash/dash.DashParser.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,61 @@ describe('DashParser', function () {
6060
expect(labelArray[1].lang).to.equal('fr');
6161
});
6262
});
63+
64+
describe('DashParser - ObjectIron', async () => {
65+
beforeEach(function () {
66+
dashManifestModel.setConfig({
67+
errHandler: errorHandlerMock
68+
});
69+
});
70+
71+
let manifest_prop = await FileLoader.loadTextFile('/data/dash/manifest_properties.xml');
72+
73+
it('should map AudioChannelConfig even if another instance is present on Representation', async () => {
74+
let parsedMpd = dashParser.parse(manifest_prop);
75+
let audioAdaptationsArray = dashManifestModel.getAdaptationsForType(parsedMpd, 0, 'audio');
76+
let audiorepresentation = dashManifestModel.getRepresentationFor(0, audioAdaptationsArray[0]);
77+
78+
let acc = dashManifestModel.getAudioChannelConfigurationForRepresentation(audiorepresentation);
79+
80+
expect(acc).to.be.instanceOf(Array);
81+
expect(acc.length).to.equal(2);
82+
});
83+
84+
it('should map allowed SupplementalProperties from AdaptationSet to Representation', async () => {
85+
let parsedMpd = dashParser.parse(manifest_prop);
86+
let rawAdaptationSet = parsedMpd.Period[0].AdaptationSet[0];
87+
88+
expect(rawAdaptationSet.SupplementalProperty).to.be.instanceOf(Array);
89+
expect(rawAdaptationSet.SupplementalProperty.length).to.equal(3);
90+
91+
let rawRepresentation = rawAdaptationSet.Representation[0];
92+
93+
expect(rawRepresentation.SupplementalProperty).to.be.instanceOf(Array);
94+
expect(rawRepresentation.SupplementalProperty.length).to.equal(4);
95+
});
96+
97+
it('should map only allowed non-Array attributes from AdaptationSet to Representation', async () => {
98+
let parsedMpd = dashParser.parse(manifest_prop);
99+
let rawAdaptationSet = parsedMpd.Period[0].AdaptationSet[0];
100+
let rawRepresentation = rawAdaptationSet.Representation[0];
101+
102+
expect(rawRepresentation.SegmentTemplate).to.be.instanceOf(Object);
103+
104+
expect(rawRepresentation.SegmentTemplate.initialization).to.equal('$RepresentationID$.m4a');
105+
expect(rawRepresentation.SegmentTemplate.media).to.equal('$Number$.m4a');
106+
expect(rawRepresentation.SegmentTemplate.duration).to.equal(300000);
107+
});
108+
109+
it('should not map attributes', async () => {
110+
let parsedMpd = dashParser.parse(manifest_prop);
111+
let rawAdaptationSet = parsedMpd.Period[0].AdaptationSet[0];
112+
let rawRepresentation = rawAdaptationSet.Representation[0];
113+
114+
expect(rawRepresentation.codecs).to.equal('mp4a.40.5');
115+
});
116+
117+
});
63118
})
64119

65120

0 commit comments

Comments
 (0)