diff --git a/dev/convert.html b/dev/convert.html
index 3778784..76494f1 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -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
},
});
diff --git a/docs/guide/converting-media-files.md b/docs/guide/converting-media-files.md
index 7c92198..b2a43aa 100644
--- a/docs/guide/converting-media-files.md
+++ b/docs/guide/converting-media-files.md
@@ -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:
diff --git a/src/conversion.ts b/src/conversion.ts
index 151e644..221e13a 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -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 (
diff --git a/src/output-format.ts b/src/output-format.ts
index 8e7b635..adef3a9 100644
--- a/src/output-format.ts
+++ b/src/output-format.ts
@@ -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;
+ }
}