Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for single segment file per stream #4

Merged
merged 5 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,31 @@ Run script locally

```
% node dist/cli.js -h
Usage: cli [options] <source> <dest>
Usage: cli [options]

Run shaka-packager with source on S3 or locally, and output to S3 or local

Examples:
$ shaka-packager-s3 -i a:1=audio.mp4 -i v:1=video.mp4 -s s3://source-bucket/folder -d s3://output-bucket/folder
$ shaka-packager-s3 -i a:1=audio.mp4 -i v:1=video.mp4 -s /path/to/source/folder -d /path/to/output/folder
$ shaka-packager-s3 -i a:2=audio.mp4 -i v:1=video.mp4 -s /path/to/source/folder -d /path/to/output/folder --segment-single-file --segment-single-file-name 'Container$KEY$.mp4' --segment-duration 3.84

Run shaka-packager with source on S3 and output to S3

Arguments:
source Source bucket URL (supported protocols: s3
dest Destination bucket URL (supported protocols: s3)

Options:
-i, --input [inputOptions...] Input options on the format: [a|v]:<key>=filename
--staging-dir <stagingDir> Staging directory (default: /tmp/data)
-h, --help display help for command
-s, --source-folder [sourceFolder] Source folder URL, ignored if input uses absolute path (supported protocols: s3, local file)
-i, --input [inputOptions...] Input options on the format: [a|v]:<key>=filename
--staging-dir [stagingDir] Staging directory (default: /tmp/data)
--shaka-executable [shakaExecutable] Path to shaka-packager executable, defaults to 'packager'. Can also be set with environment variable SHAKA_PACKAGER_EXECUTABLE.
--no-implicit-audio [noImplicitAudio] Do not include audio unless audio input specified
-d, --destination-folder <dest> Destination folder URL (supported protocols: s3, local file). Defaults to CWD.
--dash-only Package only DASH format
--hls-only Package only HLS format
--segment-single-file Use byte range addressing and a single segment file per stream
--segment-single-file-name [segmentSingleFileName] Template for single segment file name, $KEY$ will be replaced with stream key
--segment-duration [segmentDuration] Segment target duration
-h, --help display help for command

```

