mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 13:53:53 +02:00
Add support for ID3 metadata in ADTS, make MPEG-TS demuxer more resilient
This commit is contained in:
@@ -2,3 +2,5 @@
|
|||||||
- Code style is tab indent with semicolons
|
- Code style is tab indent with semicolons
|
||||||
- Mediabunny core code is contained in src/, extensions are in packages/*/, website is in docs/
|
- Mediabunny core code is contained in src/, extensions are in packages/*/, website is in docs/
|
||||||
- Tests: Prefer fewer, longer test files over many small ones. Test files should be named after the general catergory of thing that is being tested, not after any individual single test.
|
- Tests: Prefer fewer, longer test files over many small ones. Test files should be named after the general catergory of thing that is being tested, not after any individual single test.
|
||||||
|
- Avoid ifs without a {} block. So no if (cond) return;, always do if (cond) { return; }
|
||||||
|
- `type` instead of `interface` for object types
|
||||||
@@ -8,9 +8,15 @@
|
|||||||
|
|
||||||
import { aacChannelMap, aacFrequencyTable, AudioCodec } from '../codec';
|
import { aacChannelMap, aacFrequencyTable, AudioCodec } from '../codec';
|
||||||
import { Demuxer } from '../demuxer';
|
import { Demuxer } from '../demuxer';
|
||||||
|
import {
|
||||||
|
ID3_V2_HEADER_SIZE,
|
||||||
|
parseId3V2Tag,
|
||||||
|
readId3V2Header,
|
||||||
|
} from '../id3';
|
||||||
import { Input } from '../input';
|
import { Input } from '../input';
|
||||||
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||||
import { PacketRetrievalOptions } from '../media-sink';
|
import { PacketRetrievalOptions } from '../media-sink';
|
||||||
|
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
|
||||||
import {
|
import {
|
||||||
assert,
|
assert,
|
||||||
AsyncMutex,
|
AsyncMutex,
|
||||||
@@ -20,7 +26,6 @@ import {
|
|||||||
} from '../misc';
|
} from '../misc';
|
||||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||||
import { readBytes, Reader } from '../reader';
|
import { readBytes, Reader } from '../reader';
|
||||||
import { DEFAULT_TRACK_DISPOSITION } from '../metadata';
|
|
||||||
import {
|
import {
|
||||||
AdtsFrameHeader,
|
AdtsFrameHeader,
|
||||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||||
@@ -43,6 +48,7 @@ export class AdtsDemuxer extends Demuxer {
|
|||||||
metadataPromise: Promise<void> | null = null;
|
metadataPromise: Promise<void> | null = null;
|
||||||
firstFrameHeader: AdtsFrameHeader | null = null;
|
firstFrameHeader: AdtsFrameHeader | null = null;
|
||||||
loadedSamples: Sample[] = [];
|
loadedSamples: Sample[] = [];
|
||||||
|
metadataTags: MetadataTags | null = null;
|
||||||
|
|
||||||
tracks: InputAudioTrack[] = [];
|
tracks: InputAudioTrack[] = [];
|
||||||
|
|
||||||
@@ -73,6 +79,26 @@ export class AdtsDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async advanceReader() {
|
async advanceReader() {
|
||||||
|
if (this.lastLoadedPos === 0) {
|
||||||
|
// Skip all ID3v2 tags at the start of the file
|
||||||
|
while (true) {
|
||||||
|
let slice = this.reader.requestSlice(this.lastLoadedPos, ID3_V2_HEADER_SIZE);
|
||||||
|
if (slice instanceof Promise) slice = await slice;
|
||||||
|
|
||||||
|
if (!slice) {
|
||||||
|
this.lastSampleLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id3V2Header = readId3V2Header(slice);
|
||||||
|
if (!id3V2Header) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastLoadedPos = slice.filePos + id3V2Header.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let slice = this.reader.requestSliceRange(
|
let slice = this.reader.requestSliceRange(
|
||||||
this.lastLoadedPos,
|
this.lastLoadedPos,
|
||||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||||
@@ -135,7 +161,41 @@ export class AdtsDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getMetadataTags() {
|
async getMetadataTags() {
|
||||||
return {}; // No tags in this one
|
const release = await this.readingMutex.acquire();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.readMetadata();
|
||||||
|
|
||||||
|
if (this.metadataTags) {
|
||||||
|
return this.metadataTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.metadataTags = {};
|
||||||
|
let currentPos = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
let headerSlice = this.reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||||
|
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
||||||
|
if (!headerSlice) break;
|
||||||
|
|
||||||
|
const id3V2Header = readId3V2Header(headerSlice);
|
||||||
|
if (!id3V2Header) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentSlice = this.reader.requestSlice(headerSlice.filePos, id3V2Header.size);
|
||||||
|
if (contentSlice instanceof Promise) contentSlice = await contentSlice;
|
||||||
|
if (!contentSlice) break;
|
||||||
|
|
||||||
|
parseId3V2Tag(contentSlice, id3V2Header, this.metadataTags);
|
||||||
|
|
||||||
|
currentPos = headerSlice.filePos + id3V2Header.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.metadataTags;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata } from '../codec';
|
import { parseAacAudioSpecificConfig, validateAudioChunkMetadata } from '../codec';
|
||||||
|
import { Id3V2Writer } from '../id3';
|
||||||
|
import { metadataTagsAreEmpty } from '../metadata';
|
||||||
import { assert, Bitstream, toUint8Array } from '../misc';
|
import { assert, Bitstream, toUint8Array } from '../misc';
|
||||||
import { Muxer } from '../muxer';
|
import { Muxer } from '../muxer';
|
||||||
import { Output, OutputAudioTrack } from '../output';
|
import { Output, OutputAudioTrack } from '../output';
|
||||||
@@ -30,7 +32,10 @@ export class AdtsMuxer extends Muxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// Nothing needed here
|
if (!metadataTagsAreEmpty(this.output._metadataTags)) {
|
||||||
|
const id3Writer = new Id3V2Writer(this.writer);
|
||||||
|
id3Writer.writeId3V2Tag(this.output._metadataTags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMimeType() {
|
async getMimeType() {
|
||||||
|
|||||||
+19
-9
@@ -266,7 +266,6 @@ export class Mp3InputFormat extends InputFormat {
|
|||||||
if (!slice) return false;
|
if (!slice) return false;
|
||||||
|
|
||||||
let currentPos = 0;
|
let currentPos = 0;
|
||||||
let id3V2HeaderFound = false;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||||
@@ -278,7 +277,6 @@ export class Mp3InputFormat extends InputFormat {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
id3V2HeaderFound = true;
|
|
||||||
currentPos = slice.filePos + id3V2Header.size;
|
currentPos = slice.filePos + id3V2Header.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,11 +285,6 @@ export class Mp3InputFormat extends InputFormat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id3V2HeaderFound) {
|
|
||||||
// If there was an ID3v2 tag at the start, we can be pretty sure this is MP3 by now
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPos = firstResult.startPos + firstResult.header.totalSize;
|
currentPos = firstResult.startPos + firstResult.header.totalSize;
|
||||||
|
|
||||||
// Fine, we found one frame header, but we're still not entirely sure this is MP3. Let's check if we can find
|
// Fine, we found one frame header, but we're still not entirely sure this is MP3. Let's check if we can find
|
||||||
@@ -441,8 +434,23 @@ export class FlacInputFormat extends InputFormat {
|
|||||||
export class AdtsInputFormat extends InputFormat {
|
export class AdtsInputFormat extends InputFormat {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _canReadInput(input: Input) {
|
async _canReadInput(input: Input) {
|
||||||
|
let currentPos = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
let slice = input._reader.requestSlice(currentPos, ID3_V2_HEADER_SIZE);
|
||||||
|
if (slice instanceof Promise) slice = await slice;
|
||||||
|
if (!slice) break;
|
||||||
|
|
||||||
|
const id3V2Header = readId3V2Header(slice);
|
||||||
|
if (!id3V2Header) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPos = slice.filePos + id3V2Header.size;
|
||||||
|
}
|
||||||
|
|
||||||
let slice = input._reader.requestSliceRange(
|
let slice = input._reader.requestSliceRange(
|
||||||
0,
|
currentPos,
|
||||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||||
MAX_ADTS_FRAME_HEADER_SIZE,
|
MAX_ADTS_FRAME_HEADER_SIZE,
|
||||||
);
|
);
|
||||||
@@ -454,8 +462,10 @@ export class AdtsInputFormat extends InputFormat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPos += firstHeader.frameLength;
|
||||||
|
|
||||||
slice = input._reader.requestSliceRange(
|
slice = input._reader.requestSliceRange(
|
||||||
firstHeader.frameLength,
|
currentPos,
|
||||||
MIN_ADTS_FRAME_HEADER_SIZE,
|
MIN_ADTS_FRAME_HEADER_SIZE,
|
||||||
MAX_ADTS_FRAME_HEADER_SIZE,
|
MAX_ADTS_FRAME_HEADER_SIZE,
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,7 @@
|
|||||||
* - For Ogg files, there is no global metadata so instead, the metadata refers to the combined metadata of all tracks,
|
* - For Ogg files, there is no global metadata so instead, the metadata refers to the combined metadata of all tracks,
|
||||||
* in Vorbis-style comment headers.
|
* in Vorbis-style comment headers.
|
||||||
* - For WAVE files, the metadata refers to the chunks within the RIFF INFO chunk.
|
* - For WAVE files, the metadata refers to the chunks within the RIFF INFO chunk.
|
||||||
* - For ADTS files, there is no metadata.
|
* - For ADTS files, the metadata refers to the ID3v2 tags.
|
||||||
* - For FLAC files, the metadata lives in Vorbis style in the Vorbis comment block.
|
* - For FLAC files, the metadata lives in Vorbis style in the Vorbis comment block.
|
||||||
* - For MPEG-TS files, metadata tags are currently not supported.
|
* - For MPEG-TS files, metadata tags are currently not supported.
|
||||||
*
|
*
|
||||||
@@ -70,6 +70,7 @@ export type MetadataTags = {
|
|||||||
* values. Additionally, all attached files (such as font files) are included here, where the key corresponds to
|
* values. Additionally, all attached files (such as font files) are included here, where the key corresponds to
|
||||||
* the FileUID and the value is an {@link AttachedFile}.
|
* the FileUID and the value is an {@link AttachedFile}.
|
||||||
* - MP3: The ID3v2 tags, or a single `'TAG'` key with the contents of the ID3v1 tag.
|
* - MP3: The ID3v2 tags, or a single `'TAG'` key with the contents of the ID3v1 tag.
|
||||||
|
* - ADTS: The ID3v2 tags.
|
||||||
* - Ogg: The key-value string pairs from the Vorbis-style comment header (see RFC 7845, Section 5.2).
|
* - Ogg: The key-value string pairs from the Vorbis-style comment header (see RFC 7845, Section 5.2).
|
||||||
* Additionally, the `'vendor'` key refers to the vendor string within this header.
|
* Additionally, the `'vendor'` key refers to the vendor string within this header.
|
||||||
* - WAVE: The individual metadata chunks within the RIFF INFO chunk. Values are always ISO 8859-1 strings.
|
* - WAVE: The individual metadata chunks within the RIFF INFO chunk. Values are always ISO 8859-1 strings.
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ import { EncodedPacket, PacketType, PLACEHOLDER_DATA } from '../packet';
|
|||||||
import { FileSlice, readBytes, Reader, readU16Be, readU32Be, readU8 } from '../reader';
|
import { FileSlice, readBytes, Reader, readU16Be, readU32Be, readU8 } from '../reader';
|
||||||
import { buildMpegTsMimeType, MpegTsStreamType, TIMESCALE, TS_PACKET_SIZE } from './mpeg-ts-misc';
|
import { buildMpegTsMimeType, MpegTsStreamType, TIMESCALE, TS_PACKET_SIZE } from './mpeg-ts-misc';
|
||||||
|
|
||||||
const MISSING_PES_PACKET_ERROR = 'No PES packet found where one was expected.';
|
|
||||||
|
|
||||||
type ElementaryStream = {
|
type ElementaryStream = {
|
||||||
demuxer: MpegTsDemuxer;
|
demuxer: MpegTsDemuxer;
|
||||||
pid: number;
|
pid: number;
|
||||||
@@ -191,6 +189,26 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
const BYTES_BEFORE_SECTION_LENGTH = 3;
|
const BYTES_BEFORE_SECTION_LENGTH = 3;
|
||||||
const BITS_IN_CRC_32 = 32; // Duh
|
const BITS_IN_CRC_32 = 32; // Duh
|
||||||
|
|
||||||
|
// Some streams don't contain a PAT for some reason, so we must do some guesswork to figure out where
|
||||||
|
// the PMT is.
|
||||||
|
let isProbablyProgramMap = false;
|
||||||
|
if (!hasProgramMap && section.pid !== 0) {
|
||||||
|
const isPesPacket
|
||||||
|
= section.payload[0] === 0x00 && section.payload[1] === 0x00 && section.payload[2] === 0x01;
|
||||||
|
|
||||||
|
if (!isPesPacket) {
|
||||||
|
// Assume it's a PSI
|
||||||
|
|
||||||
|
const bitstream = new Bitstream(section.payload);
|
||||||
|
const pointerField = bitstream.readAlignedByte();
|
||||||
|
|
||||||
|
bitstream.skipBits(8 * pointerField);
|
||||||
|
|
||||||
|
const tableId = bitstream.readBits(8);
|
||||||
|
isProbablyProgramMap = tableId === 0x02; // 0x02 == TS_program_map_section
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (section.pid === 0 && !hasProgramAssociationTable) {
|
if (section.pid === 0 && !hasProgramAssociationTable) {
|
||||||
const bitstream = new Bitstream(section.payload);
|
const bitstream = new Bitstream(section.payload);
|
||||||
const pointerField = bitstream.readAlignedByte();
|
const pointerField = bitstream.readAlignedByte();
|
||||||
@@ -220,7 +238,7 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasProgramAssociationTable = true;
|
hasProgramAssociationTable = true;
|
||||||
} else if (section.pid === programMapPid && !hasProgramMap) {
|
} else if ((section.pid === programMapPid || isProbablyProgramMap) && !hasProgramMap) {
|
||||||
const bitstream = new Bitstream(section.payload);
|
const bitstream = new Bitstream(section.payload);
|
||||||
const pointerField = bitstream.readAlignedByte();
|
const pointerField = bitstream.readAlignedByte();
|
||||||
|
|
||||||
@@ -291,6 +309,7 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
default: {
|
default: {
|
||||||
// If we don't recognize the codec, we don't surface the track at all. This is because
|
// If we don't recognize the codec, we don't surface the track at all. This is because
|
||||||
// we can't determine its metadata and also have no idea how to packetize its data.
|
// we can't determine its metadata and also have no idea how to packetize its data.
|
||||||
|
console.warn(`Unsupported stream_type 0x${streamType.toString(16)}; ignoring stream.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,10 +452,11 @@ export class MpegTsDemuxer extends Demuxer {
|
|||||||
currentPos += this.packetStride;
|
currentPos += this.packetStride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasProgramMap) {
|
||||||
if (!hasProgramAssociationTable) {
|
if (!hasProgramAssociationTable) {
|
||||||
throw new Error('No Program Association Table found in the file.');
|
throw new Error('No Program Association Table found in the file.');
|
||||||
}
|
}
|
||||||
if (!hasProgramMap) {
|
|
||||||
throw new Error('No Program Map Table found in the file.');
|
throw new Error('No Program Map Table found in the file.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,6 +694,10 @@ type PesPacket = PesPacketHeader & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const readPesPacketHeader = (section: Section): PesPacketHeader | null => {
|
const readPesPacketHeader = (section: Section): PesPacketHeader | null => {
|
||||||
|
if (section.payload.byteLength < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const bitstream = new Bitstream(section.payload);
|
const bitstream = new Bitstream(section.payload);
|
||||||
|
|
||||||
const startCodePrefix = bitstream.readBits(24);
|
const startCodePrefix = bitstream.readBits(24);
|
||||||
@@ -994,8 +1018,10 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pesPacketHeader = readPesPacketHeader(section);
|
const pesPacketHeader = readPesPacketHeader(section);
|
||||||
|
if (pesPacketHeader) {
|
||||||
return pesPacketHeader;
|
return pesPacketHeader;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
@@ -1152,9 +1178,7 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
const section = await demuxer.readSection(currentPos, false);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
if (section) {
|
if (section) {
|
||||||
const nextPesHeader = readPesPacketHeader(section);
|
const nextPesHeader = readPesPacketHeader(section);
|
||||||
if (!nextPesHeader) {
|
if (nextPesHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
|
||||||
}
|
|
||||||
if (nextPesHeader.pts > searchPts) {
|
if (nextPesHeader.pts > searchPts) {
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
@@ -1165,6 +1189,7 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
@@ -1185,14 +1210,12 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
const section = await demuxer.readSection(pos, false);
|
const section = await demuxer.readSection(pos, false);
|
||||||
if (section) {
|
if (section) {
|
||||||
const header = readPesPacketHeader(section);
|
const header = readPesPacketHeader(section);
|
||||||
if (!header) {
|
if (header) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPesHeader = header;
|
currentPesHeader = header;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pos -= demuxer.packetStride;
|
pos -= demuxer.packetStride;
|
||||||
}
|
}
|
||||||
@@ -1265,15 +1288,12 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
const section = await demuxer.readSection(currentPos, false);
|
const section = await demuxer.readSection(currentPos, false);
|
||||||
if (section) {
|
if (section) {
|
||||||
pesHeader = readPesPacketHeader(section);
|
pesHeader = readPesPacketHeader(section);
|
||||||
if (!pesHeader) {
|
if (pesHeader) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeInsertReferencePacket(this.elementaryStream, pesHeader);
|
maybeInsertReferencePacket(this.elementaryStream, pesHeader);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentPos += demuxer.packetStride;
|
currentPos += demuxer.packetStride;
|
||||||
}
|
}
|
||||||
@@ -1298,14 +1318,12 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
|
|||||||
const section = await demuxer.readSection(pos, false);
|
const section = await demuxer.readSection(pos, false);
|
||||||
if (section) {
|
if (section) {
|
||||||
const header = readPesPacketHeader(section);
|
const header = readPesPacketHeader(section);
|
||||||
if (!header) {
|
if (header) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
startPesHeader = header;
|
startPesHeader = header;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pos -= demuxer.packetStride;
|
pos -= demuxer.packetStride;
|
||||||
}
|
}
|
||||||
@@ -1759,23 +1777,20 @@ class PacketReadingContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (packetHeader.pid === this.pid) {
|
if (packetHeader.pid === this.pid) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPos += this.demuxer.packetStride;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextSection = await this.demuxer.readSection(currentPos, true);
|
const nextSection = await this.demuxer.readSection(currentPos, true);
|
||||||
if (!nextSection) {
|
if (!nextSection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPesPacket = readPesPacket(nextSection);
|
const nextPesPacket = readPesPacket(nextSection);
|
||||||
if (!nextPesPacket) {
|
if (nextPesPacket) {
|
||||||
throw new Error(MISSING_PES_PACKET_ERROR);
|
pesPacket = nextPesPacket;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pesPacket = nextPesPacket;
|
currentPos += this.demuxer.packetStride;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pesPackets.push(pesPacket);
|
this.pesPackets.push(pesPacket);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { expect, test } from 'vitest';
|
import { expect, test } from 'vitest';
|
||||||
import { Output } from '../../src/output.js';
|
import { Output } from '../../src/output.js';
|
||||||
import {
|
import {
|
||||||
|
AdtsOutputFormat,
|
||||||
FlacOutputFormat,
|
FlacOutputFormat,
|
||||||
MkvOutputFormat,
|
MkvOutputFormat,
|
||||||
MovOutputFormat,
|
MovOutputFormat,
|
||||||
@@ -478,6 +479,61 @@ test('Read and write metadata, FLAC', async () => {
|
|||||||
expect(readTags.raw!['COMPOSER']).toBe('Hans Zimmer');
|
expect(readTags.raw!['COMPOSER']).toBe('Hans Zimmer');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Read and write metadata, ADTS', async () => {
|
||||||
|
const originalInput = new Input({
|
||||||
|
source: new FilePathSource(path.join(__dirname, '../public/sample3.aac')),
|
||||||
|
formats: ALL_FORMATS,
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = new Output({
|
||||||
|
format: new AdtsOutputFormat(),
|
||||||
|
target: new BufferTarget(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const conversion = await Conversion.init({
|
||||||
|
input: originalInput,
|
||||||
|
output,
|
||||||
|
tags: {
|
||||||
|
...songMetadata,
|
||||||
|
raw: {
|
||||||
|
TXXY: 'ID3v2 goated',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await conversion.execute();
|
||||||
|
|
||||||
|
using input = new Input({
|
||||||
|
source: new BufferSource(output.target.buffer!),
|
||||||
|
formats: ALL_FORMATS,
|
||||||
|
});
|
||||||
|
|
||||||
|
const readTags = await input.getMetadataTags();
|
||||||
|
|
||||||
|
// ID3v2 is goated, so pretty much everything was copied:
|
||||||
|
expect(readTags.title).toBe(songMetadata.title);
|
||||||
|
expect(readTags.description).toBe(songMetadata.description);
|
||||||
|
expect(readTags.artist).toBe(songMetadata.artist);
|
||||||
|
expect(readTags.album).toBe(songMetadata.album);
|
||||||
|
expect(readTags.albumArtist).toBe(songMetadata.albumArtist);
|
||||||
|
expect(readTags.comment).toBe(songMetadata.comment);
|
||||||
|
expect(readTags.lyrics).toBe(songMetadata.lyrics);
|
||||||
|
expect(readTags.trackNumber).toBe(songMetadata.trackNumber);
|
||||||
|
expect(readTags.tracksTotal).toBe(songMetadata.tracksTotal);
|
||||||
|
expect(readTags.discNumber).toBe(songMetadata.discNumber);
|
||||||
|
expect(readTags.discsTotal).toBe(songMetadata.discsTotal);
|
||||||
|
expect(readTags.date).toEqual(readTags.date);
|
||||||
|
expect(readTags.images).toHaveLength(1);
|
||||||
|
expect(readTags.images![0]!.data).toEqual(coverArt);
|
||||||
|
expect(readTags.images![0]!.mimeType).toEqual('image/jpeg');
|
||||||
|
expect(readTags.images![0]!.kind).toEqual('coverFront');
|
||||||
|
expect(readTags.images![0]!.description).toEqual(songMetadata.images![0]!.description);
|
||||||
|
expect(readTags.images![0]!.name).toBeUndefined(); // Can't be contained in ID3v2
|
||||||
|
|
||||||
|
expect(readTags.raw!['TIT2']).toBe(songMetadata.title);
|
||||||
|
expect(readTags.raw!['APIC']).instanceOf(Uint8Array);
|
||||||
|
expect(readTags.raw!['TXXY']).toBe('ID3v2 goated');
|
||||||
|
});
|
||||||
|
|
||||||
test('Read and write metadata, WAVE', async () => {
|
test('Read and write metadata, WAVE', async () => {
|
||||||
const output = new Output({
|
const output = new Output({
|
||||||
format: new WavOutputFormat(),
|
format: new WavOutputFormat(),
|
||||||
|
|||||||
Reference in New Issue
Block a user