diff --git a/dev/demux.html b/dev/demux.html
index 8fd7a01..7d7796e 100644
--- a/dev/demux.html
+++ b/dev/demux.html
@@ -43,6 +43,8 @@
const videoTrack = await input.getPrimaryVideoTrack();
const sink = new Metamuxer.VideoFrameSink(videoTrack);
+ console.log(await videoTrack.getLanguageCode());
+
for await (const thing of sink.framesAtTimestamps([0.2, 1, 2, 423])) {
console.log(thing)
}
diff --git a/src/input-track.ts b/src/input-track.ts
index bc8d09a..1282bf7 100644
--- a/src/input-track.ts
+++ b/src/input-track.ts
@@ -7,6 +7,7 @@ export interface InputTrackBacking {
getCodec(): Promise;
getFirstTimestamp(): Promise;
computeDuration(): Promise;
+ getLanguageCode(): Promise;
}
/** @public */
@@ -39,6 +40,10 @@ export abstract class InputTrack {
computeDuration() {
return this._backing.computeDuration();
}
+
+ getLanguageCode() {
+ return this._backing.getLanguageCode();
+ }
}
export interface InputVideoTrackBacking extends InputTrackBacking {
diff --git a/src/isobmff/isobmff-boxes.ts b/src/isobmff/isobmff-boxes.ts
index b7f0449..55259ab 100644
--- a/src/isobmff/isobmff-boxes.ts
+++ b/src/isobmff/isobmff-boxes.ts
@@ -11,6 +11,7 @@ import {
colorSpaceIsComplete,
IDENTITY_MATRIX,
rotationMatrix,
+ UNDETERMINED_LANGUAGE,
} from '../misc';
import {
AudioCodec,
@@ -429,7 +430,7 @@ export const mdhd = (
const u32OrU64 = needsU64 ? u64 : u32;
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 += character.charCodeAt(0) - 0x60;
}
diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts
index 3b39ffc..9f191b8 100644
--- a/src/isobmff/isobmff-demuxer.ts
+++ b/src/isobmff/isobmff-demuxer.ts
@@ -34,6 +34,7 @@ import {
last,
AsyncMutex,
findLastIndex,
+ UNDETERMINED_LANGUAGE,
} from '../misc';
import { Reader } from '../reader';
import { EncodedAudioSample, EncodedVideoSample, PLACEHOLDER_DATA, SampleType } from '../sample';
@@ -47,6 +48,7 @@ type InternalTrack = {
durationInMovieTimescale: number;
durationInMediaTimescale: number;
rotation: Rotation;
+ languageCode: string;
sampleTableByteOffset: number;
sampleTable: SampleTable | null;
fragmentLookupTable: FragmentLookupTableEntry[] | null;
@@ -557,6 +559,7 @@ export class IsobmffDemuxer extends Demuxer {
durationInMovieTimescale: -1,
durationInMediaTimescale: -1,
rotation: 0,
+ languageCode: UNDETERMINED_LANGUAGE,
sampleTableByteOffset: -1,
sampleTable: null,
fragmentLookupTable: null,
@@ -642,6 +645,17 @@ export class IsobmffDemuxer extends Demuxer {
track.timescale = this.isobmffReader.readU32();
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;
case 'hdlr': {
@@ -1689,6 +1703,10 @@ abstract class IsobmffTrackBacking<
return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0);
}
+ async getLanguageCode() {
+ return this.internalTrack.languageCode;
+ }
+
async getFirstTimestamp() {
const firstSample = await this.getFirstSample({ metadataOnly: true });
return firstSample?.timestamp ?? 0;
diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts
index f355c40..8b82991 100644
--- a/src/matroska/matroska-demuxer.ts
+++ b/src/matroska/matroska-demuxer.ts
@@ -30,6 +30,7 @@ import {
MATRIX_COEFFICIENTS_MAP_INVERSE,
Rotation,
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
+ UNDETERMINED_LANGUAGE,
} from '../misc';
import { Reader } from '../reader';
import { EncodedAudioSample, EncodedVideoSample, PLACEHOLDER_DATA, SampleType } from '../sample';
@@ -107,6 +108,7 @@ type InternalTrack = {
inputTrack: InputTrack | null;
codecId: string | null;
codecPrivate: Uint8Array | null;
+ languageCode: string;
info:
| null
| {
@@ -583,6 +585,7 @@ export class MatroskaDemuxer extends Demuxer {
inputTrack: null,
codecId: null,
codecPrivate: null,
+ languageCode: UNDETERMINED_LANGUAGE,
info: null,
};
@@ -733,6 +736,12 @@ export class MatroskaDemuxer extends Demuxer {
this.currentTrack.codecPrivate = reader.readBytes(size);
}; break;
+ case EBMLId.Language: {
+ if (!this.currentTrack) break;
+
+ this.currentTrack.languageCode = reader.readString(size);
+ }; break;
+
case EBMLId.Video: {
if (this.currentTrack?.info?.type !== 'video') break;
@@ -967,6 +976,10 @@ abstract class MatroskaTrackBacking<
return (lastSample?.timestamp ?? 0) + (lastSample?.duration ?? 0);
}
+ async getLanguageCode() {
+ return this.internalTrack.languageCode;
+ }
+
async getFirstTimestamp() {
const firstSample = await this.getFirstSample({ metadataOnly: true });
return firstSample?.timestamp ?? 0;
diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts
index 7c6f4a7..36f8210 100644
--- a/src/matroska/matroska-muxer.ts
+++ b/src/matroska/matroska-muxer.ts
@@ -2,6 +2,7 @@ import {
COLOR_PRIMARIES_MAP,
MATRIX_COEFFICIENTS_MAP,
TRANSFER_CHARACTERISTICS_MAP,
+ UNDETERMINED_LANGUAGE,
assert,
colorSpaceIsComplete,
readBits,
@@ -241,7 +242,7 @@ export class MatroskaMuxer extends Muxer {
{ id: EBMLId.TrackUID, data: trackData.track.id },
{ id: EBMLId.TrackType, data: TRACK_TYPE_MAP[trackData.type] },
{ 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.CodecDelay, data: 0 },
{ id: EBMLId.SeekPreRoll, data: seekPreRollNs },
diff --git a/src/misc.ts b/src/misc.ts
index 5bfd2b3..500eea6 100644
--- a/src/misc.ts
+++ b/src/misc.ts
@@ -329,3 +329,5 @@ export const mapAsyncGenerator = (
export const clamp = (value: number, min: number, max: number) => {
return Math.max(min, Math.min(max, value));
};
+
+export const UNDETERMINED_LANGUAGE = 'und';
diff --git a/src/wave/wave-demuxer.ts b/src/wave/wave-demuxer.ts
index 557085a..68e856e 100644
--- a/src/wave/wave-demuxer.ts
+++ b/src/wave/wave-demuxer.ts
@@ -3,7 +3,7 @@ import { Demuxer } from '../demuxer';
import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { SampleRetrievalOptions } from '../media-sink';
-import { assert } from '../misc';
+import { assert, UNDETERMINED_LANGUAGE } from '../misc';
import { Reader } from '../reader';
import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample';
import { RiffReader } from './riff-reader';
@@ -215,6 +215,10 @@ class WaveAudioTrackBacking implements InputAudioTrackBacking {
return this.demuxer.audioInfo.sampleRate;
}
+ async getLanguageCode() {
+ return UNDETERMINED_LANGUAGE;
+ }
+
async getFirstTimestamp() {
return 0;
}