Allow negative trim ranges for conversion, add OutputFormat.supportsTimestampedMediaData

This commit is contained in:
Vanilagy
2026-02-11 15:56:51 +01:00
parent 417c85d8ed
commit 4fae45ec40
4 changed files with 66 additions and 10 deletions
+7 -4
View File
@@ -24,7 +24,7 @@
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.Mp4OutputFormat({});
const outputFormat = new Mediabunny.WavOutputFormat({});
const button = document.createElement('button');
button.textContent = 'Cancel';
@@ -100,8 +100,9 @@
},
*/
video: () => ({
forceTranscode: true,
allowRotationMetadata: false,
width: 320,
//forceTranscode: true,
//allowRotationMetadata: false,
//width: 720,
//frameRate: 30,
//bitrate: Mediabunny.QUALITY_VERY_LOW,
@@ -181,7 +182,9 @@
}
},
trim: {
start: 0,
start: -2,
end: 5,
//start: 0,
//end: 4
},
});
+13
View File
@@ -367,6 +367,19 @@ If only `start` is set, the clip will run until the end of the input file. If on
Note that when using the trimming defaults, the resulting media file will always begin at timestamp 0. If your input file has a start time offset (like is common with MPEG-TS files) and you want to retain that, use `trim: { start: 0 }` to ensure timestamps don't get shifted.
---
You can even use negative trimming values to offset the start of the media:
```ts
const conversion = await Conversion.init({
// ...
trim: {
start: -2, // Two seconds of no media data (freeze frame / silence) at the start
},
// ...
});
```
## Metadata tags
By default, any [descriptive metadata tags](../api/MetadataTags.md) of the input will be copied to the output. If you want to further control the metadata tags written to the output, you can use the `tags` options:
+7 -6
View File
@@ -564,11 +564,11 @@ export class Conversion {
if (options.trim !== undefined && (!options.trim || typeof options.trim !== 'object')) {
throw new TypeError('options.trim, when provided, must be an object.');
}
if (options.trim?.start !== undefined && (!Number.isFinite(options.trim.start) || options.trim.start < 0)) {
throw new TypeError('options.trim.start, when provided, must be a non-negative number.');
if (options.trim?.start !== undefined && (!Number.isFinite(options.trim.start))) {
throw new TypeError('options.trim.start, when provided, must be a finite number.');
}
if (options.trim?.end !== undefined && (!Number.isFinite(options.trim.end) || options.trim.end < 0)) {
throw new TypeError('options.trim.end, when provided, must be a non-negative number.');
if (options.trim?.end !== undefined && (!Number.isFinite(options.trim.end))) {
throw new TypeError('options.trim.end, when provided, must be a finite number.');
}
if (
options.trim?.start !== undefined
@@ -607,7 +607,7 @@ export class Conversion {
// those out by default.
0,
);
this._endTimestamp = this._options.trim?.end ?? Infinity;
this._endTimestamp = Math.max(this._options.trim?.end ?? Infinity, this._startTimestamp);
const inputTracks = await this.input.getTracks();
const outputTrackCounts = this.output.format.getSupportedTrackCounts();
@@ -1326,7 +1326,8 @@ export class Conversion {
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
let needsResample = numberOfChannels !== originalNumberOfChannels
|| sampleRate !== originalSampleRate
|| firstTimestamp < this._startTimestamp;
|| firstTimestamp < this._startTimestamp
|| (firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData);
let audioCodecs = this.output.format.getSupportedAudioCodecs();
if (
+39
View File
@@ -74,6 +74,13 @@ export abstract class OutputFormat {
abstract getSupportedTrackCounts(): TrackCountLimits;
/** Whether this output format supports video rotation metadata. */
abstract get supportsVideoRotationMetadata(): boolean;
/**
* Whether this output format's tracks store timestamped media data. When `true`, the timestamps of added packets
* will be respected, allowing things like gaps in media data or non-zero start times. When `false`, the format's
* media data implicitly starts at zero and follows an implicit sequential timing from there, using the intrinsic
* durations of the media data.
*/
abstract get supportsTimestampedMediaData(): boolean;
/** Returns a list of video codecs that this output format can contain. */
getSupportedVideoCodecs() {
@@ -258,6 +265,10 @@ export abstract class IsobmffOutputFormat extends OutputFormat {
return true;
}
get supportsTimestampedMediaData() {
return true;
}
/** @internal */
_createMuxer(output: Output) {
return new IsobmffMuxer(output, this);
@@ -490,6 +501,10 @@ export class MkvOutputFormat extends OutputFormat {
// While it technically does support it with ProjectionPoseRoll, many players appear to ignore this value
return false;
}
get supportsTimestampedMediaData() {
return true;
}
}
/**
@@ -626,6 +641,10 @@ export class Mp3OutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return false;
}
}
/**
@@ -724,6 +743,10 @@ export class WavOutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return false;
}
}
/**
@@ -815,6 +838,10 @@ export class OggOutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return false;
}
}
/**
@@ -889,6 +916,10 @@ export class AdtsOutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return false;
}
}
/**
@@ -960,6 +991,10 @@ export class FlacOutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return false;
}
}
/**
@@ -1041,4 +1076,8 @@ export class MpegTsOutputFormat extends OutputFormat {
get supportsVideoRotationMetadata() {
return false;
}
get supportsTimestampedMediaData() {
return true;
}
}