Skip to content

Commit

Permalink
feat: add utility for processing DASH segment template URIs
Browse files Browse the repository at this point in the history
see: #129
Signed-off-by: Casey Occhialini <1508707+littlespex@users.noreply.github.com>
  • Loading branch information
littlespex committed Feb 12, 2025
1 parent 4ae7ed0 commit 35176c1
Show file tree
Hide file tree
Showing 9 changed files with 873 additions and 150 deletions.
205 changes: 121 additions & 84 deletions CHANGELOG.md

Large diffs are not rendered by default.

448 changes: 392 additions & 56 deletions lib/NOTICE

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions lib/config/common-media-library.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,9 @@ export type Presentation = Ham & {
selectionSets: SelectionSet[];
};

// @beta
export function processUriTemplate(uriTemplate: string, representationId: string | null | undefined, number: number | null | undefined, subNumber: number | null | undefined, bandwidth: number | null | undefined, time: number | null | undefined): string;

// @beta
export type RequestInterceptor = (request: CommonMediaRequest) => Promise<CommonMediaRequest>;

Expand Down
1 change: 1 addition & 0 deletions lib/src/dash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { processUriTemplate } from './dash/processUriTemplate.js';
108 changes: 108 additions & 0 deletions lib/src/dash/processUriTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const re = /\$(RepresentationID|Number|SubNumber|Bandwidth|Time)?(?:%0([0-9]+)([diouxX]))?\$/g;

/**
* Process a URI template used in `SegmentTemplate` nodes.
*
* @param uriTemplate - URI template to process.
* @param representationId - Representation ID.
* @param number - Number.
* @param subNumber - Sub-number.
* @param bandwidth - Bandwidth.
* @param time - Time.
*
* @returns Processed URI template.
*
* @group DASH
* @beta
*
* @example
* ```ts
* const uriTemplate = ;
* const result = processUriTemplate(
* 'http://example.com/$RepresentationID$/$Number$/$SubNumber$/$Bandwidth$/$Time%02d$/$$',
* 'rep1',
* 1,
* 2,
* 3,
* 4,
* );
* console.log(result);
* // -> 'http://example.com/rep1/1/2/3/04/$'
* ```
*/
export function processUriTemplate(
uriTemplate: string,
representationId: string | null | undefined,
number: number | null | undefined,
subNumber: number | null | undefined,
bandwidth: number | null | undefined,
time: number | null | undefined,
) {
const uri = uriTemplate.replace(re, (match, name, widthStr, format) => {
let value: string | number | null | undefined;

switch (name) {
case undefined: // $$ case
return '$';

case 'RepresentationID':
value = representationId;
break;

case 'Number':
value = number;
break;

case 'SubNumber':
value = subNumber;
break;

case 'Bandwidth':
value = bandwidth;
break;

case 'Time':
value = time ? Math.round(time) : time;
break;

default:
value = null;
}

if (value == null) {
return match;
}

let valueString: string;

switch (format) {
case undefined: // Happens if there is no format specifier.
case 'd':
case 'i':
case 'u':
valueString = value.toString();
break;

case 'o':
valueString = value.toString(8);
break;

case 'x':
valueString = value.toString(16);
break;

case 'X':
valueString = value.toString(16).toUpperCase();
break;

default:
valueString = value.toString();
break;
}

const width = parseInt(widthStr, 10) || 1;
return valueString.padStart(width, '0');
});

return uri;
}
1 change: 1 addition & 0 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './cmaf-ham.js';
export * from './cmcd.js';
export * from './cmsd.js';
export type * from './cta.js';
export * from './dash.js';
export * from './id3.js';
export type * from './request.js';
export * from './structuredfield.js';
Expand Down
237 changes: 237 additions & 0 deletions lib/test/dash/processUriTemplate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { processUriTemplate } from '@svta/common-media-library';
import { equal } from 'node:assert';
import { describe, it } from 'node:test';

