UNPKG

jamp3

Version:

mp3, id3v1, id3v2 - reader & writer

111 lines (104 loc) 3.58 kB
import fse from 'fs-extra'; import {Readable} from 'stream'; import {IMP3} from './mp3.types'; import {MP3Reader, MP3ReaderOptions} from './mp3.reader'; import {IID3V2} from '../id3v2/id3v2.types'; import {rawHeaderOffSet} from './mp3.mpeg.frame'; import {ITagID} from '../..'; import {updateFile} from '../common/update-file'; import {prepareResult} from './mp3.result'; /** * Class for * - reading ID3v1/2 and MP3 information * - removing ID3v1/2 * * Basic usage example: * * ```ts * [[include:snippet_mp3-read.ts]] * ``` */ export class MP3 { /** * Reads a stream with given options * @param stream the stream to read (NodeJS.stream.Readable) * @param options define which information should be returned * @param streamSize if known, provide the stream size to speed up duration calculation (otherwise stream may have to be parsed in full) * @return a object returning parsed information */ async readStream(stream: Readable, options: IMP3.ReadOptions, streamSize?: number): Promise<IMP3.Result> { const reader = new MP3Reader(); const layout = await reader.readStream(stream, Object.assign({streamSize}, options)); return await prepareResult(options, layout); } /** * Reads a file in given path with given options * @param filename the file to read * @param options define which information should be returned * @return a object returning parsed information */ async read(filename: string, options: IMP3.ReadOptions): Promise<IMP3.Result> { const reader = new MP3Reader(); const layout = await reader.read(filename, options); return await prepareResult(options, layout); } /** * Removes ID3v1 and/or ID3v2 Tag from a file with given options * @param filename the file to clean * @param options define which tags should be removed * @return a object returning which tags have been removed */ async removeTags(filename: string, options: IMP3.RemoveTagsOptions): Promise<IMP3.RemoveResult | undefined> { const stat = await fse.stat(filename); const opts: MP3ReaderOptions = { streamSize: stat.size, id3v2: options.id3v2, detectDuplicateID3v2: options.id3v2, id3v1: options.id3v1, mpegQuick: options.id3v2 }; let id2v1removed = false; let id2v2removed = false; await updateFile(filename, opts, !!options.keepBackup, layout => { for (const tag of layout.tags) { if (options.id3v2 && tag.id === ITagID.ID3v2 && tag.end > 0) { return true; } else if (options.id3v1 && tag.id === ITagID.ID3v1 && tag.end === stat.size && tag.start < stat.size) { return true; } } return false; }, async (layout, fileWriter): Promise<void> => { let start = 0; let finish = stat.size; let specEnd = 0; for (const tag of layout.tags) { if (tag.id === ITagID.ID3v2 && options.id3v2) { if (start < tag.end) { specEnd = (tag as IID3V2.RawTag).head.size + tag.start + 10 /*header itself*/; start = tag.end; id2v2removed = true; } } else if (tag.id === ITagID.ID3v1 && options.id3v1 && tag.end === stat.size) { if (finish > tag.start) { finish = tag.start; id2v1removed = true; } } } if (options.id3v2) { if (layout.frameheaders.length > 0) { start = rawHeaderOffSet(layout.frameheaders[0]); } else { start = Math.max(start, specEnd); } } if (finish > start) { await fileWriter.copyRange(filename, start, finish); } }); return id2v2removed || id2v1removed ? {id3v2: id2v2removed, id3v1: id2v1removed} : undefined; } }