mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Add support for header removal content compression in Matroska
This commit is contained in:
+23
-1
@@ -14,7 +14,29 @@
|
|||||||
source: new Mediabunny.BlobSource(file),
|
source: new Mediabunny.BlobSource(file),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(await input.getMetadataTags())
|
const videoTrack = await input.getPrimaryVideoTrack();
|
||||||
|
const sink = new Mediabunny.VideoSampleSink(videoTrack);
|
||||||
|
const sink2 = new Mediabunny.EncodedPacketSink(videoTrack);
|
||||||
|
|
||||||
|
for await (const packet of sink2.packets()) {
|
||||||
|
console.log(packet)//, await videoTrack.determinePacketType(packet));
|
||||||
|
|
||||||
|
if (packet.timestamp >= 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(await sink.getSample(0))
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
for await (const sample of sink.samples()) {
|
||||||
|
console.log(sample);
|
||||||
|
|
||||||
|
if (sample.timestamp >= 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
|
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ export enum EBMLId {
|
|||||||
TagLanguage = 0x447a,
|
TagLanguage = 0x447a,
|
||||||
TagString = 0x4487,
|
TagString = 0x4487,
|
||||||
TagBinary = 0x4485,
|
TagBinary = 0x4485,
|
||||||
|
ContentEncodings = 0x6d80,
|
||||||
|
ContentEncoding = 0x6240,
|
||||||
|
ContentEncodingOrder = 0x5031,
|
||||||
|
ContentEncodingScope = 0x5032,
|
||||||
|
ContentCompression = 0x5034,
|
||||||
|
ContentCompAlgo = 0x4254,
|
||||||
|
ContentCompSettings = 0x4255,
|
||||||
|
ContentEncryption = 0x5035,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LEVEL_0_EBML_IDS: EBMLId[] = [
|
export const LEVEL_0_EBML_IDS: EBMLId[] = [
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ type SeekEntry = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Cluster = {
|
type Cluster = {
|
||||||
|
segment: Segment;
|
||||||
elementStartPos: number;
|
elementStartPos: number;
|
||||||
elementEndPos: number;
|
elementEndPos: number;
|
||||||
dataStartPos: number;
|
dataStartPos: number;
|
||||||
@@ -115,6 +116,7 @@ type Cluster = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ClusterTrackData = {
|
type ClusterTrackData = {
|
||||||
|
track: InternalTrack | null;
|
||||||
startTimestamp: number;
|
startTimestamp: number;
|
||||||
endTimestamp: number;
|
endTimestamp: number;
|
||||||
firstKeyFrameTimestamp: number | null;
|
firstKeyFrameTimestamp: number | null;
|
||||||
@@ -147,6 +149,32 @@ type CuePoint = {
|
|||||||
clusterPosition: number;
|
clusterPosition: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ContentEncodingScope {
|
||||||
|
Block = 1,
|
||||||
|
Private = 2,
|
||||||
|
Next = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContentCompAlgo {
|
||||||
|
Zlib,
|
||||||
|
Bzlib,
|
||||||
|
lzo1x,
|
||||||
|
HeaderStripping,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecodingInstruction = {
|
||||||
|
order: number;
|
||||||
|
scope: ContentEncodingScope;
|
||||||
|
data: {
|
||||||
|
type: 'decompress';
|
||||||
|
algorithm: ContentCompAlgo | null;
|
||||||
|
settings: Uint8Array | null;
|
||||||
|
} | {
|
||||||
|
type: 'decrypt';
|
||||||
|
// Don't store more yet since this operation is unsupported
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
type InternalTrack = {
|
type InternalTrack = {
|
||||||
id: number;
|
id: number;
|
||||||
demuxer: MatroskaDemuxer;
|
demuxer: MatroskaDemuxer;
|
||||||
@@ -162,6 +190,8 @@ type InternalTrack = {
|
|||||||
defaultDuration: number | null;
|
defaultDuration: number | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
languageCode: string;
|
languageCode: string;
|
||||||
|
decodingInstructions: DecodingInstruction[];
|
||||||
|
|
||||||
info:
|
info:
|
||||||
| null
|
| null
|
||||||
| {
|
| {
|
||||||
@@ -205,6 +235,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
currentCluster: Cluster | null = null;
|
currentCluster: Cluster | null = null;
|
||||||
currentBlock: ClusterBlock | null = null;
|
currentBlock: ClusterBlock | null = null;
|
||||||
currentCueTime: number | null = null;
|
currentCueTime: number | null = null;
|
||||||
|
currentDecodingInstruction: DecodingInstruction | null = null;
|
||||||
currentTagTargetIsMovie: boolean = true;
|
currentTagTargetIsMovie: boolean = true;
|
||||||
currentSimpleTagName: string | null = null;
|
currentSimpleTagName: string | null = null;
|
||||||
currentAttachedFile: {
|
currentAttachedFile: {
|
||||||
@@ -583,6 +614,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
if (dataSlice instanceof Promise) dataSlice = await dataSlice;
|
||||||
|
|
||||||
const cluster: Cluster = {
|
const cluster: Cluster = {
|
||||||
|
segment,
|
||||||
elementStartPos,
|
elementStartPos,
|
||||||
elementEndPos: dataStartPos + size,
|
elementEndPos: dataStartPos + size,
|
||||||
dataStartPos,
|
dataStartPos,
|
||||||
@@ -597,8 +629,8 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
this.readContiguousElements(dataSlice);
|
this.readContiguousElements(dataSlice);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [trackId, trackData] of cluster.trackData) {
|
for (const [, trackData] of cluster.trackData) {
|
||||||
const track = segment.tracks.find(x => x.id === trackId) ?? null;
|
const track = trackData.track;
|
||||||
|
|
||||||
// This must hold, as track datas only get created if a block for that track is encountered
|
// This must hold, as track datas only get created if a block for that track is encountered
|
||||||
assert(trackData.blocks.length > 0);
|
assert(trackData.blocks.length > 0);
|
||||||
@@ -683,6 +715,7 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
let trackData = cluster.trackData.get(trackNumber);
|
let trackData = cluster.trackData.get(trackNumber);
|
||||||
if (!trackData) {
|
if (!trackData) {
|
||||||
trackData = {
|
trackData = {
|
||||||
|
track: cluster.segment.tracks.find(x => x.id === trackNumber) ?? null,
|
||||||
startTimestamp: 0,
|
startTimestamp: 0,
|
||||||
endTimestamp: 0,
|
endTimestamp: 0,
|
||||||
firstKeyFrameTimestamp: null,
|
firstKeyFrameTimestamp: null,
|
||||||
@@ -934,11 +967,21 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
defaultDuration: null,
|
defaultDuration: null,
|
||||||
name: null,
|
name: null,
|
||||||
languageCode: UNDETERMINED_LANGUAGE,
|
languageCode: UNDETERMINED_LANGUAGE,
|
||||||
|
decodingInstructions: [],
|
||||||
|
|
||||||
info: null,
|
info: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.readContiguousElements(slice.slice(dataStartPos, size));
|
this.readContiguousElements(slice.slice(dataStartPos, size));
|
||||||
|
|
||||||
|
if (this.currentTrack.decodingInstructions.some((instruction) => {
|
||||||
|
return instruction.data?.type !== 'decompress'
|
||||||
|
|| instruction.data.algorithm !== ContentCompAlgo.HeaderStripping;
|
||||||
|
})) {
|
||||||
|
console.warn(`Track #${this.currentTrack.id} has an unsupported content encoding; dropping.`);
|
||||||
|
this.currentTrack = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.currentTrack
|
this.currentTrack
|
||||||
&& this.currentTrack.id !== -1
|
&& this.currentTrack.id !== -1
|
||||||
@@ -1295,12 +1338,18 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
||||||
|
|
||||||
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
||||||
|
let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
||||||
|
|
||||||
|
if (trackData.track) {
|
||||||
|
blockData = this.decodeBlockData(trackData.track, blockData);
|
||||||
|
}
|
||||||
|
|
||||||
trackData.blocks.push({
|
trackData.blocks.push({
|
||||||
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
||||||
duration: 0, // Will set later
|
duration: 0, // Will set later
|
||||||
isKeyFrame,
|
isKeyFrame,
|
||||||
referencedTimestamps: [],
|
referencedTimestamps: [],
|
||||||
data: readBytes(slice, size - (slice.filePos - dataStartPos)),
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
});
|
});
|
||||||
}; break;
|
}; break;
|
||||||
@@ -1331,12 +1380,18 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
|
||||||
|
|
||||||
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
|
||||||
|
let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
|
||||||
|
|
||||||
|
if (trackData.track) {
|
||||||
|
blockData = this.decodeBlockData(trackData.track, blockData);
|
||||||
|
}
|
||||||
|
|
||||||
this.currentBlock = {
|
this.currentBlock = {
|
||||||
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
|
||||||
duration: 0, // Will set later
|
duration: 0, // Will set later
|
||||||
isKeyFrame: true,
|
isKeyFrame: true,
|
||||||
referencedTimestamps: [],
|
referencedTimestamps: [],
|
||||||
data: readBytes(slice, size - (slice.filePos - dataStartPos)),
|
data: blockData,
|
||||||
lacing,
|
lacing,
|
||||||
};
|
};
|
||||||
trackData.blocks.push(this.currentBlock);
|
trackData.blocks.push(this.currentBlock);
|
||||||
@@ -1469,12 +1524,117 @@ export class MatroskaDemuxer extends Demuxer {
|
|||||||
|
|
||||||
this.currentAttachedFile.fileDescription = readUnicodeString(slice, size);
|
this.currentAttachedFile.fileDescription = readUnicodeString(slice, size);
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentEncodings: {
|
||||||
|
if (!this.currentTrack) break;
|
||||||
|
|
||||||
|
this.readContiguousElements(slice.slice(dataStartPos, size));
|
||||||
|
|
||||||
|
// "**MUST** start with the `ContentEncoding` with the highest `ContentEncodingOrder`"
|
||||||
|
this.currentTrack.decodingInstructions.sort((a, b) => b.order - a.order);
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentEncoding: {
|
||||||
|
this.currentDecodingInstruction = {
|
||||||
|
order: 0,
|
||||||
|
scope: ContentEncodingScope.Block,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.readContiguousElements(slice.slice(dataStartPos, size));
|
||||||
|
|
||||||
|
if (this.currentDecodingInstruction.data) {
|
||||||
|
this.currentTrack!.decodingInstructions.push(this.currentDecodingInstruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentDecodingInstruction = null;
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentEncodingOrder: {
|
||||||
|
if (!this.currentDecodingInstruction) break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.order = readUnsignedInt(slice, size);
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentEncodingScope: {
|
||||||
|
if (!this.currentDecodingInstruction) break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.scope = readUnsignedInt(slice, size);
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentCompression: {
|
||||||
|
if (!this.currentDecodingInstruction) break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.data = {
|
||||||
|
type: 'decompress',
|
||||||
|
algorithm: ContentCompAlgo.Zlib,
|
||||||
|
settings: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.readContiguousElements(slice.slice(dataStartPos, size));
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentCompAlgo: {
|
||||||
|
if (this.currentDecodingInstruction?.data?.type !== 'decompress') break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.data.algorithm = readUnsignedInt(slice, size);
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentCompSettings: {
|
||||||
|
if (this.currentDecodingInstruction?.data?.type !== 'decompress') break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.data.settings = readBytes(slice, size);
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
case EBMLId.ContentEncryption: {
|
||||||
|
if (!this.currentDecodingInstruction) break;
|
||||||
|
|
||||||
|
this.currentDecodingInstruction.data = {
|
||||||
|
type: 'decrypt',
|
||||||
|
};
|
||||||
|
}; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
slice.filePos = dataStartPos + size;
|
slice.filePos = dataStartPos + size;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decodeBlockData(track: InternalTrack, rawData: Uint8Array) {
|
||||||
|
let currentData = rawData;
|
||||||
|
|
||||||
|
// In the vast number of cases there are exactly zero decoding instructions
|
||||||
|
for (let i = 0; i < track.decodingInstructions.length; i++) {
|
||||||
|
const instruction = track.decodingInstructions[i]!;
|
||||||
|
assert(instruction.data);
|
||||||
|
|
||||||
|
switch (instruction.data.type) {
|
||||||
|
case 'decompress': {
|
||||||
|
switch (instruction.data.algorithm) {
|
||||||
|
case ContentCompAlgo.HeaderStripping: {
|
||||||
|
const prefix = instruction.data.settings ?? new Uint8Array(0);
|
||||||
|
const newData = new Uint8Array(prefix.length + currentData.length);
|
||||||
|
|
||||||
|
newData.set(prefix, 0);
|
||||||
|
newData.set(currentData, prefix.length);
|
||||||
|
|
||||||
|
currentData = newData;
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// Unhandled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}; break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
// Unhandled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentData;
|
||||||
|
}
|
||||||
|
|
||||||
processTagValue(name: string, value: string | Uint8Array) {
|
processTagValue(name: string, value: string | Uint8Array) {
|
||||||
if (!this.currentSegment?.metadataTags) return;
|
if (!this.currentSegment?.metadataTags) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user