1
0
mirror of synced 2026-09-01 00:04:45 +00:00
Files

144 lines
4.9 KiB
TypeScript
Raw Permalink Normal View History

import initDebug from 'debug';
2025-07-02 20:29:33 +02:00
import { Uint8ArrayType } from 'token-types';
2024-08-08 10:19:14 +02:00
import { type IVorbisPicture, VorbisPictureToken } from '../ogg/vorbis/Vorbis.js';
2022-01-27 15:40:10 +01:00
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
import { FourCcToken } from '../common/FourCC.js';
2025-06-30 20:49:10 +02:00
import { VorbisStream } from '../ogg/vorbis/VorbisStream.js';
2022-01-27 15:40:10 +01:00
import { VorbisDecoder } from '../ogg/vorbis/VorbisDecoder.js';
import { makeUnexpectedFileContentError } from '../ParseError.js';
2025-07-02 20:29:33 +02:00
import * as Flac from './FlacToken.js';
import type { IBlockStreamInfo } from './FlacToken.js';
2022-01-28 14:15:02 +01:00
const debug = initDebug('music-metadata:parser:FLAC');
class FlacContentError extends makeUnexpectedFileContentError('FLAC'){
}
export class FlacParser extends AbstractID3Parser {
2025-06-30 20:49:10 +02:00
private vorbisParser = new VorbisStream(this.metadata, this.options);
2017-06-28 08:55:41 +02:00
2022-01-28 14:15:02 +01:00
public async postId3v2Parse(): Promise<void> {
2018-11-24 15:23:57 +01:00
const fourCC = await this.tokenizer.readToken<string>(FourCcToken);
if (fourCC.toString() !== 'fLaC') {
throw new FlacContentError('Invalid FLAC preamble');
2018-11-24 15:23:57 +01:00
}
2025-07-02 20:29:33 +02:00
let blockHeader: Flac.IBlockHeader;
2018-11-24 15:23:57 +01:00
do {
// Read block header
2025-07-02 20:29:33 +02:00
blockHeader = await this.tokenizer.readToken(Flac.BlockHeader);
// Parse block data
2018-11-24 15:23:57 +01:00
await this.parseDataBlock(blockHeader);
}
while (!blockHeader.lastBlock);
if (this.tokenizer.fileInfo.size && this.metadata.format.duration) {
const dataSize = this.tokenizer.fileInfo.size - this.tokenizer.position;
2018-11-24 15:23:57 +01:00
this.metadata.setFormat('bitrate', 8 * dataSize / this.metadata.format.duration);
}
}
2025-07-02 20:29:33 +02:00
private async parseDataBlock(blockHeader: Flac.IBlockHeader): Promise<void> {
debug(`blockHeader type=${blockHeader.type}, length=${blockHeader.length}`);
switch (blockHeader.type) {
2025-07-02 20:29:33 +02:00
case Flac.BlockType.STREAMINFO:
return this.readBlockStreamInfo(blockHeader.length);
case Flac.BlockType.PADDING:
break;
2025-07-02 20:29:33 +02:00
case Flac.BlockType.APPLICATION:
break;
2025-07-02 20:29:33 +02:00
case Flac.BlockType.SEEKTABLE:
break;
2025-07-02 20:29:33 +02:00
case Flac.BlockType.VORBIS_COMMENT:
return this.readComment(blockHeader.length);
case Flac.BlockType.CUESHEET:
break;
2025-07-02 20:29:33 +02:00
case Flac.BlockType.PICTURE:
await this.parsePicture(blockHeader.length);
return;
default:
2024-08-08 10:19:14 +02:00
this.metadata.addWarning(`Unknown block type: ${blockHeader.type}`);
}
// Ignore data block
2020-01-25 14:47:50 +01:00
return this.tokenizer.ignore(blockHeader.length).then();
}
/**
* Parse STREAMINFO
*/
2025-07-02 20:29:33 +02:00
private async readBlockStreamInfo(dataLen: number): Promise<void> {
2025-07-02 20:29:33 +02:00
if (dataLen !== Flac.BlockStreamInfo.len)
throw new FlacContentError('Unexpected block-stream-info length');
2018-11-24 15:23:57 +01:00
2025-07-02 20:29:33 +02:00
const streamInfo = await this.tokenizer.readToken(Flac.BlockStreamInfo);
2019-06-02 15:31:16 +02:00
this.metadata.setFormat('container', 'FLAC');
2025-07-02 20:29:33 +02:00
this.processsStreamInfo(streamInfo);
}
/**
* Parse STREAMINFO
*/
public processsStreamInfo(streamInfo: IBlockStreamInfo): void {
2019-06-02 15:31:16 +02:00
this.metadata.setFormat('codec', 'FLAC');
2025-07-02 20:29:33 +02:00
this.metadata.setFormat('hasAudio', true);
2018-11-24 15:23:57 +01:00
this.metadata.setFormat('lossless', true);
this.metadata.setFormat('numberOfChannels', streamInfo.channels);
this.metadata.setFormat('bitsPerSample', streamInfo.bitsPerSample);
this.metadata.setFormat('sampleRate', streamInfo.sampleRate);
if (streamInfo.totalSamples > 0) {
this.metadata.setFormat('duration', streamInfo.totalSamples / streamInfo.sampleRate);
}
}
2025-07-02 20:29:33 +02:00
/**
* Read VORBIS_COMMENT from tokenizer
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
*/
private async readComment(dataLen: number): Promise<void> {
const data = await this.tokenizer.readToken(new Uint8ArrayType(dataLen));
return this.parseComment(data);
}
/**
* Parse VORBIS_COMMENT
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
*/
2025-07-02 20:29:33 +02:00
public async parseComment(data: Uint8Array): Promise<void> {
const decoder = new VorbisDecoder(data, 0);
const vendor = decoder.readStringUtf8();
if (vendor.length > 0) {
this.metadata.setFormat('tool', vendor);
}
2018-11-24 15:23:57 +01:00
const commentListLength = decoder.readInt32();
const tags = new Array(commentListLength);
2018-11-24 15:23:57 +01:00
for (let i = 0; i < commentListLength; i++) {
tags[i] = decoder.parseUserComment();
2018-11-24 15:23:57 +01:00
}
await Promise.all(tags.map(tag => {
if (tag.key==='ENCODER') {
this.metadata.setFormat('tool', tag.value);
}
return this.addTag(tag.key, tag.value);
}));
}
2018-11-24 15:23:57 +01:00
private async parsePicture(dataLen: number) {
2017-07-02 15:16:29 +02:00
if (this.options.skipCovers) {
return this.tokenizer.ignore(dataLen);
2024-08-08 10:19:14 +02:00
}
2025-07-02 20:29:33 +02:00
return this.addPictureTag(await this.tokenizer.readToken(new VorbisPictureToken(dataLen)));
}
2025-07-02 20:29:33 +02:00
public addPictureTag(picture: IVorbisPicture): Promise<void> {
return this.addTag('METADATA_BLOCK_PICTURE', picture);
2024-08-08 10:19:14 +02:00
}
2025-07-02 20:29:33 +02:00
private addTag(id: string, value: string | IVorbisPicture): Promise<void> {
return this.vorbisParser.addTag(id, value);
2024-08-08 10:19:14 +02:00
}
2025-07-02 20:29:33 +02:00
}