Initial working version
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser';
|
||||
import { INativeMetadataCollector } from '../common/MetadataCollector';
|
||||
import { ITokenizer } from 'strtok3/lib/core';
|
||||
import { IOptions } from '../type';
|
||||
import { ITokenParser } from '../ParserFactory';
|
||||
export declare class FlacParser extends AbstractID3Parser {
|
||||
private vorbisParser;
|
||||
private padding;
|
||||
/**
|
||||
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
||||
* @param {INativeMetadataCollector} metadata Output
|
||||
* @param {ITokenizer} tokenizer Input
|
||||
* @param {IOptions} options Parsing options
|
||||
*/
|
||||
init(metadata: INativeMetadataCollector, tokenizer: ITokenizer, options: IOptions): ITokenParser;
|
||||
_parse(): Promise<void>;
|
||||
private parseDataBlock;
|
||||
/**
|
||||
* Parse STREAMINFO
|
||||
*/
|
||||
private parseBlockStreamInfo;
|
||||
/**
|
||||
* Parse VORBIS_COMMENT
|
||||
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
|
||||
*/
|
||||
private parseComment;
|
||||
private parsePicture;
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FlacParser = void 0;
|
||||
const util = require("../common/Util");
|
||||
const Token = require("token-types");
|
||||
const Vorbis_1 = require("../ogg/vorbis/Vorbis");
|
||||
const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
|
||||
const FourCC_1 = require("../common/FourCC");
|
||||
const _debug = require("debug");
|
||||
const VorbisParser_1 = require("../ogg/vorbis/VorbisParser");
|
||||
const VorbisDecoder_1 = require("../ogg/vorbis/VorbisDecoder");
|
||||
const debug = _debug('music-metadata:parser:FLAC');
|
||||
/**
|
||||
* FLAC supports up to 128 kinds of metadata blocks; currently the following are defined:
|
||||
* ref: https://xiph.org/flac/format.html#metadata_block
|
||||
*/
|
||||
var BlockType;
|
||||
(function (BlockType) {
|
||||
BlockType[BlockType["STREAMINFO"] = 0] = "STREAMINFO";
|
||||
BlockType[BlockType["PADDING"] = 1] = "PADDING";
|
||||
BlockType[BlockType["APPLICATION"] = 2] = "APPLICATION";
|
||||
BlockType[BlockType["SEEKTABLE"] = 3] = "SEEKTABLE";
|
||||
BlockType[BlockType["VORBIS_COMMENT"] = 4] = "VORBIS_COMMENT";
|
||||
BlockType[BlockType["CUESHEET"] = 5] = "CUESHEET";
|
||||
BlockType[BlockType["PICTURE"] = 6] = "PICTURE";
|
||||
})(BlockType || (BlockType = {}));
|
||||
class FlacParser extends AbstractID3Parser_1.AbstractID3Parser {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.padding = 0;
|
||||
}
|
||||
/**
|
||||
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
||||
* @param {INativeMetadataCollector} metadata Output
|
||||
* @param {ITokenizer} tokenizer Input
|
||||
* @param {IOptions} options Parsing options
|
||||
*/
|
||||
init(metadata, tokenizer, options) {
|
||||
super.init(metadata, tokenizer, options);
|
||||
this.vorbisParser = new VorbisParser_1.VorbisParser(metadata, options);
|
||||
return this;
|
||||
}
|
||||
async _parse() {
|
||||
const fourCC = await this.tokenizer.readToken(FourCC_1.FourCcToken);
|
||||
if (fourCC.toString() !== 'fLaC') {
|
||||
throw new Error('Invalid FLAC preamble');
|
||||
}
|
||||
let blockHeader;
|
||||
do {
|
||||
// Read block header
|
||||
blockHeader = await this.tokenizer.readToken(Metadata.BlockHeader);
|
||||
// Parse block data
|
||||
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;
|
||||
this.metadata.setFormat('bitrate', 8 * dataSize / this.metadata.format.duration);
|
||||
}
|
||||
}
|
||||
parseDataBlock(blockHeader) {
|
||||
debug(`blockHeader type=${blockHeader.type}, length=${blockHeader.length}`);
|
||||
switch (blockHeader.type) {
|
||||
case BlockType.STREAMINFO:
|
||||
return this.parseBlockStreamInfo(blockHeader.length);
|
||||
case BlockType.PADDING:
|
||||
this.padding += blockHeader.length;
|
||||
break;
|
||||
case BlockType.APPLICATION:
|
||||
break;
|
||||
case BlockType.SEEKTABLE:
|
||||
break;
|
||||
case BlockType.VORBIS_COMMENT:
|
||||
return this.parseComment(blockHeader.length);
|
||||
case BlockType.CUESHEET:
|
||||
break;
|
||||
case BlockType.PICTURE:
|
||||
return this.parsePicture(blockHeader.length).then();
|
||||
default:
|
||||
this.metadata.addWarning('Unknown block type: ' + blockHeader.type);
|
||||
}
|
||||
// Ignore data block
|
||||
return this.tokenizer.ignore(blockHeader.length).then();
|
||||
}
|
||||
/**
|
||||
* Parse STREAMINFO
|
||||
*/
|
||||
async parseBlockStreamInfo(dataLen) {
|
||||
if (dataLen !== Metadata.BlockStreamInfo.len)
|
||||
throw new Error('Unexpected block-stream-info length');
|
||||
const streamInfo = await this.tokenizer.readToken(Metadata.BlockStreamInfo);
|
||||
this.metadata.setFormat('container', 'FLAC');
|
||||
this.metadata.setFormat('codec', 'FLAC');
|
||||
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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse VORBIS_COMMENT
|
||||
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
|
||||
*/
|
||||
async parseComment(dataLen) {
|
||||
const data = await this.tokenizer.readToken(new Token.Uint8ArrayType(dataLen));
|
||||
const decoder = new VorbisDecoder_1.VorbisDecoder(data, 0);
|
||||
decoder.readStringUtf8(); // vendor (skip)
|
||||
const commentListLength = decoder.readInt32();
|
||||
for (let i = 0; i < commentListLength; i++) {
|
||||
const tag = decoder.parseUserComment();
|
||||
this.vorbisParser.addTag(tag.key, tag.value);
|
||||
}
|
||||
}
|
||||
async parsePicture(dataLen) {
|
||||
if (this.options.skipCovers) {
|
||||
return this.tokenizer.ignore(dataLen);
|
||||
}
|
||||
else {
|
||||
const picture = await this.tokenizer.readToken(new Vorbis_1.VorbisPictureToken(dataLen));
|
||||
this.vorbisParser.addTag('METADATA_BLOCK_PICTURE', picture);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.FlacParser = FlacParser;
|
||||
class Metadata {
|
||||
}
|
||||
Metadata.BlockHeader = {
|
||||
len: 4,
|
||||
get: (buf, off) => {
|
||||
return {
|
||||
lastBlock: util.getBit(buf, off, 7),
|
||||
type: util.getBitAllignedNumber(buf, off, 1, 7),
|
||||
length: Token.UINT24_BE.get(buf, off + 1)
|
||||
};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* METADATA_BLOCK_DATA
|
||||
* Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
|
||||
*/
|
||||
Metadata.BlockStreamInfo = {
|
||||
len: 34,
|
||||
get: (buf, off) => {
|
||||
return {
|
||||
// The minimum block size (in samples) used in the stream.
|
||||
minimumBlockSize: Token.UINT16_BE.get(buf, off),
|
||||
// The maximum block size (in samples) used in the stream.
|
||||
// (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream.
|
||||
maximumBlockSize: Token.UINT16_BE.get(buf, off + 2) / 1000,
|
||||
// The minimum frame size (in bytes) used in the stream.
|
||||
// May be 0 to imply the value is not known.
|
||||
minimumFrameSize: Token.UINT24_BE.get(buf, off + 4),
|
||||
// The maximum frame size (in bytes) used in the stream.
|
||||
// May be 0 to imply the value is not known.
|
||||
maximumFrameSize: Token.UINT24_BE.get(buf, off + 7),
|
||||
// Sample rate in Hz. Though 20 bits are available,
|
||||
// the maximum sample rate is limited by the structure of frame headers to 655350Hz.
|
||||
// Also, a value of 0 is invalid.
|
||||
sampleRate: Token.UINT24_BE.get(buf, off + 10) >> 4,
|
||||
// probably slower: sampleRate: common.getBitAllignedNumber(buf, off + 10, 0, 20),
|
||||
// (number of channels)-1. FLAC supports from 1 to 8 channels
|
||||
channels: util.getBitAllignedNumber(buf, off + 12, 4, 3) + 1,
|
||||
// bits per sample)-1.
|
||||
// FLAC supports from 4 to 32 bits per sample. Currently the reference encoder and decoders only support up to 24 bits per sample.
|
||||
bitsPerSample: util.getBitAllignedNumber(buf, off + 12, 7, 5) + 1,
|
||||
// Total samples in stream.
|
||||
// 'Samples' means inter-channel sample, i.e. one second of 44.1Khz audio will have 44100 samples regardless of the number of channels.
|
||||
// A value of zero here means the number of total samples is unknown.
|
||||
totalSamples: util.getBitAllignedNumber(buf, off + 13, 4, 36),
|
||||
// the MD5 hash of the file (see notes for usage... it's a littly tricky)
|
||||
fileMD5: new Token.Uint8ArrayType(16).get(buf, off + 18)
|
||||
};
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user