Files
lidify/backend/src/services/coverArtExtractor.ts
T
2025-12-25 18:58:06 -06:00

76 lines
2.1 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
import * as crypto from "crypto";
import { parseFile } from "music-metadata";
export class CoverArtExtractor {
private coverCachePath: string;
constructor(coverCachePath: string) {
this.coverCachePath = coverCachePath;
// Ensure cache directory exists
if (!fs.existsSync(this.coverCachePath)) {
fs.mkdirSync(this.coverCachePath, { recursive: true });
}
}
/**
* Extract cover art from audio file and save to cache
* Returns relative path to cached cover art, or null if none found
*/
async extractCoverArt(
audioFilePath: string,
albumId: string
): Promise<string | null> {
try {
// Check if already cached
const cacheFileName = `${albumId}.jpg`;
const cachePath = path.join(this.coverCachePath, cacheFileName);
if (fs.existsSync(cachePath)) {
return cacheFileName;
}
// Parse audio file metadata
const metadata = await parseFile(audioFilePath);
// Get embedded picture
const picture = metadata.common.picture?.[0];
if (!picture) {
return null;
}
// Save to cache
await fs.promises.writeFile(cachePath, picture.data);
console.log(
`[COVER-ART] Extracted cover art from ${path.basename(audioFilePath)}: ${cacheFileName}`
);
return cacheFileName;
} catch (err) {
console.error(
`[COVER-ART] Failed to extract from ${audioFilePath}:`,
err
);
return null;
}
}
/**
* Get cover art URL for album
* Returns relative path if available, or null
*/
async getCoverArtPath(albumId: string): Promise<string | null> {
const cacheFileName = `${albumId}.jpg`;
const cachePath = path.join(this.coverCachePath, cacheFileName);
if (fs.existsSync(cachePath)) {
return cacheFileName;
}
return null;
}
}