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 () => {
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 input = new Metamuxer.Input({
@@ -40,7 +40,27 @@
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);
console.log(await videoTrack.getLanguageCode());
@@ -49,6 +69,7 @@
console.log(thing)
}
console.log("Done")
*/
/*
//const videoTrack = await input.getPrimaryVideoTrack();
+4
View File
@@ -5,6 +5,8 @@ export {
VideoTrackMetadata,
AudioTrackMetadata,
SubtitleTrackMetadata,
TrackType,
ALL_TRACK_TYPES,
} from './output';
export {
OutputFormat,
@@ -17,6 +19,8 @@ export {
WebMOutputFormat,
WebMOutputFormatOptions,
WaveOutputFormat,
TrackCountLimits,
InclusiveRange,
} from './output-format';
export {
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 { MatroskaMuxer } from './matroska/matroska-muxer';
import { Muxer } from './muxer';
import { Output } from './output';
import { Output, TrackType } from './output';
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 */
export abstract class OutputFormat {
/** @internal */
abstract _createMuxer(output: Output): Muxer;
/** @internal */
abstract _getName(): string;
abstract getFileExtension(): string;
abstract getSupportedCodecs(): MediaCodec[];
abstract getSupportedTrackCounts(): TrackCountLimits;
getSupportedVideoCodecs() {
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;
}
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 */
_createMuxer(output: Output) {
return new IsobmffMuxer(output, this);
@@ -161,6 +180,15 @@ export class MkvOutputFormat extends OutputFormat {
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() {
return '.mkv';
}
@@ -228,6 +256,15 @@ export class WaveOutputFormat extends OutputFormat {
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() {
return '.wav';
}
+58
View File
@@ -11,9 +11,15 @@ export type OutputOptions = {
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 = {
id: number;
output: Output;
type: TrackType;
} & ({
type: 'video';
source: VideoSource;
@@ -152,6 +158,29 @@ export class Output {
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 = {
id: this._tracks.length + 1,
output: this,
@@ -219,6 +248,35 @@ export class Output {
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._writer.start();