mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 05:13:50 +02:00
Allow fractional frame rates in track metadata, document frameRate better, other small doc adjustments
This commit is contained in:
+1
-1
@@ -70,7 +70,7 @@ export abstract class InputTrack {
|
||||
return this._backing.getId();
|
||||
}
|
||||
|
||||
/** The ISO 639-2 language code for this track. If the language is unknown, this field is 'und' (undetermined). */
|
||||
/** The ISO 639-2/T language code for this track. If the language is unknown, this field is 'und' (undetermined). */
|
||||
get languageCode() {
|
||||
return this._backing.getLanguageCode();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Box, ftyp, IsobmffBoxWriter, mdat, mfra, moof, moov, vtta, vttc, vtte }
|
||||
import { Muxer } from '../muxer';
|
||||
import { Output, OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from '../output';
|
||||
import { BufferTargetWriter, Writer } from '../writer';
|
||||
import { assert, last } from '../misc';
|
||||
import { assert, computeRationalApproximation, last } from '../misc';
|
||||
import { IsobmffOutputFormatOptions, IsobmffOutputFormat, MovOutputFormat } from '../output-format';
|
||||
import { inlineTimestampRegex, SubtitleConfig, SubtitleCue, SubtitleMetadata } from '../subtitles';
|
||||
import {
|
||||
@@ -196,6 +196,11 @@ export class IsobmffMuxer extends Muxer {
|
||||
assert(meta.decoderConfig.codedWidth !== undefined);
|
||||
assert(meta.decoderConfig.codedHeight !== undefined);
|
||||
|
||||
// The frame rate set by the user may not be an integer. Since timescale is an integer, we'll approximate the
|
||||
// frame time (inverse of frame rate) with a rational number, then use that approximation's denominator
|
||||
// as the timescale.
|
||||
const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
|
||||
|
||||
const newTrackData: IsobmffVideoTrackData = {
|
||||
track,
|
||||
type: 'video',
|
||||
@@ -204,7 +209,7 @@ export class IsobmffMuxer extends Muxer {
|
||||
height: meta.decoderConfig.codedHeight,
|
||||
decoderConfig: meta.decoderConfig,
|
||||
},
|
||||
timescale: track.metadata.frameRate ?? 57600,
|
||||
timescale,
|
||||
samples: [],
|
||||
sampleQueue: [],
|
||||
timestampProcessingQueue: [],
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
colorSpaceIsComplete,
|
||||
normalizeRotation,
|
||||
readBits,
|
||||
roundToMultiple,
|
||||
textEncoder,
|
||||
toUint8Array,
|
||||
writeBits,
|
||||
@@ -510,8 +511,16 @@ export class MatroskaMuxer extends Muxer {
|
||||
const trackData = this.getVideoTrackData(track, meta);
|
||||
|
||||
const isKeyFrame = packet.type === 'key';
|
||||
const timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame);
|
||||
const videoChunk = this.createInternalChunk(packet.data, timestamp, packet.duration, packet.type);
|
||||
let timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame);
|
||||
let duration = packet.duration;
|
||||
|
||||
if (track.metadata.frameRate !== undefined) {
|
||||
// Constrain the time values to the frame rate
|
||||
timestamp = roundToMultiple(timestamp, 1 / track.metadata.frameRate);
|
||||
duration = roundToMultiple(duration, 1 / track.metadata.frameRate);
|
||||
}
|
||||
|
||||
const videoChunk = this.createInternalChunk(packet.data, timestamp, duration, packet.type);
|
||||
if (track.source._codec === 'vp9') this.fixVP9ColorSpace(trackData, videoChunk);
|
||||
|
||||
trackData.chunkQueue.push(videoChunk);
|
||||
|
||||
+44
@@ -450,3 +450,47 @@ export const retriedFetch = async (
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const computeRationalApproximation = (x: number, maxDenominator: number) => {
|
||||
// Handle negative numbers
|
||||
const sign = x < 0 ? -1 : 1;
|
||||
x = Math.abs(x);
|
||||
|
||||
let prevNumerator = 0, prevDenominator = 1;
|
||||
let currNumerator = 1, currDenominator = 0;
|
||||
|
||||
// Continued fraction algorithm
|
||||
let remainder = x;
|
||||
|
||||
while (true) {
|
||||
const integer = Math.floor(remainder);
|
||||
|
||||
// Calculate next convergent
|
||||
const nextNumerator = integer * currNumerator + prevNumerator;
|
||||
const nextDenominator = integer * currDenominator + prevDenominator;
|
||||
|
||||
if (nextDenominator > maxDenominator) {
|
||||
return {
|
||||
numerator: sign * currNumerator,
|
||||
denominator: currDenominator,
|
||||
};
|
||||
}
|
||||
|
||||
prevNumerator = currNumerator;
|
||||
prevDenominator = currDenominator;
|
||||
currNumerator = nextNumerator;
|
||||
currDenominator = nextDenominator;
|
||||
|
||||
remainder = 1 / (remainder - integer);
|
||||
|
||||
// Guard against precision issues
|
||||
if (!isFinite(remainder)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
numerator: sign * currNumerator,
|
||||
denominator: currDenominator,
|
||||
};
|
||||
};
|
||||
|
||||
+9
-5
@@ -57,7 +57,7 @@ export type OutputSubtitleTrack = OutputTrack & { type: 'subtitle' };
|
||||
* @public
|
||||
*/
|
||||
export type BaseTrackMetadata = {
|
||||
/** The three-letter, ISO 639-2 language code specifying the language of this track. */
|
||||
/** The three-letter, ISO 639-2/T language code specifying the language of this track. */
|
||||
languageCode?: string;
|
||||
};
|
||||
|
||||
@@ -68,7 +68,11 @@ export type BaseTrackMetadata = {
|
||||
export type VideoTrackMetadata = BaseTrackMetadata & {
|
||||
/** The angle in degrees by which the track's frames should be rotated (clockwise). */
|
||||
rotation?: Rotation;
|
||||
/** The expected video frame rate. You should not exceed the value you set here. */
|
||||
/**
|
||||
* The expected video frame rate in hertz. If set, all timestamps and durations of this track will be snapped to
|
||||
* this frame rate. You should avoid adding more frames than the rate allows, as this will lead to multiple frames
|
||||
* with the same timestamp.
|
||||
*/
|
||||
frameRate?: number;
|
||||
};
|
||||
/**
|
||||
@@ -87,7 +91,7 @@ const validateBaseTrackMetadata = (metadata: BaseTrackMetadata) => {
|
||||
throw new TypeError('metadata must be an object.');
|
||||
}
|
||||
if (metadata.languageCode !== undefined && !isIso639Dash2LanguageCode(metadata.languageCode)) {
|
||||
throw new TypeError('metadata.languageCode must be a three-letter, ISO 639-2 language code.');
|
||||
throw new TypeError('metadata.languageCode must be a three-letter, ISO 639-2/T language code.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,10 +162,10 @@ export class Output<
|
||||
}
|
||||
if (
|
||||
metadata.frameRate !== undefined
|
||||
&& (!Number.isInteger(metadata.frameRate) || metadata.frameRate <= 0)
|
||||
&& (!Number.isFinite(metadata.frameRate) || metadata.frameRate <= 0)
|
||||
) {
|
||||
throw new TypeError(
|
||||
`Invalid video frame rate: ${metadata.frameRate}. Must be a positive integer.`,
|
||||
`Invalid video frame rate: ${metadata.frameRate}. Must be a positive number.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user