Multiple miscellaneous fixes

This commit is contained in:
Vanilagy
2025-02-18 22:13:21 +01:00
parent 3848a59ec3
commit 7988b6c2f8
9 changed files with 50 additions and 39 deletions
+3 -3
View File
@@ -641,7 +641,7 @@ class Conversion {
} }
if (needsResample) { if (needsResample) {
audioSource = await this.resampleAudio(track, codecOfChoice, numberOfChannels, sampleRate); audioSource = this.resampleAudio(track, codecOfChoice, numberOfChannels, sampleRate);
} else { } else {
const source = new AudioDataSource({ const source = new AudioDataSource({
codec: codecOfChoice, codec: codecOfChoice,
@@ -654,7 +654,7 @@ class Conversion {
await this.started; await this.started;
const sink = new AudioDataSink(track); const sink = new AudioDataSink(track);
for await (const { data, timestamp } of sink.data(undefined, this.startTimestamp)) { for await (const { data, timestamp } of sink.data(undefined, this.endTimestamp)) {
if (this.synchronizer.shouldWait(track.id, timestamp)) { if (this.synchronizer.shouldWait(track.id, timestamp)) {
await this.synchronizer.wait(timestamp); await this.synchronizer.wait(timestamp);
} }
@@ -686,7 +686,7 @@ class Conversion {
* Resamples the audio by decoding it, playing it onto an OfflineAudioContext and encoding the * Resamples the audio by decoding it, playing it onto an OfflineAudioContext and encoding the
* resulting AudioBuffer. * resulting AudioBuffer.
*/ */
async resampleAudio( resampleAudio(
track: InputAudioTrack, track: InputAudioTrack,
codec: AudioCodec, codec: AudioCodec,
targetNumberOfChannels: number, targetNumberOfChannels: number,
+11 -12
View File
@@ -27,7 +27,6 @@ import {
COLOR_PRIMARIES_MAP_INVERSE, COLOR_PRIMARIES_MAP_INVERSE,
MATRIX_COEFFICIENTS_MAP_INVERSE, MATRIX_COEFFICIENTS_MAP_INVERSE,
TRANSFER_CHARACTERISTICS_MAP_INVERSE, TRANSFER_CHARACTERISTICS_MAP_INVERSE,
rotationMatrix,
binarySearchLessOrEqual, binarySearchLessOrEqual,
binarySearchExact, binarySearchExact,
Rotation, Rotation,
@@ -38,6 +37,8 @@ import {
TransformationMatrix, TransformationMatrix,
extractRotationFromMatrix, extractRotationFromMatrix,
roundToPrecision, roundToPrecision,
isIso639Dash2LanguageCode,
roundToMultiple,
} from '../misc'; } from '../misc';
import { Reader } from '../reader'; import { Reader } from '../reader';
import { EncodedAudioSample, EncodedVideoSample, PLACEHOLDER_DATA, SampleType } from '../sample'; import { EncodedAudioSample, EncodedVideoSample, PLACEHOLDER_DATA, SampleType } from '../sample';
@@ -175,8 +176,6 @@ type Fragment = {
isKnownToBeFirstFragment: boolean; isKnownToBeFirstFragment: boolean;
}; };
const knownMatrixes = [rotationMatrix(0), rotationMatrix(90), rotationMatrix(180), rotationMatrix(270)];
export class IsobmffDemuxer extends Demuxer { export class IsobmffDemuxer extends Demuxer {
metadataReader: IsobmffReader; metadataReader: IsobmffReader;
currentTrack: InternalTrack | null = null; currentTrack: InternalTrack | null = null;
@@ -657,16 +656,11 @@ export class IsobmffDemuxer extends Demuxer {
this.metadataReader.readFixed_2_30(), this.metadataReader.readFixed_2_30(),
]; ];
const rotation = extractRotationFromMatrix(matrix); const rotation = (roundToMultiple(extractRotationFromMatrix(matrix), 90) + 360) % 360 as Rotation;
const comparisonMatrix = rotationMatrix(rotation); assert(rotation === 0 || rotation === 90 || rotation === 180 || rotation === 270);
const matrixIndex = knownMatrixes.findIndex(mat => mat.every((y, i) => y === comparisonMatrix[i])); // Flip clockwise to counter-clockwise
if (matrixIndex === -1) { track.rotation = (-rotation + 360) % 360 as Rotation;
console.warn(`Wacky rotation matrix ${comparisonMatrix.join(',')}; sticking with no rotation.`);
track.rotation = 0;
} else {
track.rotation = (90 * matrixIndex) as Rotation;
}
}; break; }; break;
case 'elst': { case 'elst': {
@@ -739,6 +733,11 @@ export class IsobmffDemuxer extends Demuxer {
track.languageCode = String.fromCharCode(0x60 + (language & 0b11111)) + track.languageCode; track.languageCode = String.fromCharCode(0x60 + (language & 0b11111)) + track.languageCode;
language >>= 5; language >>= 5;
} }
if (!isIso639Dash2LanguageCode(track.languageCode)) {
// Sometimes the bytes are garbage
track.languageCode = UNDETERMINED_LANGUAGE;
}
} }
}; break; }; break;
+5 -3
View File
@@ -13,7 +13,7 @@ import {
validateSubtitleMetadata, validateSubtitleMetadata,
validateVideoChunkMetadata, validateVideoChunkMetadata,
} from '../codec'; } from '../codec';
import { EncodedAudioSample, EncodedVideoSample } from '../sample'; import { EncodedAudioSample, EncodedVideoSample, SampleType } from '../sample';
import { BufferTarget } from '../target'; import { BufferTarget } from '../target';
export const GLOBAL_TIMESCALE = 1000; export const GLOBAL_TIMESCALE = 1000;
@@ -25,7 +25,7 @@ export type Sample = {
duration: number; duration: number;
data: Uint8Array | null; data: Uint8Array | null;
size: number; size: number;
type: 'key' | 'delta'; type: SampleType;
timescaleUnitsToNextSample: number; timescaleUnitsToNextSample: number;
}; };
@@ -492,7 +492,7 @@ export class IsobmffMuxer extends Muxer {
data: Uint8Array, data: Uint8Array,
timestamp: number, timestamp: number,
duration: number, duration: number,
type: 'key' | 'delta', type: SampleType,
) { ) {
const sample: Sample = { const sample: Sample = {
timestamp, timestamp,
@@ -564,6 +564,8 @@ export class IsobmffMuxer extends Muxer {
const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false); const timescaleUnits = intoTimescale(sample.decodeTimestamp, trackData.timescale, false);
const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits); const delta = Math.round(timescaleUnits - trackData.lastTimescaleUnits);
assert(delta >= 0);
trackData.lastTimescaleUnits += delta; trackData.lastTimescaleUnits += delta;
trackData.lastSample.timescaleUnitsToNextSample = delta; trackData.lastSample.timescaleUnitsToNextSample = delta;
+5
View File
@@ -26,6 +26,7 @@ import {
binarySearchLessOrEqual, binarySearchLessOrEqual,
COLOR_PRIMARIES_MAP_INVERSE, COLOR_PRIMARIES_MAP_INVERSE,
findLastIndex, findLastIndex,
isIso639Dash2LanguageCode,
last, last,
MATRIX_COEFFICIENTS_MAP_INVERSE, MATRIX_COEFFICIENTS_MAP_INVERSE,
Rotation, Rotation,
@@ -749,6 +750,10 @@ export class MatroskaDemuxer extends Demuxer {
if (!this.currentTrack) break; if (!this.currentTrack) break;
this.currentTrack.languageCode = reader.readString(size); this.currentTrack.languageCode = reader.readString(size);
if (!isIso639Dash2LanguageCode(this.currentTrack.languageCode)) {
this.currentTrack.languageCode = UNDETERMINED_LANGUAGE;
}
}; break; }; break;
case EBMLId.Video: { case EBMLId.Video: {
+2 -2
View File
@@ -687,8 +687,8 @@ export class MatroskaMuxer extends Muxer {
} }
const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp!; const relativeTimestamp = msTimestamp - this.currentClusterStartMsTimestamp!;
if (relativeTimestamp < 0) { if (relativeTimestamp < -(2 ** 15)) {
// The chunk lies outside of the current cluster // The block lies too far in the past, it's not representable within this cluster
return; return;
} }
+9
View File
@@ -393,6 +393,10 @@ export const roundToPrecision = (value: number, digits: number) => {
return Math.round(value * factor) / factor; return Math.round(value * factor) / factor;
}; };
export const roundToMultiple = (value: number, multiple: number) => {
return Math.round(value / multiple) * multiple;
};
export const ilog = (x: number) => { export const ilog = (x: number) => {
let ret = 0; let ret = 0;
while (x) { while (x) {
@@ -401,3 +405,8 @@ export const ilog = (x: number) => {
} }
return ret; return ret;
}; };
const ISO_639_2_REGEX = /^[a-z]{3}$/;
export const isIso639Dash2LanguageCode = (x: string) => {
return ISO_639_2_REGEX.test(x);
};
+10 -16
View File
@@ -31,7 +31,7 @@ export abstract class Muxer {
private trackTimestampInfo = new WeakMap<OutputTrack, { private trackTimestampInfo = new WeakMap<OutputTrack, {
timestampOffset: number; timestampOffset: number;
maxTimestamp: number; maxTimestamp: number;
lastKeyFrameTimestamp: number; maxTimestampBeforeLastKeyFrame: number;
}>(); }>();
protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyFrame: boolean) { protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyFrame: boolean) {
@@ -44,7 +44,7 @@ export abstract class Muxer {
timestampInfo = { timestampInfo = {
timestampOffset: timestampInSeconds, timestampOffset: timestampInSeconds,
maxTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, maxTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds,
lastKeyFrameTimestamp: track.source._offsetTimestamps ? 0 : timestampInSeconds, maxTimestampBeforeLastKeyFrame: track.source._offsetTimestamps ? 0 : timestampInSeconds,
}; };
this.trackTimestampInfo.set(track, timestampInfo); this.trackTimestampInfo.set(track, timestampInfo);
} }
@@ -57,22 +57,16 @@ export abstract class Muxer {
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`); throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
} }
if (timestampInSeconds < timestampInfo.lastKeyFrameTimestamp) { if (isKeyFrame) {
throw new Error( timestampInfo.maxTimestampBeforeLastKeyFrame = timestampInfo.maxTimestamp;
`Timestamp cannot be smaller than last key frame's timestamp (got ${timestampInSeconds}s,`
+ ` last key frame at ${timestampInfo.lastKeyFrameTimestamp}s).`,
);
} }
if (isKeyFrame) { if (timestampInSeconds < timestampInfo.maxTimestampBeforeLastKeyFrame) {
if (timestampInSeconds < timestampInfo.maxTimestamp) { throw new Error(
throw new Error( `Timestamps cannot be smaller than the highest timestamp of the previous run (a run begins with a`
`Key frame timestamps cannot be smaller than any timestamp that came before` + ` key frame and ends right before the next key frame). Got ${timestampInSeconds}s, but highest`
+ ` (got ${timestampInSeconds}s, max timestamp was ${timestampInfo.maxTimestamp}s).`, + ` timestamp is ${timestampInfo.maxTimestampBeforeLastKeyFrame}s.`,
); );
}
timestampInfo.lastKeyFrameTimestamp = timestampInSeconds;
} }
timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds); timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
+2 -2
View File
@@ -1,4 +1,4 @@
import { AsyncMutex, TransformationMatrix } from './misc'; import { AsyncMutex, isIso639Dash2LanguageCode, TransformationMatrix } from './misc';
import { Muxer } from './muxer'; import { Muxer } from './muxer';
import { OutputFormat } from './output-format'; import { OutputFormat } from './output-format';
import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './media-source'; import { AudioSource, MediaSource, SubtitleSource, VideoSource } from './media-source';
@@ -60,7 +60,7 @@ const validateBaseTrackMetadata = (metadata: BaseTrackMetadata) => {
if (!metadata || typeof metadata !== 'object') { if (!metadata || typeof metadata !== 'object') {
throw new TypeError('metadata must be an object.'); throw new TypeError('metadata must be an object.');
} }
if (metadata.languageCode !== undefined && !/^[a-z]{3}$/.test(metadata.languageCode)) { 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 language code.');
} }
}; };
+2
View File
@@ -1,3 +1,5 @@
- onHeader, etc callbacks for Matroska - onHeader, etc callbacks for Matroska
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now - https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
- is this fixed? https://github.com/Vanilagy/webm-muxer/issues/50 - is this fixed? https://github.com/Vanilagy/webm-muxer/issues/50
- cross-track offset for streaming sources
- configurable fragmented mp4 fragment size, like the mp4-muxer PR