Add track count limit checking

This commit is contained in:
Vanilagy
2025-01-17 12:26:12 +01:00
parent 7b6567fcf0
commit d323c2be8e
4 changed files with 124 additions and 4 deletions
+23 -2
View File
@@ -32,7 +32,7 @@
fileInput.addEventListener('change', async () => { fileInput.addEventListener('change', async () => {
const file = fileInput.files[0]; const file = fileInput.files[0];
const source = new Metamuxer.BlobSource(file); const source = new Metamuxer.BufferSource(await file.arrayBuffer()) ?? new Metamuxer.BlobSource(file);
const start = performance.now(); const start = performance.now();
const input = new Metamuxer.Input({ const input = new Metamuxer.Input({
@@ -40,7 +40,27 @@
source source
}); });
const videoTrack = await input.getPrimaryVideoTrack(); const output = new Metamuxer.Output({
format: new Metamuxer.WaveOutputFormat(),
target: new Metamuxer.BufferTarget()
});
output.start();
/*
const audioTrack = await input.getPrimaryAudioTrack();
const sink = new Metamuxer.EncodedAudioSampleSink(audioTrack);
let total = 0;
for await (const sample of sink.samples(undefined, undefined, { metadataOnly: true })) {
total++;
if (performance.now() > 10000) {
break;
}
}
console.log("Done", total);
*/
/*
const sink = new Metamuxer.VideoFrameSink(videoTrack); const sink = new Metamuxer.VideoFrameSink(videoTrack);
console.log(await videoTrack.getLanguageCode()); console.log(await videoTrack.getLanguageCode());
@@ -49,6 +69,7 @@
console.log(thing) console.log(thing)
} }
console.log("Done") console.log("Done")
*/
/* /*
//const videoTrack = await input.getPrimaryVideoTrack(); //const videoTrack = await input.getPrimaryVideoTrack();
+4
View File
@@ -5,6 +5,8 @@ export {
VideoTrackMetadata, VideoTrackMetadata,
AudioTrackMetadata, AudioTrackMetadata,
SubtitleTrackMetadata, SubtitleTrackMetadata,
TrackType,
ALL_TRACK_TYPES,
} from './output'; } from './output';
export { export {
OutputFormat, OutputFormat,
@@ -17,6 +19,8 @@ export {
WebMOutputFormat, WebMOutputFormat,
WebMOutputFormatOptions, WebMOutputFormatOptions,
WaveOutputFormat, WaveOutputFormat,
TrackCountLimits,
InclusiveRange,
} from './output-format'; } from './output-format';
export { export {
VideoEncodingConfig, VideoEncodingConfig,
+39 -2
View File
@@ -2,18 +2,28 @@ import { AUDIO_CODECS, MediaCodec, PCM_CODECS, SUBTITLE_CODECS, VIDEO_CODECS } f
import { IsobmffMuxer } from './isobmff/isobmff-muxer'; import { IsobmffMuxer } from './isobmff/isobmff-muxer';
import { MatroskaMuxer } from './matroska/matroska-muxer'; import { MatroskaMuxer } from './matroska/matroska-muxer';
import { Muxer } from './muxer'; import { Muxer } from './muxer';
import { Output } from './output'; import { Output, TrackType } from './output';
import { WaveMuxer } from './wave/wave-muxer'; import { WaveMuxer } from './wave/wave-muxer';
/** @public */
export type InclusiveRange = { min: number; max: number };
/** @public */
export type TrackCountLimits = {
[K in TrackType]: InclusiveRange;
} & {
total: InclusiveRange;
};
/** @public */ /** @public */
export abstract class OutputFormat { export abstract class OutputFormat {
/** @internal */ /** @internal */
abstract _createMuxer(output: Output): Muxer; abstract _createMuxer(output: Output): Muxer;
/** @internal */ /** @internal */
abstract _getName(): string; abstract _getName(): string;
abstract getFileExtension(): string; abstract getFileExtension(): string;
abstract getSupportedCodecs(): MediaCodec[]; abstract getSupportedCodecs(): MediaCodec[];
abstract getSupportedTrackCounts(): TrackCountLimits;
getSupportedVideoCodecs() { getSupportedVideoCodecs() {
return this.getSupportedCodecs().filter(codec => (VIDEO_CODECS as readonly string[]).includes(codec)); return this.getSupportedCodecs().filter(codec => (VIDEO_CODECS as readonly string[]).includes(codec));
@@ -57,6 +67,15 @@ export abstract class IsobmffOutputFormat extends OutputFormat {
this._options = options; this._options = options;
} }
getSupportedTrackCounts(): TrackCountLimits {
return {
video: { min: 0, max: Infinity },
audio: { min: 0, max: Infinity },
subtitle: { min: 0, max: Infinity },
total: { min: 1, max: 2 ** 32 - 1 }, // Have fun reaching this one
};
}
/** @internal */ /** @internal */
_createMuxer(output: Output) { _createMuxer(output: Output) {
return new IsobmffMuxer(output, this); return new IsobmffMuxer(output, this);
@@ -161,6 +180,15 @@ export class MkvOutputFormat extends OutputFormat {
return 'Matroska'; return 'Matroska';
} }
getSupportedTrackCounts(): TrackCountLimits {
return {
video: { min: 0, max: Infinity },
audio: { min: 0, max: Infinity },
subtitle: { min: 0, max: Infinity },
total: { min: 1, max: 127 },
};
}
getFileExtension() { getFileExtension() {
return '.mkv'; return '.mkv';
} }
@@ -228,6 +256,15 @@ export class WaveOutputFormat extends OutputFormat {
return 'WAVE'; return 'WAVE';
} }
getSupportedTrackCounts(): TrackCountLimits {
return {
video: { min: 0, max: 0 },
audio: { min: 1, max: 1 },
subtitle: { min: 0, max: 0 },
total: { min: 1, max: 1 },
};
}
getFileExtension() { getFileExtension() {
return '.wav'; return '.wav';
} }
+58
View File
@@ -11,9 +11,15 @@ export type OutputOptions = {
target: Target; target: Target;
}; };
/** @public */
export const ALL_TRACK_TYPES = ['video', 'audio', 'subtitle'] as const;
/** @public */
export type TrackType = typeof ALL_TRACK_TYPES[number];
export type OutputTrack = { export type OutputTrack = {
id: number; id: number;
output: Output; output: Output;
type: TrackType;
} & ({ } & ({
type: 'video'; type: 'video';
source: VideoSource; source: VideoSource;
@@ -152,6 +158,29 @@ export class Output {
throw new Error('Source is already used for a track.'); throw new Error('Source is already used for a track.');
} }
// Verify maximum track count constraints
const supportedTrackCounts = this._format.getSupportedTrackCounts();
const presentTracksOfThisType = this._tracks.reduce(
(count, track) => count + (track.type === type ? 1 : 0),
0,
);
const maxCount = supportedTrackCounts[type].max;
if (presentTracksOfThisType === maxCount) {
throw new Error(
maxCount === 0
? `${this._format._getName()} does not support ${type} tracks.`
: (`${this._format._getName()} does not support more than ${maxCount} ${type} track`
+ `${maxCount === 1 ? '' : 's'}.`),
);
}
const maxTotalCount = supportedTrackCounts.total.max;
if (this._tracks.length === maxTotalCount) {
throw new Error(
`${this._format._getName()} does not support more than ${maxTotalCount} tracks`
+ `${maxTotalCount === 1 ? '' : 's'} in total.`,
);
}
const track = { const track = {
id: this._tracks.length + 1, id: this._tracks.length + 1,
output: this, output: this,
@@ -219,6 +248,35 @@ export class Output {
throw new Error('Output already started.'); throw new Error('Output already started.');
} }
// Verify minimum track count constraints
const supportedTrackCounts = this._format.getSupportedTrackCounts();
for (const trackType of ALL_TRACK_TYPES) {
const presentTracksOfThisType = this._tracks.reduce(
(count, track) => count + (track.type === trackType ? 1 : 0),
0,
);
const minCount = supportedTrackCounts[trackType].min;
if (presentTracksOfThisType < minCount) {
throw new Error(
minCount === supportedTrackCounts[trackType].max
? (`${this._format._getName()} requires exactly ${minCount} ${trackType}`
+ ` track${minCount === 1 ? '' : 's'}.`)
: (`${this._format._getName()} requires at least ${minCount} ${trackType}`
+ ` track${minCount === 1 ? '' : 's'}.`),
);
}
}
const totalMinCount = supportedTrackCounts.total.min;
if (this._tracks.length < totalMinCount) {
throw new Error(
totalMinCount === supportedTrackCounts.total.max
? (`${this._format._getName()} requires exactly ${totalMinCount} track`
+ `${totalMinCount === 1 ? '' : 's'}.`)
: (`${this._format._getName()} requires at least ${totalMinCount} track`
+ `${totalMinCount === 1 ? '' : 's'}.`),
);
}
this._started = true; this._started = true;
this._writer.start(); this._writer.start();