import { formatFolderPath, formatMp3Filename, sanitizeFilename, type Mp3Metadata, } from "./mp3Tags"; import type { ScannedLibraryTrack } from "./libraryScanner"; import type { SpotiRipSettings } from "./spotiripSettings"; type OrganizeDirectoryHandle = FileSystemDirectoryHandle & { getDirectoryHandle(name: string, options?: { create?: boolean }): Promise; getFileHandle(name: string, options?: { create?: boolean }): Promise; }; function segment(value: string | undefined, fallback: string) { return sanitizeFilename(value?.trim() || fallback).replace(/[./\\]+/g, "_"); } async function ensureDirectory(root: FileSystemDirectoryHandle, names: string[]) { let current = root as OrganizeDirectoryHandle; for (const name of names) { current = (await current.getDirectoryHandle(name, { create: true })) as OrganizeDirectoryHandle; } return current; } async function exists(directory: FileSystemDirectoryHandle, name: string) { try { await (directory as OrganizeDirectoryHandle).getFileHandle(name); return true; } catch { return false; } } async function uniqueName(directory: FileSystemDirectoryHandle, desired: string) { if (!(await exists(directory, desired))) return desired; const dot = desired.lastIndexOf("."); const base = dot > 0 ? desired.slice(0, dot) : desired; const ext = dot > 0 ? desired.slice(dot) : ""; for (let index = 2; index < 10000; index += 1) { const candidate = base + " (" + index + ")" + ext; if (!(await exists(directory, candidate))) return candidate; } throw new Error("Could not create a unique library filename."); } function metadataForTrack( track: ScannedLibraryTrack, settings?: Pick, ): Mp3Metadata { const trackLabel = track.trackNumber ? settings?.multiDiscMode === "offset" && track.discNumber ? String(track.discNumber * 100 + track.trackNumber) : settings?.multiDiscMode === "disc-prefix" && track.discNumber ? String(track.discNumber) + "-" + String(track.trackNumber).padStart(2, "0") : String(track.trackNumber).padStart(2, "0") : ""; return { title: track.title || "Unknown Track", artist: track.artist || "Unknown Artist", album: track.album, albumArtist: track.albumArtist, year: track.date?.slice(0, 4), trackNumber: track.trackNumber, trackLabel, discNumber: track.discNumber, genre: track.genre, isrc: track.isrc, }; } export function suggestedLibraryPath( track: ScannedLibraryTrack, settings?: Pick< SpotiRipSettings, "folderTemplate" | "filenameTemplate" | "multiDiscMode" >, ) { const metadata = metadataForTrack(track, settings); const folders = formatFolderPath( settings?.folderTemplate || "{album_artist}/{year} - {album}", metadata, ); const extension = track.file.name.includes(".") ? track.file.name.slice(track.file.name.lastIndexOf(".")).toLowerCase() : ""; const formatted = formatMp3Filename( settings?.filenameTemplate || "{artist} - {title}", metadata, ).replace(/\.mp3$/i, ""); const filename = segment(formatted, "Unknown Track") + extension; return { folders, filename }; } export async function copyOrganizedTrack( root: FileSystemDirectoryHandle, track: ScannedLibraryTrack, settings?: Pick< SpotiRipSettings, "folderTemplate" | "filenameTemplate" | "multiDiscMode" >, ) { const target = suggestedLibraryPath(track, settings); const directory = await ensureDirectory(root, target.folders); const filename = await uniqueName(directory, target.filename); const handle = await (directory as OrganizeDirectoryHandle).getFileHandle(filename, { create: true }); const writable = await handle.createWritable(); await writable.write(track.file); await writable.close(); return [...target.folders, filename].join("/"); }