Initial working version

This commit is contained in:
Samuel Kent
2022-12-22 20:22:22 +11:00
parent ce9675a1cc
commit ced7fa5092
902 changed files with 150252 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
import { BasicParser } from '../common/BasicParser';
import * as iff from '../iff';
/**
* AIFF - Audio Interchange File Format
*
* Ref:
* http://www.onicos.com/staff/iz/formats/aiff.html
* http://muratnkonar.com/aiff/index.html
* http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html
*/
export declare class AIFFParser extends BasicParser {
private isCompressed;
parse(): Promise<void>;
readData(header: iff.IChunkHeader): Promise<number>;
}
+85
View File
@@ -0,0 +1,85 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIFFParser = void 0;
const Token = require("token-types");
const initDebug = require("debug");
const strtok3 = require("strtok3/lib/core");
const ID3v2Parser_1 = require("../id3v2/ID3v2Parser");
const FourCC_1 = require("../common/FourCC");
const BasicParser_1 = require("../common/BasicParser");
const AiffToken = require("./AiffToken");
const iff = require("../iff");
const debug = initDebug('music-metadata:parser:aiff');
/**
* AIFF - Audio Interchange File Format
*
* Ref:
* http://www.onicos.com/staff/iz/formats/aiff.html
* http://muratnkonar.com/aiff/index.html
* http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html
*/
class AIFFParser extends BasicParser_1.BasicParser {
async parse() {
const header = await this.tokenizer.readToken(iff.Header);
if (header.chunkID !== 'FORM')
throw new Error('Invalid Chunk-ID, expected \'FORM\''); // Not AIFF format
const type = await this.tokenizer.readToken(FourCC_1.FourCcToken);
switch (type) {
case 'AIFF':
this.metadata.setFormat('container', type);
this.isCompressed = false;
break;
case 'AIFC':
this.metadata.setFormat('container', 'AIFF-C');
this.isCompressed = true;
break;
default:
throw Error('Unsupported AIFF type: ' + type);
}
this.metadata.setFormat('lossless', !this.isCompressed);
try {
while (!this.tokenizer.fileInfo.size || this.tokenizer.fileInfo.size - this.tokenizer.position >= iff.Header.len) {
debug('Reading AIFF chunk at offset=' + this.tokenizer.position);
const chunkHeader = await this.tokenizer.readToken(iff.Header);
debug(`Chunk id=${chunkHeader.chunkID}`);
const nextChunk = 2 * Math.round(chunkHeader.chunkSize / 2);
const bytesRead = await this.readData(chunkHeader);
await this.tokenizer.ignore(nextChunk - bytesRead);
}
}
catch (err) {
if (err instanceof strtok3.EndOfStreamError) {
debug(`End-of-stream`);
}
else {
throw err;
}
}
}
async readData(header) {
switch (header.chunkID) {
case 'COMM': // The Common Chunk
const common = await this.tokenizer.readToken(new AiffToken.Common(header, this.isCompressed));
this.metadata.setFormat('bitsPerSample', common.sampleSize);
this.metadata.setFormat('sampleRate', common.sampleRate);
this.metadata.setFormat('numberOfChannels', common.numChannels);
this.metadata.setFormat('numberOfSamples', common.numSampleFrames);
this.metadata.setFormat('duration', common.numSampleFrames / common.sampleRate);
this.metadata.setFormat('codec', common.compressionName);
return header.chunkSize;
case 'ID3 ': // ID3-meta-data
const id3_data = await this.tokenizer.readToken(new Token.Uint8ArrayType(header.chunkSize));
const rst = strtok3.fromBuffer(id3_data);
await new ID3v2Parser_1.ID3v2Parser().parse(this.metadata, rst, this.options);
return header.chunkSize;
case 'SSND': // Sound Data Chunk
if (this.metadata.format.duration) {
this.metadata.setFormat('bitrate', 8 * header.chunkSize / this.metadata.format.duration);
}
return 0;
default:
return 0;
}
}
}
exports.AIFFParser = AIFFParser;
+22
View File
@@ -0,0 +1,22 @@
/// <reference types="node" />
import * as iff from '../iff';
import { IGetToken } from "strtok3";
/**
* The Common Chunk.
* Describes fundamental parameters of the waveform data such as sample rate, bit resolution, and how many channels of
* digital audio are stored in the FORM AIFF.
*/
export interface ICommon {
numChannels: number;
numSampleFrames: number;
sampleSize: number;
sampleRate: number;
compressionType?: string;
compressionName?: string;
}
export declare class Common implements IGetToken<ICommon> {
private isAifc;
len: number;
constructor(header: iff.IChunkHeader, isAifc: boolean);
get(buf: Buffer, off: number): ICommon;
}
+43
View File
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Common = void 0;
const Token = require("token-types");
const FourCC_1 = require("../common/FourCC");
class Common {
constructor(header, isAifc) {
this.isAifc = isAifc;
const minimumChunkSize = isAifc ? 22 : 18;
if (header.chunkSize < minimumChunkSize)
throw new Error(`COMMON CHUNK size should always be at least ${minimumChunkSize}`);
this.len = header.chunkSize;
}
get(buf, off) {
// see: https://cycling74.com/forums/aiffs-80-bit-sample-rate-value
const shift = buf.readUInt16BE(off + 8) - 16398;
const baseSampleRate = buf.readUInt16BE(off + 8 + 2);
const res = {
numChannels: buf.readUInt16BE(off),
numSampleFrames: buf.readUInt32BE(off + 2),
sampleSize: buf.readUInt16BE(off + 6),
sampleRate: shift < 0 ? baseSampleRate >> Math.abs(shift) : baseSampleRate << shift
};
if (this.isAifc) {
res.compressionType = FourCC_1.FourCcToken.get(buf, off + 18);
if (this.len > 22) {
const strLen = buf.readInt8(off + 22);
const padding = (strLen + 1) % 2;
if (23 + strLen + padding === this.len) {
res.compressionName = new Token.StringType(strLen, 'binary').get(buf, off + 23);
}
else {
throw new Error('Illegal pstring length');
}
}
}
else {
res.compressionName = 'PCM';
}
return res;
}
}
exports.Common = Common;