UNPKG

taglib-wasm

Version:

TagLib for TypeScript platforms: Deno, Node.js, Bun, Electron, browsers, and Cloudflare Workers

337 lines (336 loc) 10.6 kB
import { cStringToJS, jsToCString, loadTagLibModuleForWorkers } from "./wasm-workers.js"; import { EnvironmentError, InvalidFormatError, MemoryError } from "./errors.js"; class AudioFileWorkers { constructor(module, fileId) { this.module = module; this.fileId = fileId; this.tagPtr = module._taglib_file_tag?.(fileId) || 0; this.propsPtr = module._taglib_file_audioproperties?.(fileId) || 0; } /** * Check if the file is valid and was loaded successfully. * @returns true if the file is valid and can be processed */ isValid() { return this.module._taglib_file_is_valid?.(this.fileId) !== 0; } /** * Get the file format. * @returns Audio format (e.g., "MP3", "FLAC", "OGG") */ format() { const formatPtr = this.module._taglib_file_format?.(this.fileId) || 0; if (formatPtr === 0) return "MP3"; const formatStr = cStringToJS(this.module, formatPtr); return formatStr; } /** * Get basic tag information. * @returns Object containing title, artist, album, etc. */ tag() { if (this.tagPtr === 0) return {}; const title = this.module._taglib_tag_title?.(this.tagPtr) || 0; const artist = this.module._taglib_tag_artist?.(this.tagPtr) || 0; const album = this.module._taglib_tag_album?.(this.tagPtr) || 0; const comment = this.module._taglib_tag_comment?.(this.tagPtr) || 0; const genre = this.module._taglib_tag_genre?.(this.tagPtr) || 0; const year = this.module._taglib_tag_year?.(this.tagPtr) || 0; const track = this.module._taglib_tag_track?.(this.tagPtr) || 0; return { title: title ? cStringToJS(this.module, title) : void 0, artist: artist ? cStringToJS(this.module, artist) : void 0, album: album ? cStringToJS(this.module, album) : void 0, comment: comment ? cStringToJS(this.module, comment) : void 0, genre: genre ? cStringToJS(this.module, genre) : void 0, year: year || void 0, track: track || void 0 }; } /** * Get audio properties (duration, bitrate, etc.). * @returns Audio properties or null if unavailable */ audioProperties() { if (this.propsPtr === 0) return null; const length = this.module._taglib_audioproperties_length?.(this.propsPtr) || 0; const bitrate = this.module._taglib_audioproperties_bitrate?.(this.propsPtr) || 0; const sampleRate = this.module._taglib_audioproperties_samplerate?.( this.propsPtr ) || 0; const channels = this.module._taglib_audioproperties_channels?.( this.propsPtr ) || 0; return { length, bitrate, sampleRate, channels, bitsPerSample: 0, // Not available in C API compatibility mode codec: "Unknown", // Not available in C API compatibility mode containerFormat: "UNKNOWN", // Not available in C API compatibility mode isLossless: false // Not available in C API compatibility mode }; } /** * Set the title tag. * @param title - New title value */ setTitle(title) { if (this.tagPtr === 0) return; const titlePtr = jsToCString(this.module, title); this.module._taglib_tag_set_title?.(this.tagPtr, titlePtr); this.module._free(titlePtr); } /** * Set the artist tag. * @param artist - New artist value */ setArtist(artist) { if (this.tagPtr === 0) return; const artistPtr = jsToCString(this.module, artist); this.module._taglib_tag_set_artist?.(this.tagPtr, artistPtr); this.module._free(artistPtr); } /** * Set the album tag. * @param album - New album value */ setAlbum(album) { if (this.tagPtr === 0) return; const albumPtr = jsToCString(this.module, album); this.module._taglib_tag_set_album?.(this.tagPtr, albumPtr); this.module._free(albumPtr); } /** * Set the comment tag. * @param comment - New comment value */ setComment(comment) { if (this.tagPtr === 0) return; const commentPtr = jsToCString(this.module, comment); this.module._taglib_tag_set_comment?.(this.tagPtr, commentPtr); this.module._free(commentPtr); } /** * Set the genre tag. * @param genre - New genre value */ setGenre(genre) { if (this.tagPtr === 0) return; const genrePtr = jsToCString(this.module, genre); this.module._taglib_tag_set_genre?.(this.tagPtr, genrePtr); this.module._free(genrePtr); } /** * Set the year tag. * @param year - Release year */ setYear(year) { if (this.tagPtr === 0) return; this.module._taglib_tag_set_year?.(this.tagPtr, year); } /** * Set the track number tag. * @param track - Track number */ setTrack(track) { if (this.tagPtr === 0) return; this.module._taglib_tag_set_track?.(this.tagPtr, track); } /** * Save changes to the file. * Note: In Workers context, this saves to the in-memory buffer only. * @returns true if save was successful */ save() { if (this.fileId !== 0) { return this.module._taglib_file_save?.(this.fileId) !== 0; } return false; } /** * Get the current file buffer after modifications. * Note: This is not implemented in the Workers API. * @returns Empty Uint8Array (not implemented) * @throws {Error} Consider using the Full API for this functionality */ getFileBuffer() { console.warn( "getFileBuffer() is not implemented in Workers API. Use Full API for this functionality." ); return new Uint8Array(0); } /** * Get extended metadata with format-agnostic field names. * Note: Currently returns only basic fields in Workers API. * @returns Extended tag object with basic fields populated */ extendedTag() { const basicTag = this.tag(); return { ...basicTag, // Advanced fields placeholder - would be populated by PropertyMap reading acoustidFingerprint: void 0, acoustidId: void 0, musicbrainzTrackId: void 0, musicbrainzReleaseId: void 0, musicbrainzArtistId: void 0, musicbrainzReleaseGroupId: void 0, albumArtist: void 0, composer: void 0, discNumber: void 0, totalTracks: void 0, totalDiscs: void 0, bpm: void 0, compilation: void 0, titleSort: void 0, artistSort: void 0, albumSort: void 0, replayGainTrackGain: void 0, replayGainTrackPeak: void 0, replayGainAlbumGain: void 0, replayGainAlbumPeak: void 0, appleSoundCheck: void 0 }; } /** * Set extended metadata using format-agnostic field names. * Note: Currently only supports basic fields in Workers API. * @param tag - Partial extended tag object with fields to update */ setExtendedTag(tag) { if (tag.title !== void 0) this.setTitle(tag.title); if (tag.artist !== void 0) this.setArtist(tag.artist); if (tag.album !== void 0) this.setAlbum(tag.album); if (tag.comment !== void 0) this.setComment(tag.comment); if (tag.genre !== void 0) this.setGenre(tag.genre); if (tag.year !== void 0) this.setYear(tag.year); if (tag.track !== void 0) this.setTrack(tag.track); } /** * Clean up resources. * Always call this when done to prevent memory leaks. */ dispose() { if (this.fileId !== 0) { this.module._taglib_file_delete?.(this.fileId); this.fileId = 0; } } } class TagLibWorkers { constructor(module) { this.module = module; } /** * Initialize TagLib for Workers with Wasm binary * * @param wasmBinary - The WebAssembly binary as Uint8Array * @param config - Optional configuration for the Wasm module * * @example * ```typescript * // In a Cloudflare Worker * import wasmBinary from "../build/taglib.wasm.js"; * * const taglib = await TagLibWorkers.initialize(wasmBinary); * const file = taglib.open(audioBuffer); * const metadata = file.tag(); * ``` */ static async initialize(wasmBinary, config) { const module = await loadTagLibModuleForWorkers(wasmBinary, config); return new TagLibWorkers(module); } /** * Open an audio file from a buffer. * * @param buffer - Audio file data as Uint8Array * @returns AudioFileWorkers instance * @throws {Error} If Wasm module is not initialized * @throws {Error} If file format is invalid or unsupported * @throws {Error} If Workers API C-style functions are not available * * @example * ```typescript * const audioData = new Uint8Array(await request.arrayBuffer()); * const file = taglib.open(audioData); * ``` */ open(buffer) { if (!this.module.HEAPU8) { throw new MemoryError( "Wasm module not properly initialized: missing HEAPU8. The module may not have loaded correctly in the Workers environment." ); } let dataPtr; if (this.module.allocate && this.module.ALLOC_NORMAL !== void 0) { dataPtr = this.module.allocate(buffer, this.module.ALLOC_NORMAL); } else { dataPtr = this.module._malloc(buffer.length); this.module.HEAPU8.set(buffer, dataPtr); } if (!this.module._taglib_file_new_from_buffer) { throw new EnvironmentError( "Workers", "requires C-style functions which are not available. Use the Full API instead for this environment", "C-style function exports" ); } const fileId = this.module._taglib_file_new_from_buffer( dataPtr, buffer.length ); if (fileId === 0) { this.module._free(dataPtr); throw new InvalidFormatError( "Failed to open audio file. File format may be invalid or not supported", buffer.length ); } this.module._free(dataPtr); return new AudioFileWorkers(this.module, fileId); } /** * Open an audio file from a buffer (backward compatibility). * Consider using `open()` for consistency with the Full API. * @param buffer Audio file data as Uint8Array * @returns Audio file instance */ openFile(buffer) { return this.open(buffer); } /** * Get the underlying Wasm module for advanced usage. * @returns The initialized TagLib Wasm module */ getModule() { return this.module; } } async function processAudioMetadata(wasmBinary, audioData, config) { const taglib = await TagLibWorkers.initialize(wasmBinary, config); const file = taglib.open(audioData); try { const tag = file.tag(); const properties = file.audioProperties(); const format = file.format(); return { tag, properties, format }; } finally { file.dispose(); } } export { AudioFileWorkers, TagLibWorkers, processAudioMetadata };