Add language getter to InputTrack

This commit is contained in:
Vanilagy
2025-01-15 20:21:34 +01:00
parent 2bec661985
commit 7b6567fcf0
8 changed files with 49 additions and 3 deletions
+2
View File
@@ -43,6 +43,8 @@
const videoTrack = await input.getPrimaryVideoTrack(); const videoTrack = await input.getPrimaryVideoTrack();
const sink = new Metamuxer.VideoFrameSink(videoTrack); const sink = new Metamuxer.VideoFrameSink(videoTrack);
console.log(await videoTrack.getLanguageCode());
for await (const thing of sink.framesAtTimestamps([0.2, 1, 2, 423])) { for await (const thing of sink.framesAtTimestamps([0.2, 1, 2, 423])) {
console.log(thing) console.log(thing)
} }
+5
View File
@@ -7,6 +7,7 @@ export interface InputTrackBacking {
getCodec(): Promise<MediaCodec | null>; getCodec(): Promise<MediaCodec | null>;
getFirstTimestamp(): Promise<number>; getFirstTimestamp(): Promise<number>;
computeDuration(): Promise<number>; computeDuration(): Promise<number>;
getLanguageCode(): Promise<string>;
} }
/** @public */ /** @public */
@@ -39,6 +40,10 @@ export abstract class InputTrack {
computeDuration() { computeDuration() {
return this._backing.computeDuration(); return this._backing.computeDuration();
} }
getLanguageCode() {
return this._backing.getLanguageCode();
}
} }
export interface InputVideoTrackBacking extends InputTrackBacking { export interface InputVideoTrackBacking extends InputTrackBacking {
+2 -1
View File
@@ -11,6 +11,7 @@ import {
colorSpaceIsComplete, colorSpaceIsComplete,
IDENTITY_MATRIX, IDENTITY_MATRIX,
rotationMatrix, rotationMatrix,
UNDETERMINED_LANGUAGE,
} from '../misc'; } from '../misc';
import { import {
AudioCodec, AudioCodec,
@@ -429,7 +430,7 @@ export const mdhd = (
const u32OrU64 = needsU64 ? u64 : u32; const u32OrU64 = needsU64 ? u64 : u32;
let language = 0; let language = 0;
for (const character of (trackData.track.metadata.languageCode ?? 'und')) { for (const character of (trackData.track.metadata.languageCode ?? UNDETERMINED_LANGUAGE)) {
language <<= 5; language <<= 5;
language += character.charCodeAt(0) - 0x60; language += character.charCodeAt(0) - 0x60;
} }
+18
View File
@@ -34,6 +34,7 @@ import {
last, last,
AsyncMutex, AsyncMutex,
findLastIndex, findLastIndex,
UNDETERMINED_LANGUAGE,
} 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';
@@ -47,6 +48,7 @@ type InternalTrack = {
durationInMovieTimescale: number; durationInMovieTimescale: number;
durationInMediaTimescale: number; durationInMediaTimescale: number;
rotation: Rotation; rotation: Rotation;
languageCode: string;
sampleTableByteOffset: number; sampleTableByteOffset: number;
sampleTable: SampleTable | null; sampleTable: SampleTable | null;
fragmentLookupTable: FragmentLookupTableEntry[] | null; fragmentLookupTable: FragmentLookupTableEntry[] | null;
@@ -557,6 +559,7 @@ export class IsobmffDemuxer extends Demuxer {
durationInMovieTimescale: -1, durationInMovieTimescale: -1,
durationInMediaTimescale: -1, durationInMediaTimescale: -1,
rotation: 0, rotation: 0,
languageCode: UNDETERMINED_LANGUAGE,
sampleTableByteOffset: -1, sampleTableByteOffset: -1,
sampleTable: null, sampleTable: null,
fragmentLookupTable: null, fragmentLookupTable: null,
@@ -642,6 +645,17 @@ export class IsobmffDemuxer extends Demuxer {
track.timescale = this.isobmffReader.readU32(); track.timescale = this.isobmffReader.readU32();
track.durationInMediaTimescale = this.isobmffReader.readU64(); track.durationInMediaTimescale = this.isobmffReader.readU64();
} }
let language = this.isobmffReader.readU16();
if (language > 0) {
track.languageCode = '';
for (let i = 0; i < 3; i++) {
track.languageCode = String.fromCharCode(0x60 + (language & 0b11111)) + track.languageCode;
language >>= 5;
}
}
}; break; }; break;
case 'hdlr': { case 'hdlr': {
@@ -1689,6 +1703,10 @@ abstract class IsobmffTrackBacking<
return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0); return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0);
} }
async getLanguageCode() {
return this.internalTrack.languageCode;
}
async getFirstTimestamp() { async getFirstTimestamp() {
const firstSample = await this.getFirstSample({ metadataOnly: true }); const firstSample = await this.getFirstSample({ metadataOnly: true });
return firstSample?.timestamp ?? 0; return firstSample?.timestamp ?? 0;
+13
View File
@@ -30,6 +30,7 @@ import {
MATRIX_COEFFICIENTS_MAP_INVERSE, MATRIX_COEFFICIENTS_MAP_INVERSE,
Rotation, Rotation,
TRANSFER_CHARACTERISTICS_MAP_INVERSE, TRANSFER_CHARACTERISTICS_MAP_INVERSE,
UNDETERMINED_LANGUAGE,
} 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';
@@ -107,6 +108,7 @@ type InternalTrack = {
inputTrack: InputTrack | null; inputTrack: InputTrack | null;
codecId: string | null; codecId: string | null;
codecPrivate: Uint8Array | null; codecPrivate: Uint8Array | null;
languageCode: string;
info: info:
| null | null
| { | {
@@ -583,6 +585,7 @@ export class MatroskaDemuxer extends Demuxer {
inputTrack: null, inputTrack: null,
codecId: null, codecId: null,
codecPrivate: null, codecPrivate: null,
languageCode: UNDETERMINED_LANGUAGE,
info: null, info: null,
}; };
@@ -733,6 +736,12 @@ export class MatroskaDemuxer extends Demuxer {
this.currentTrack.codecPrivate = reader.readBytes(size); this.currentTrack.codecPrivate = reader.readBytes(size);
}; break; }; break;
case EBMLId.Language: {
if (!this.currentTrack) break;
this.currentTrack.languageCode = reader.readString(size);
}; break;
case EBMLId.Video: { case EBMLId.Video: {
if (this.currentTrack?.info?.type !== 'video') break; if (this.currentTrack?.info?.type !== 'video') break;
@@ -967,6 +976,10 @@ abstract class MatroskaTrackBacking<
return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0); return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0);
} }
async getLanguageCode() {
return this.internalTrack.languageCode;
}
async getFirstTimestamp() { async getFirstTimestamp() {
const firstSample = await this.getFirstSample({ metadataOnly: true }); const firstSample = await this.getFirstSample({ metadataOnly: true });
return firstSample?.timestamp ?? 0; return firstSample?.timestamp ?? 0;
+2 -1
View File
@@ -2,6 +2,7 @@ import {
COLOR_PRIMARIES_MAP, COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP, MATRIX_COEFFICIENTS_MAP,
TRANSFER_CHARACTERISTICS_MAP, TRANSFER_CHARACTERISTICS_MAP,
UNDETERMINED_LANGUAGE,
assert, assert,
colorSpaceIsComplete, colorSpaceIsComplete,
readBits, readBits,
@@ -241,7 +242,7 @@ export class MatroskaMuxer extends Muxer {
{ id: EBMLId.TrackUID, data: trackData.track.id }, { id: EBMLId.TrackUID, data: trackData.track.id },
{ id: EBMLId.TrackType, data: TRACK_TYPE_MAP[trackData.type] }, { id: EBMLId.TrackType, data: TRACK_TYPE_MAP[trackData.type] },
{ id: EBMLId.FlagLacing, data: 0 }, { id: EBMLId.FlagLacing, data: 0 },
{ id: EBMLId.Language, data: trackData.track.metadata.languageCode ?? 'und' }, { id: EBMLId.Language, data: trackData.track.metadata.languageCode ?? UNDETERMINED_LANGUAGE },
{ id: EBMLId.CodecID, data: codecId }, { id: EBMLId.CodecID, data: codecId },
{ id: EBMLId.CodecDelay, data: 0 }, { id: EBMLId.CodecDelay, data: 0 },
{ id: EBMLId.SeekPreRoll, data: seekPreRollNs }, { id: EBMLId.SeekPreRoll, data: seekPreRollNs },
+2
View File
@@ -329,3 +329,5 @@ export const mapAsyncGenerator = <T, U>(
export const clamp = (value: number, min: number, max: number) => { export const clamp = (value: number, min: number, max: number) => {
return Math.max(min, Math.min(max, value)); return Math.max(min, Math.min(max, value));
}; };
export const UNDETERMINED_LANGUAGE = 'und';
+5 -1
View File
@@ -3,7 +3,7 @@ import { Demuxer } from '../demuxer';
import { Input } from '../input'; import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track'; import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { SampleRetrievalOptions } from '../media-sink'; import { SampleRetrievalOptions } from '../media-sink';
import { assert } from '../misc'; import { assert, UNDETERMINED_LANGUAGE } from '../misc';
import { Reader } from '../reader'; import { Reader } from '../reader';
import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample'; import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample';
import { RiffReader } from './riff-reader'; import { RiffReader } from './riff-reader';
@@ -215,6 +215,10 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
return this.demuxer.audioInfo.sampleRate; return this.demuxer.audioInfo.sampleRate;
} }
async getLanguageCode() {
return UNDETERMINED_LANGUAGE;
}
async getFirstTimestamp() { async getFirstTimestamp() {
return 0; return 0;
} }