describe('processUriTemplate', () => {
it('handles a single RepresentationID identifier', () => {
equal(
processUriTemplate(
'/example/$RepresentationID$.mp4',
'100', null, null, null, null,
),
'/example/100.mp4',
);

// RepresentationID cannot use a width specifier.
equal(
processUriTemplate(
'/example/$RepresentationID%01d$.mp4',
'100', null, null, null, null,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$RepresentationID$.mp4',
null, null, null, null, null,
),
'/example/$RepresentationID$.mp4',
);
});

it('handles a single Number identifier', () => {
equal(
processUriTemplate(
'/example/$Number$.mp4',
null, 100, null, null, null,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$Number%05d$.mp4',
null, 100, null, null, null,
),
'/example/00100.mp4',
);

equal(
processUriTemplate(
'/example/$Number$.mp4',
null, null, null, null, null,
),
'/example/$Number$.mp4',
);
});

it('handles a single SubNumber identifier', () => {
equal(
processUriTemplate(
'/example/$SubNumber$.mp4',
null, null, 100, null, null,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$SubNumber%05d$.mp4',
null, null, 100, null, null,
),
'/example/00100.mp4',
);

equal(
processUriTemplate(
'/example/$SubNumber$.mp4',
null, null, null, null, null,
),
'/example/$SubNumber$.mp4',
);
});

it('handles a single Bandwidth identifier', () => {
equal(
processUriTemplate(
'/example/$Bandwidth$.mp4',
null, null, null, 100, null,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$Bandwidth%05d$.mp4',
null, null, null, 100, null,
),
'/example/00100.mp4',
);

equal(
processUriTemplate(
'/example/$Bandwidth$.mp4',
null, null, null, null, null,
),
'/example/$Bandwidth$.mp4',
);
});

it('handles a single Time identifier', () => {
equal(
processUriTemplate(
'/example/$Time$.mp4',
null, null, null, null, 100,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$Time%05d$.mp4',
null, null, null, null, 100,
),
'/example/00100.mp4',
);

equal(
processUriTemplate(
'/example/$Time$.mp4',
null, null, null, null, null,
),
'/example/$Time$.mp4',
);
});

it('handles rounding errors for calculated Times', () => {
equal(
processUriTemplate(
'/example/$Time$.mp4',
null, null, null, null, 100.0001,
),
'/example/100.mp4',
);

equal(
processUriTemplate(
'/example/$Time%05d$.mp4',
null, null, null, null, 99.9999,
),
'/example/00100.mp4',
);
});

it('handles multiple identifiers', () => {
equal(
processUriTemplate(
'/example/$RepresentationID$_$Number$_$SubNumber$_$Bandwidth$_$Time$.mp4',
'1', 2, 3, 4, 5,
),
'/example/1_2_3_4_5.mp4',
);

// No spaces.
equal(
processUriTemplate(
'/example/$RepresentationID$$Number$$SubNumber$$Bandwidth$$Time$.mp4',
'1', 2, 3, 4, 5,
),
'/example/12345.mp4',
);

// Different order.
equal(
processUriTemplate(
'/example/$SubNumber$_$Bandwidth$_$Time$_$RepresentationID$_$Number$.mp4',
'1', 2, 3, 4, 5,
),
'/example/3_4_5_1_2.mp4',
);

// Single width.
equal(
processUriTemplate(
'$RepresentationID$_$Number%01d$_$SubNumber%01d$_$Bandwidth%01d$_$Time%01d$',
'1', 2, 3, 4, 500,
),
'1_2_3_4_500',
);

// Different widths.
equal(
processUriTemplate(
'$RepresentationID$_$Number%02d$_$SubNumber%02d$_$Bandwidth%02d$_$Time%02d$',
'1', 2, 3, 4, 5,
),
'1_02_03_04_05',
);

// Double $$.
equal(
processUriTemplate(
'$$/$RepresentationID$$$$Number$$$$SubNumber$$$$Bandwidth$$$$Time$$$.$$',
'1', 2, 3, 4, 5,
),
'$/1$2$3$4$5$.$',
);
});

it('handles invalid identifiers', () => {
equal(
processUriTemplate(
'/example/$Garbage$.mp4',
'1', 2, 3, 4, 5,
),
'/example/$Garbage$.mp4',
);

equal(
processUriTemplate(
'/example/$Time.mp4',
'1', 2, 3, 4, 5,
),
'/example/$Time.mp4',
);
});

it('handles non-decimal format specifiers', () => {
equal(
processUriTemplate(
'/$Number%05x$_$Number%01X$_$Number%01u$_$Number%01o$.mp4',
'', 180, 0, 0, 0,
),
'/000b4_B4_180_264.mp4',
);
});
});
4 changes: 2 additions & 2 deletions scripts/createReleaseNotes.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ npm install @svta/common-media-library@${version}

async function getChanges(version: string) {
const changeLog = await readFile('./CHANGELOG.md', 'utf8');
const logs = changeLog.split('\n\n\n');
const match = `## [${version}]`;
const logs = changeLog.split(/^## /m);
const match = `[${version}]`;
const log = logs.find(log => log.includes(match)) || '';
return log.split('\n\n').slice(1).join('\n\n');
}
Expand Down
Loading

0 comments on commit 35176c1

Please sign in to comment.