## Support
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"pretty": "prettier --check --ignore-unknown .",
"typecheck": "tsc --noEmit -p tsconfig.json",
"test": "jest --passWithNoTests",
"postversion": "git push && git push --tags"
"postversion": "git push && git push --tags",
"start": "ts-node -T src/cli.ts"
},
"author": "Eyevinn Technology <work@eyevinn.se>",
"license": "MIT",
Expand Down
65 changes: 50 additions & 15 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cli
Examples:
$ shaka-packager-s3 -i a:1=audio.mp4 -i v:1=video.mp4 -s s3://source-bucket/folder -d s3://output-bucket/folder
$ shaka-packager-s3 -i a:1=audio.mp4 -i v:1=video.mp4 -s /path/to/source/folder -d /path/to/output/folder
$ shaka-packager-s3 -i a:2=audio.mp4 -i v:1=video.mp4 -s /path/to/source/folder -d /path/to/output/folder --segment-single-file --segment-single-file-name 'Container$KEY$.mp4' --segment-duration 3.84
`
)
.option(
Expand All @@ -53,28 +54,62 @@ cli
'-d, --destination-folder <dest>',
'Destination folder URL (supported protocols: s3, local file). Defaults to CWD.'
)
.option('--dash-only', 'Package only DASH format')
.option('--hls-only', 'Package only HLS format')
.option(
'--segment-single-file',
'Use byte range addressing and a single segment file per stream'
)
.option(
'--segment-single-file-name [segmentSingleFileName]',
'Template for single segment file name, must contain $KEY$ which will be replaced with key of corresponding input'
)
.option('--segment-duration [segmentDuration]', 'Segment target duration')
.action(async (options) => {
try {
const inputOptions = parseInputOptions(options.input);
if (inputOptions) {
console.log('inputs', inputOptions);
console.log(
`dest: ${options.destinationFolder}, source: ${options.sourceFolder}`
);
await doPackage({
dest: options.destinationFolder || '.',
source: options.sourceFolder,
inputs: inputOptions,
stagingDir: options.stagingDir,
noImplicitAudio: options.noImplicitAudio,
shakaExecutable:
options.shakaExecutable || process.env.SHAKA_PACKAGER_EXECUTABLE
});
} else {
if (!inputOptions) {
console.error('Need at least one input!\n');
cli.help();
process.exit(1);
}
if (options.hlsOnly && options.dashOnly) {
console.error('Cannot disable both hls and dash\n');
cli.help();
process.exit(1);
}
if (
options.segmentSingleFileName &&
options.segmentSingleFileName.indexOf('$KEY$') === -1
) {
console.error(
'--segment-single-file-name argument must contain $KEY$\n'
);
cli.help();
process.exit(1);
}
console.log('inputs', inputOptions);
console.log(
`dest: ${options.destinationFolder}, source: ${options.sourceFolder}`
);
await doPackage({
dest: options.destinationFolder || '.',
source: options.sourceFolder,
inputs: inputOptions,
stagingDir: options.stagingDir,
noImplicitAudio: options.noImplicitAudio,
packageFormatOptions: {
hlsOnly: options.hlsOnly,
dashOnly: options.dashOnly,
segmentSingleFile: options.segmentSingleFile,
segmentSingleFileTemplate: options.segmentSingleFileName,
segmentDuration: options.segmentDuration
? parseFloat(options.segmentDuration)
: undefined
},
shakaExecutable:
options.shakaExecutable || process.env.SHAKA_PACKAGER_EXECUTABLE
});
} catch (err) {
console.log((err as Error).message);
}
Expand Down
118 changes: 107 additions & 11 deletions src/packager.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
import { createShakaArgs, Input } from './packager';
import { createShakaArgs, doPackage, Input } from './packager';

const singleInputVideo = [
{
type: 'video',
filename: 'test.mp4',
key: '1'
} as Input
];

describe('Test doPackage', () => {
it('Both hlsOnly and dashOnly specified, throws error', async () => {
try {
await doPackage({
inputs: singleInputVideo,
dest: '.',
packageFormatOptions: {
hlsOnly: true,
dashOnly: true
}
});
fail('Should throw');
} catch (err) {
expect((err as Error).message).toBe('Cannot disable both hls and dash');
}
});

it('segmentSingleFileTemplate does not contain $KEY$, throws error', async () => {
try {
await doPackage({
inputs: singleInputVideo,
dest: '.',
packageFormatOptions: {
segmentSingleFileTemplate: 'Container.mp4'
}
});
fail('Should throw');
} catch (err) {
expect((err as Error).message).toBe(
'segmentSingleFileTemplate must contain $KEY$'
);
}
});
});

describe('Test create shaka args', () => {
const singleInputVideo = [
{
type: 'video',
filename: 'test.mp4',
key: '1'
} as Input
];
it('Should use first video file as audio source if noImplicitAudio not set', async () => {
const args = createShakaArgs(singleInputVideo, false);
expect(args).toEqual([
'in=test.mp4,stream=video,init_segment=video-1/init.mp4,segment_template=video-1/$Number$.m4s,playlist_name=video-1.m3u8',
'in=test.mp4,stream=audio,init_segment=audio/init.mp4,segment_template=audio/$Number$.m4s,playlist_name=audio.m3u8,hls_group_id=audio,hls_name=defaultaudio',
'in=test.mp4,stream=video,playlist_name=video-1.m3u8,init_segment=video-1/init.mp4,segment_template=video-1/$Number$.m4s',
'in=test.mp4,stream=audio,playlist_name=audio.m3u8,hls_group_id=audio,hls_name=defaultaudio,init_segment=audio/init.mp4,segment_template=audio/$Number$.m4s',
'--hls_master_playlist_output',
'index.m3u8',
'--generate_static_live_mpd',
'--mpd_output',
'manifest.mpd'
]);
Expand All @@ -23,9 +60,68 @@ describe('Test create shaka args', () => {
it('Should not use first video file as audio source if noImplicitAudio is true', async () => {
const args = createShakaArgs(singleInputVideo, true);
expect(args).toEqual([
'in=test.mp4,stream=video,init_segment=video-1/init.mp4,segment_template=video-1/$Number$.m4s,playlist_name=video-1.m3u8',
'in=test.mp4,stream=video,playlist_name=video-1.m3u8,init_segment=video-1/init.mp4,segment_template=video-1/$Number$.m4s',
'--hls_master_playlist_output',
'index.m3u8',
'--generate_static_live_mpd',
'--mpd_output',
'manifest.mpd'
]);
});

it('Should set --segement_duration option if segmentDuration is set', async () => {
const args = createShakaArgs(singleInputVideo, true, {
segmentDuration: 3.84
});
expect(args).toEqual([
'in=test.mp4,stream=video,playlist_name=video-1.m3u8,init_segment=video-1/init.mp4,segment_template=video-1/$Number$.m4s',
'--hls_master_playlist_output',
'index.m3u8',
'--generate_static_live_mpd',
'--mpd_output',
'manifest.mpd',
'--segment_duration',
'3.84'
]);
});

it('Should set correct output path for stream if single file segment specified', async () => {
const args = createShakaArgs(singleInputVideo, true, {
segmentSingleFile: true,
segmentSingleFileTemplate: 'Container-$KEY$.mp4'
});
expect(args).toEqual([
'in=test.mp4,stream=video,playlist_name=video-1.m3u8,out=Container-1.mp4',
'--hls_master_playlist_output',
'index.m3u8',
'--generate_static_live_mpd',
'--mpd_output',
'manifest.mpd'
]);
});

it('Should set correct output path for stream if single file segment specified with audio', async () => {
const args = createShakaArgs(
[
...singleInputVideo,
{
type: 'audio',
filename: 'audio.mp4',
key: '2'
}
],
true,
{
segmentSingleFile: true,
segmentSingleFileTemplate: 'Container-$KEY$.mp4'
}
);
expect(args).toEqual([
'in=test.mp4,stream=video,playlist_name=video-1.m3u8,out=Container-1.mp4',
'in=audio.mp4,stream=audio,playlist_name=audio.m3u8,hls_group_id=audio,hls_name=defaultaudio,out=Container-2.mp4',
'--hls_master_playlist_output',
'index.m3u8',
'--generate_static_live_mpd',
'--mpd_output',
'manifest.mpd'
]);
Expand Down
Loading
Loading