mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 13:23:51 +02:00
Add Matroska resync logic for malformed files, add RVE sponsor
This commit is contained in:
+23
-2
@@ -122,9 +122,8 @@ export const LEVEL_0_EBML_IDS: EBMLId[] = [
|
||||
EBMLId.Segment,
|
||||
];
|
||||
|
||||
// All the stuff that can appear in a segment, basically
|
||||
export const LEVEL_1_EBML_IDS: EBMLId[] = [
|
||||
EBMLId.EBMLMaxIDLength,
|
||||
EBMLId.EBMLMaxSizeLength,
|
||||
EBMLId.SeekHead,
|
||||
EBMLId.Info,
|
||||
EBMLId.Cluster,
|
||||
@@ -585,6 +584,28 @@ export class EBMLReader {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Searches for the next occurrence of an element ID using a naive byte-wise search. */
|
||||
async resync(ids: EBMLId[], until: number) {
|
||||
const loadChunkSize = 2 ** 20; // 1 MiB
|
||||
const idsSet = new Set(ids);
|
||||
|
||||
while (this.pos <= until - MIN_HEADER_SIZE) {
|
||||
if (!this.reader.rangeIsLoaded(this.pos, Math.min(this.pos + MAX_HEADER_SIZE, until))) {
|
||||
await this.reader.loadRange(this.pos, Math.min(this.pos + loadChunkSize, until));
|
||||
}
|
||||
|
||||
const elementStartPos = this.pos;
|
||||
const elementId = this.readElementId();
|
||||
if (elementId !== null && idsSet.has(elementId)) {
|
||||
return elementStartPos;
|
||||
}
|
||||
|
||||
this.pos = elementStartPos + 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>> = {
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
EBMLId,
|
||||
EBMLReader,
|
||||
LEVEL_0_AND_1_EBML_IDS,
|
||||
LEVEL_1_EBML_IDS,
|
||||
MAX_HEADER_SIZE,
|
||||
MIN_HEADER_SIZE,
|
||||
readVarInt,
|
||||
@@ -175,6 +176,7 @@ const METADATA_ELEMENTS = [
|
||||
{ id: EBMLId.Tracks, flag: 'tracksSeen' },
|
||||
{ id: EBMLId.Cues, flag: 'cuesSeen' },
|
||||
] as const;
|
||||
const MAX_RESYNC_LENGTH = 10 * 2 ** 20; // 10 MiB
|
||||
|
||||
export class MatroskaDemuxer extends Demuxer {
|
||||
metadataReader: EBMLReader;
|
||||
@@ -330,8 +332,22 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
|
||||
const elementStartPos = this.metadataReader.pos;
|
||||
const header = this.metadataReader.readElementHeader();
|
||||
if (!header) {
|
||||
break;
|
||||
|
||||
if (!header || !LEVEL_1_EBML_IDS.includes(header.id)) {
|
||||
// Potential junk. Let's try to resync
|
||||
|
||||
this.metadataReader.pos = elementStartPos;
|
||||
const nextPos = await this.metadataReader.resync(
|
||||
LEVEL_1_EBML_IDS,
|
||||
Math.min(this.currentSegment.elementEndPos, this.metadataReader.pos + MAX_RESYNC_LENGTH),
|
||||
);
|
||||
|
||||
if (nextPos) {
|
||||
this.metadataReader.pos = nextPos;
|
||||
continue;
|
||||
} else {
|
||||
break; // Resync failed
|
||||
}
|
||||
}
|
||||
|
||||
const { id, size } = header;
|
||||
@@ -352,6 +368,10 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
}
|
||||
}
|
||||
|
||||
if (size !== null) {
|
||||
this.metadataReader.pos = dataStartPos + size;
|
||||
}
|
||||
|
||||
if (this.currentSegment.infoSeen && this.currentSegment.tracksSeen && this.currentSegment.cuesSeen) {
|
||||
// No need to search anymore, we have everything
|
||||
break;
|
||||
@@ -381,10 +401,15 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
if (size === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.metadataReader.pos = dataStartPos + size;
|
||||
if (!clusterEncountered) {
|
||||
const seekEntry = this.currentSegment.seekEntries.find(entry => entry.id === EBMLId.Cluster);
|
||||
|
||||
if (!clusterEncountered) {
|
||||
if (seekEntry) {
|
||||
// The seek head points us to the first cluster, nice
|
||||
this.currentSegment.clusterSeekStartPos = segmentDataStart + seekEntry.segmentPosition;
|
||||
} else {
|
||||
this.currentSegment.clusterSeekStartPos = this.metadataReader.pos;
|
||||
}
|
||||
}
|
||||
@@ -1609,8 +1634,24 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
|
||||
await metadataReader.reader.loadRange(metadataReader.pos, metadataReader.pos + MAX_HEADER_SIZE);
|
||||
const elementStartPos = metadataReader.pos;
|
||||
const elementHeader = metadataReader.readElementHeader();
|
||||
if (!elementHeader) {
|
||||
break;
|
||||
|
||||
if (!elementHeader || !LEVEL_1_EBML_IDS.includes(elementHeader.id)) {
|
||||
// There's an element here that shouldn't be here (or Void). Might be garbage. In this case, let's
|
||||
// try and resync to the next valid element.
|
||||
|
||||
metadataReader.pos = elementStartPos;
|
||||
|
||||
const nextPos = await metadataReader.resync(
|
||||
LEVEL_1_EBML_IDS,
|
||||
Math.min(segment.elementEndPos, metadataReader.pos + MAX_RESYNC_LENGTH),
|
||||
);
|
||||
|
||||
if (nextPos) {
|
||||
metadataReader.pos = nextPos;
|
||||
continue;
|
||||
} else {
|
||||
break; // Resync failed
|
||||
}
|
||||
}
|
||||
|
||||
const id = elementHeader.id;
|
||||
|
||||
Reference in New Issue
Block a user