From 4485f88eef539160e209218fbb50bf37e1152383 Mon Sep 17 00:00:00 2001 From: flamboh Date: Fri, 17 Jul 2026 03:56:32 -0800 Subject: [PATCH 1/2] refactor(audio): make formats explicit --- src/features/audio/audioFormat.ts | 40 +++++++++++++++++++ src/features/audio/audioMetadataIO.ts | 16 ++++++-- src/features/audio/mp3Compatibility.ts | 17 ++++---- src/features/editor/TrackMetadataEditor.tsx | 8 +++- src/features/editor/useTrackEditorSession.ts | 13 ++++-- src/features/export/downloadLibrary.ts | 7 +++- src/features/import/LandingScreen.tsx | 3 +- src/features/import/audioUpload.tsx | 3 +- src/features/import/downloadTrack.ts | 15 ++++--- src/features/library/fileMetadataOps.ts | 13 ++++-- src/features/library/metadataCleanup.ts | 3 +- src/features/library/types.ts | 2 + tests/unit/features/audio/audioFormat.test.ts | 30 ++++++++++++++ .../features/audio/audioMetadataIO.test.ts | 35 ++++++++++++++++ tests/unit/features/audio/mp3Utils.test.ts | 2 + .../features/editor/albumDialogState.test.ts | 1 + .../unit/features/editor/audioTagger.test.ts | 13 +++++- .../editor/trackMetadataEditor.test.tsx | 1 + .../editor/useTrackEditorSession.test.ts | 3 ++ .../features/export/downloadLibrary.test.ts | 3 ++ .../export/exportMetadataWrites.test.ts | 1 + .../features/export/useExportSession.test.ts | 1 + .../import/importQueuePresentation.test.ts | 1 + tests/unit/features/library/albumOps.test.ts | 1 + .../features/library/fileMetadataOps.test.ts | 4 ++ .../features/library/libraryState.test.ts | 1 + .../features/library/metadataCleanup.test.ts | 1 + .../features/library/useLibraryStore.test.ts | 2 + .../workspace/useAudioImportSession.test.ts | 1 + .../workspace/useAudioWorkspace.test.tsx | 1 + 30 files changed, 211 insertions(+), 31 deletions(-) create mode 100644 src/features/audio/audioFormat.ts create mode 100644 tests/unit/features/audio/audioFormat.test.ts diff --git a/src/features/audio/audioFormat.ts b/src/features/audio/audioFormat.ts new file mode 100644 index 00000000..419aa40d --- /dev/null +++ b/src/features/audio/audioFormat.ts @@ -0,0 +1,40 @@ +export type AudioFormat = "mp3"; + +export interface AudioFormatInfo { + id: AudioFormat; + extension: ".mp3"; + mimeType: "audio/mpeg"; +} + +const audioFormatInfo = { + mp3: { + id: "mp3", + extension: ".mp3", + mimeType: "audio/mpeg", + }, +} as const satisfies Record; + +export const getAudioFormatInfo = (format: AudioFormat): AudioFormatInfo => + audioFormatInfo[format]; + +export const getAudioUploadAccept = () => + Object.values(audioFormatInfo) + .flatMap(({ extension, mimeType }) => [extension, mimeType]) + .join(","); + +export const hasAudioExtension = (filename: string, format: AudioFormat) => + filename.toLowerCase().endsWith(getAudioFormatInfo(format).extension); + +export const withoutAudioExtension = (filename: string, format: AudioFormat) => { + const { extension } = getAudioFormatInfo(format); + return hasAudioExtension(filename, format) ? filename.slice(0, -extension.length) : filename; +}; + +export const withAudioExtension = (base: string, format: AudioFormat) => + hasAudioExtension(base, format) ? base : `${base}${getAudioFormatInfo(format).extension}`; + +export const normalizeAudioFilename = (filename: string, format: AudioFormat) => { + if (hasAudioExtension(filename, format)) return filename; + const basename = filename.replace(/\.[^.]+$/, "") || "track"; + return withAudioExtension(basename, format); +}; diff --git a/src/features/audio/audioMetadataIO.ts b/src/features/audio/audioMetadataIO.ts index 08ad0076..fad6306b 100644 --- a/src/features/audio/audioMetadataIO.ts +++ b/src/features/audio/audioMetadataIO.ts @@ -7,9 +7,13 @@ import { import { audioMetadataSchema } from "@/features/audio/metadata"; import { parseTrackTagNumber, toGenreString, type UploadedTrack } from "@/features/audio/mp3Utils"; import type { AudioMetadata, TagiumFile } from "@/features/library/types"; +import { + getAudioFormatInfo, + withAudioExtension, + withoutAudioExtension, +} from "@/features/audio/audioFormat"; import { getMp3AdmissionError, - MP3_MIME_TYPE, normalizeMp3File, normalizeMp3Filename, } from "@/features/audio/mp3Compatibility"; @@ -171,7 +175,7 @@ const parseUploadedTrack = (file: File) => })) ?? []; const metadata = yield* decodeReadMetadata({ - filename: normalizeMp3Filename(file.name).replace(/\.mp3$/i, ""), + filename: withoutAudioExtension(normalizeMp3Filename(file.name), "mp3"), title: mp3tag.tags.title || "", artist: mp3tag.tags.artist || "", album: mp3tag.tags.album || "", @@ -187,6 +191,7 @@ const parseUploadedTrack = (file: File) => return { file: { id, + format: "mp3", file: normalizedFile, originalFile: file, filename: normalizedFile.name, @@ -209,6 +214,7 @@ const parseUploadedTrack = (file: File) => return Effect.succeed({ file: { id, + format: "mp3", file, originalFile: file, filename: file.name, @@ -297,9 +303,11 @@ const writeMetadata = (fileToUpdate: TagiumFile, newTags: AudioMetadata) => return new File( [new Uint8Array(buffer)], - metadataToWrite.filename ? `${metadataToWrite.filename}.mp3` : fileToUpdate.filename, + metadataToWrite.filename + ? withAudioExtension(metadataToWrite.filename, fileToUpdate.format) + : fileToUpdate.filename, { - type: MP3_MIME_TYPE, + type: getAudioFormatInfo(fileToUpdate.format).mimeType, }, ); }); diff --git a/src/features/audio/mp3Compatibility.ts b/src/features/audio/mp3Compatibility.ts index b10929e1..24f84b9f 100644 --- a/src/features/audio/mp3Compatibility.ts +++ b/src/features/audio/mp3Compatibility.ts @@ -1,4 +1,10 @@ -export const MP3_MIME_TYPE = "audio/mpeg"; +import { + getAudioFormatInfo, + hasAudioExtension, + normalizeAudioFilename, +} from "@/features/audio/audioFormat"; + +export const MP3_MIME_TYPE = getAudioFormatInfo("mp3").mimeType; const bitrateKbps = { mpeg1Layer1: [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448], @@ -120,7 +126,7 @@ const startsWithAscii = (bytes: Uint8Array, value: string, offset = 0) => export const getMp3AdmissionError = (file: File, bytes: Uint8Array) => { if (bytes.length === 0) return `${file.name} is empty. Choose a valid mp3 file.`; - if (!/\.mp3$/i.test(file.name)) { + if (!hasAudioExtension(file.name, "mp3")) { return `${file.name} is not an mp3. tagium currently supports mp3 files only.`; } const knownUnsupported = @@ -135,13 +141,10 @@ export const getMp3AdmissionError = (file: File, bytes: Uint8Array) => { return `${file.name} is not a valid mp3. The file may be corrupt or renamed.`; }; -export const normalizeMp3Filename = (filename: string) => { - const basename = filename.replace(/\.[^.]+$/, "") || "track"; - return `${basename}.mp3`; -}; +export const normalizeMp3Filename = (filename: string) => normalizeAudioFilename(filename, "mp3"); export const normalizeMp3File = (file: File) => - file.type === MP3_MIME_TYPE && /\.mp3$/i.test(file.name) + file.type === MP3_MIME_TYPE && hasAudioExtension(file.name, "mp3") ? file : new File([file], normalizeMp3Filename(file.name), { type: MP3_MIME_TYPE, diff --git a/src/features/editor/TrackMetadataEditor.tsx b/src/features/editor/TrackMetadataEditor.tsx index 0a0ae986..6bfb3af7 100644 --- a/src/features/editor/TrackMetadataEditor.tsx +++ b/src/features/editor/TrackMetadataEditor.tsx @@ -16,6 +16,7 @@ import { Input } from "@/components/ui/input"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import CoverArt from "@/features/editor/coverArt"; import { isValidFilenameBase, sanitizeFilenameBase } from "@/features/library/filename"; +import { getAudioFormatInfo } from "@/features/audio/audioFormat"; import { getSampleTrack, type SampleTrackMetadata } from "@/features/editor/sampleMetadata"; import { getTrackFailureDisplay } from "@/features/workspace/systemFailure"; import type { AlbumGroup, AudioMetadata, TagiumFile } from "@/features/library/types"; @@ -90,6 +91,7 @@ function TrackFilenameHeader({ filenamePlaceholder, filenameInvalid, filenameRegistration, + extension, failure, }: { syncFilenames: boolean; @@ -98,6 +100,7 @@ function TrackFilenameHeader({ filenamePlaceholder: string; filenameInvalid: boolean; filenameRegistration: UseFormRegisterReturn<"filename">; + extension: string; failure: TrackFailure | null; }) { return ( @@ -113,7 +116,7 @@ function TrackFilenameHeader({ filename follows the title - .mp3 + {extension} ) : ( )} @@ -414,6 +417,7 @@ function LoadedTrackMetadataEditor({ filenamePlaceholder={placeholder.filename} filenameInvalid={filenameInvalid} filenameRegistration={filenameRegistration} + extension={getAudioFormatInfo(selectedFile.format).extension} failure={failure} />
diff --git a/src/features/editor/useTrackEditorSession.ts b/src/features/editor/useTrackEditorSession.ts index cd0f5505..de8112e0 100644 --- a/src/features/editor/useTrackEditorSession.ts +++ b/src/features/editor/useTrackEditorSession.ts @@ -22,6 +22,7 @@ import type { MetadataPatch, TagiumFile, } from "@/features/library/types"; +import { withAudioExtension } from "@/features/audio/audioFormat"; type PreviewField = "filename" | "title" | "artist"; @@ -64,7 +65,9 @@ const applyMetadataPatch = (metadata: AudioMetadata, patch: MetadataPatch): Audi }); const getFilenameFromPatch = (file: TagiumFile, patch: MetadataPatch) => - hasOwn(patch, "filename") && patch.filename ? `${patch.filename}.mp3` : file.filename; + hasOwn(patch, "filename") && patch.filename + ? withAudioExtension(patch.filename, file.format) + : file.filename; const withPendingMetadataPatch = ( file: TagiumFile, @@ -261,7 +264,9 @@ export const useTrackEditorSession = ({ ? withPendingMetadataPatch( { ...file, - filename: newTags.filename ? `${newTags.filename}.mp3` : file.filename, + filename: newTags.filename + ? withAudioExtension(newTags.filename, file.format) + : file.filename, metadata, status: "pending" as const, }, @@ -306,7 +311,9 @@ export const useTrackEditorSession = ({ bitrate: file.metadata?.bitrate || 0, sampleRate: file.metadata?.sampleRate || 0, }, - filename: newTags.filename ? `${newTags.filename}.mp3` : file.filename, + filename: newTags.filename + ? withAudioExtension(newTags.filename, file.format) + : file.filename, downloadError: message, }, createSubmittedMetadataPatch(newTags), diff --git a/src/features/export/downloadLibrary.ts b/src/features/export/downloadLibrary.ts index a4c3394e..60c0b059 100644 --- a/src/features/export/downloadLibrary.ts +++ b/src/features/export/downloadLibrary.ts @@ -1,7 +1,7 @@ import filenamify from "filenamify"; import type { AlbumGroup, TagiumFile } from "@/features/library/types"; import { isValidFilenameBase } from "@/features/library/filename"; -import { normalizeMp3Filename } from "@/features/audio/mp3Compatibility"; +import { normalizeAudioFilename, withAudioExtension } from "@/features/audio/audioFormat"; export interface DownloadZipEntry { path: string; @@ -120,7 +120,10 @@ const addTrackEntry = ( track: TagiumFile | undefined, ) => { if (!track || !isTrackReadyForDownload(track) || !track.file) return; - const filename = normalizeMp3Filename(cleanPathPart(track.filename, "track.mp3")); + const filename = normalizeAudioFilename( + cleanPathPart(track.filename, withAudioExtension("track", track.format)), + track.format, + ); entries.push({ path: uniquePath(`${folderPath}/${filename}`, usedPaths), file: track.file, diff --git a/src/features/import/LandingScreen.tsx b/src/features/import/LandingScreen.tsx index 3925d328..9165f2de 100644 --- a/src/features/import/LandingScreen.tsx +++ b/src/features/import/LandingScreen.tsx @@ -3,6 +3,7 @@ import { useRef, useState, type ReactNode } from "react"; import { Music4, Upload } from "lucide-react"; import { cn } from "@/lib/utils"; +import { getAudioUploadAccept } from "@/features/audio/audioFormat"; interface LandingScreenProps { active: boolean; @@ -62,7 +63,7 @@ export default function LandingScreen({ active, children, onAudioUpload }: Landi void; @@ -34,7 +35,7 @@ export default function AudioUpload({ onAudioUpload }: AudioUploadProps) { type="file" id={inputId} className="hidden" - accept=".mp3,audio/mpeg" + accept={getAudioUploadAccept()} onChange={handleAudioUpload} multiple ref={fileInputRef} diff --git a/src/features/import/downloadTrack.ts b/src/features/import/downloadTrack.ts index 237da516..fd8c3954 100644 --- a/src/features/import/downloadTrack.ts +++ b/src/features/import/downloadTrack.ts @@ -15,6 +15,7 @@ import type { MetadataPatch, TagiumFile, } from "@/features/library/types"; +import { withAudioExtension, withoutAudioExtension } from "@/features/audio/audioFormat"; export type DownloadRequest = NonNullable; @@ -107,8 +108,8 @@ export type SoundCloudSetDownloadWorkflowDeps = PlaylistDownloadWorkflowDeps; const filenameFromTitle = (title: string) => { const filename = filenamify(title.trim(), { replacement: "-" }); - if (filename) return `${filename}.mp3`; - return "downloading-track.mp3"; + if (filename) return withAudioExtension(filename, "mp3"); + return withAudioExtension("downloading-track", "mp3"); }; export const titleFromSourceUrl = (sourceUrl: string) => { @@ -139,7 +140,7 @@ export const createDownloadMetadata = ({ duration?: number; trackNumber?: number; }): AudioMetadata => ({ - filename: filenameFromTitle(title).replace(/\.mp3$/i, ""), + filename: withoutAudioExtension(filenameFromTitle(title), "mp3"), title, artist, album, @@ -160,7 +161,8 @@ export const createPendingDownloadTrack = ( pendingMetadataPatch?: MetadataPatch, ): PendingDownloadTrack => ({ id, - filename: `${metadata.filename}.mp3`, + format: "mp3", + filename: withAudioExtension(metadata.filename, "mp3"), status: "pending", downloadStatus: "downloading", downloadRequest, @@ -226,7 +228,10 @@ export const fetchImportedCover = async ( export const createQueuedDownloadTrack = (file: PendingDownloadTrack): QueuedDownloadTrack => ({ fileId: file.id, - title: file.metadata.title || file.filename.replace(/\.mp3$/i, "") || "downloading audio", + title: + file.metadata.title || + withoutAudioExtension(file.filename, file.format) || + "downloading audio", downloadRequest: file.downloadRequest, }); diff --git a/src/features/library/fileMetadataOps.ts b/src/features/library/fileMetadataOps.ts index 05bdecf5..34193638 100644 --- a/src/features/library/fileMetadataOps.ts +++ b/src/features/library/fileMetadataOps.ts @@ -7,6 +7,7 @@ import type { MetadataPatch, TagiumFile, } from "@/features/library/types"; +import { withAudioExtension } from "@/features/audio/audioFormat"; export interface DownloadedTrackHydration { hydratedFile: TagiumFile; @@ -240,14 +241,15 @@ export function applySyncedFilenamesToFiles(files: TagiumFile[], trackIds?: stri const syncedFilename = filenamify(file.metadata.title, { replacement: "-" }); if (!syncedFilename) return file; - if (file.filename === `${syncedFilename}.mp3` && file.metadata.filename === syncedFilename) { + const filename = withAudioExtension(syncedFilename, file.format); + if (file.filename === filename && file.metadata.filename === syncedFilename) { return file; } return markPendingMetadataPatch( { ...file, - filename: `${syncedFilename}.mp3`, + filename, status: file.status === "saved" ? "pending" : file.status, metadata: { ...file.metadata, @@ -436,7 +438,10 @@ export function prepareDownloadedTrackHydration( ...currentFile, file: parsedFile.file, originalFile: parsedFile.originalFile, - filename: nextMetadata?.filename ? `${nextMetadata.filename}.mp3` : parsedFile.filename, + format: parsedFile.format, + filename: nextMetadata?.filename + ? withAudioExtension(nextMetadata.filename, parsedFile.format) + : parsedFile.filename, metadata: nextMetadata, downloadStatus: "ready", downloadError: parsedFile.downloadError, @@ -466,7 +471,7 @@ export function resolveDownloadedTrackHydrationWrite( const nextFile = latestFormMetadata ? { ...latestFile, - filename: `${latestFormMetadata.filename}.mp3`, + filename: withAudioExtension(latestFormMetadata.filename, latestFile.format), metadata: latestFormMetadata, } : latestFile; diff --git a/src/features/library/metadataCleanup.ts b/src/features/library/metadataCleanup.ts index 69ee2def..29c7a2ae 100644 --- a/src/features/library/metadataCleanup.ts +++ b/src/features/library/metadataCleanup.ts @@ -1,5 +1,6 @@ import { sanitizeFilenameBase } from "@/features/library/filename"; import type { AlbumGroup, MetadataPatch, TagiumFile } from "@/features/library/types"; +import { withAudioExtension } from "@/features/audio/audioFormat"; const removableLabels = [ "official audio", @@ -160,7 +161,7 @@ export function findMetadataCleanupSuggestions( beforeTitle: file.metadata.title, afterTitle: cleanup.afterTitle, beforeFilename: file.filename, - afterFilename: `${sanitizeFilenameBase(cleanup.afterTitle)}.mp3`, + afterFilename: withAudioExtension(sanitizeFilenameBase(cleanup.afterTitle), file.format), reasons: cleanup.reasons, }, ]; diff --git a/src/features/library/types.ts b/src/features/library/types.ts index 7f7a903a..1f359baa 100644 --- a/src/features/library/types.ts +++ b/src/features/library/types.ts @@ -1,10 +1,12 @@ import type { AudioDownloadBitrate } from "@/features/import/cobaltAudio"; import type { AudioMetadata, MetadataPatch } from "@/features/audio/metadata"; +import type { AudioFormat } from "@/features/audio/audioFormat"; export type { AudioMetadata, MetadataPatch } from "@/features/audio/metadata"; export interface TagiumFile { id: string; + format: AudioFormat; file?: File; originalFile?: File; sourceImportKey?: string; diff --git a/tests/unit/features/audio/audioFormat.test.ts b/tests/unit/features/audio/audioFormat.test.ts new file mode 100644 index 00000000..0f1e41db --- /dev/null +++ b/tests/unit/features/audio/audioFormat.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vite-plus/test"; +import { + getAudioFormatInfo, + getAudioUploadAccept, + normalizeAudioFilename, + withAudioExtension, + withoutAudioExtension, +} from "@/features/audio/audioFormat"; + +describe("audio format helpers", () => { + it("centralizes the MP3 extension, MIME type, and upload admission hint", () => { + expect(getAudioFormatInfo("mp3")).toEqual({ + id: "mp3", + extension: ".mp3", + mimeType: "audio/mpeg", + }); + expect(getAudioUploadAccept()).toBe(".mp3,audio/mpeg"); + }); + + it("joins and removes the authoritative extension without changing filename casing", () => { + expect(withAudioExtension("Artist - Track", "mp3")).toBe("Artist - Track.mp3"); + expect(withAudioExtension("Artist - Track.MP3", "mp3")).toBe("Artist - Track.MP3"); + expect(withoutAudioExtension("Artist - Track.MP3", "mp3")).toBe("Artist - Track"); + }); + + it("replaces a misleading extension while preserving already-correct filenames", () => { + expect(normalizeAudioFilename("Artist - Track.wav", "mp3")).toBe("Artist - Track.mp3"); + expect(normalizeAudioFilename("Artist - Track.MP3", "mp3")).toBe("Artist - Track.MP3"); + }); +}); diff --git a/tests/unit/features/audio/audioMetadataIO.test.ts b/tests/unit/features/audio/audioMetadataIO.test.ts index df3a28a0..67c9443d 100644 --- a/tests/unit/features/audio/audioMetadataIO.test.ts +++ b/tests/unit/features/audio/audioMetadataIO.test.ts @@ -88,6 +88,7 @@ const tagiumFile = (overrides: Partial = {}): TagiumFile => ({ hasBufferedChanges: false, metadata: metadata(), ...overrides, + format: overrides.format ?? "mp3", }); const audioMetadataRuntime = makeAudioRuntime(AudioMetadataIOLive); @@ -144,6 +145,7 @@ describe("AudioMetadataIO", () => { ]); expect(upload.file.status).toBe("pending"); + expect(upload.file.format).toBe("mp3"); expect(upload.file.metadata).toMatchObject({ filename: "artist.track", title: "Track Title", @@ -181,6 +183,28 @@ describe("AudioMetadataIO", () => { ]); }); + it("uses content and extension for admission while treating browser MIME as advisory", async () => { + const bytes = validMp3Bytes(); + const uploads = await parseUploadedTracks([ + new File([bytes], "mime-lied.mp3", { type: "audio/wav" }), + new File([bytes], "extension-lied.wav", { type: "audio/mpeg" }), + ]); + + expect(uploads[0]?.file).toMatchObject({ + format: "mp3", + filename: "mime-lied.mp3", + status: "pending", + }); + expect(uploads[0]?.file.file?.type).toBe("audio/mpeg"); + expect(uploads[1]?.file).toMatchObject({ + format: "mp3", + filename: "extension-lied.wav", + status: "error", + downloadError: + "extension-lied.wav is not an mp3. tagium currently supports mp3 files only.", + }); + }); + it("normalizes missing numeric tags to null in parsed metadata snapshots", async () => { const originalTags = structuredClone(mp3tagMock.nextTags); const nextTags = mp3tagMock.nextTags as Partial; @@ -270,4 +294,15 @@ describe("AudioMetadataIO", () => { "audio file is still downloading.", ); }); + + it("preserves the current filename when a write does not request a rename", async () => { + const current = new File([validMp3Bytes()], "Archive.MP3", { type: "audio/mpeg" }); + const updatedFile = await writeMetadataToFile( + tagiumFile({ file: current, filename: current.name }), + metadata({ filename: "" }), + ); + + expect(updatedFile.name).toBe("Archive.MP3"); + expect(updatedFile.type).toBe("audio/mpeg"); + }); }); diff --git a/tests/unit/features/audio/mp3Utils.test.ts b/tests/unit/features/audio/mp3Utils.test.ts index ab8160b6..64318277 100644 --- a/tests/unit/features/audio/mp3Utils.test.ts +++ b/tests/unit/features/audio/mp3Utils.test.ts @@ -24,6 +24,7 @@ const metadata = (trackNumber?: number): AudioMetadata => ({ const upload = (id: string, trackNumber?: number): UploadedTrack => ({ file: { id, + format: "mp3", file: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), originalFile: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), filename: `${id}.mp3`, @@ -42,6 +43,7 @@ const upload = (id: string, trackNumber?: number): UploadedTrack => ({ const failedUpload = (id: string): UploadedTrack => ({ file: { id, + format: "mp3", file: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), originalFile: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), filename: `${id}.mp3`, diff --git a/tests/unit/features/editor/albumDialogState.test.ts b/tests/unit/features/editor/albumDialogState.test.ts index 0363886f..ea9bacd2 100644 --- a/tests/unit/features/editor/albumDialogState.test.ts +++ b/tests/unit/features/editor/albumDialogState.test.ts @@ -40,6 +40,7 @@ const file = (id: string, overrides: Partial = {}): TagiumFile => ({ downloadStatus: "ready", metadata: metadata(), ...overrides, + format: overrides.format ?? "mp3", }); const album = (overrides: Partial = {}): AlbumGroup => ({ diff --git a/tests/unit/features/editor/audioTagger.test.ts b/tests/unit/features/editor/audioTagger.test.ts index 1aeff508..3aa6b99c 100644 --- a/tests/unit/features/editor/audioTagger.test.ts +++ b/tests/unit/features/editor/audioTagger.test.ts @@ -31,6 +31,7 @@ describe("audioTagger metadata patches", () => { const accepted = { file: { id: "accepted", + format: "mp3", filename: "accepted.mp3", status: "pending", downloadStatus: "ready", @@ -40,6 +41,7 @@ describe("audioTagger metadata patches", () => { const rejected = { file: { id: "rejected", + format: "mp3", filename: "rejected.mp3", status: "error", downloadStatus: "ready", @@ -60,6 +62,7 @@ describe("audioTagger metadata patches", () => { ({ file: { id: `rejected-${index}`, + format: "mp3", filename: index === 0 ? "empty.mp3" : "song.wav", status: "error", downloadStatus: "ready", @@ -82,6 +85,7 @@ describe("audioTagger metadata patches", () => { expect( getTagiumFileImportKey({ id: "track", + format: "mp3", filename: edited.name, file: edited, originalFile: edited, @@ -182,9 +186,16 @@ describe("audioTagger metadata patches", () => { it("summarizes removed track sources without exposing their URLs", () => { expect( getTrackSourceMix([ - { id: "local", filename: "local.mp3", status: "saved", downloadStatus: "ready" }, + { + id: "local", + format: "mp3", + filename: "local.mp3", + status: "saved", + downloadStatus: "ready", + }, { id: "imported", + format: "mp3", filename: "imported.mp3", status: "saved", downloadStatus: "ready", diff --git a/tests/unit/features/editor/trackMetadataEditor.test.tsx b/tests/unit/features/editor/trackMetadataEditor.test.tsx index 43d79df9..2e784a9b 100644 --- a/tests/unit/features/editor/trackMetadataEditor.test.tsx +++ b/tests/unit/features/editor/trackMetadataEditor.test.tsx @@ -21,6 +21,7 @@ const metadata: AudioMetadata = { const loadedTrack: TagiumFile = { id: "track-1", + format: "mp3", filename: "track-1.mp3", status: "saved", downloadStatus: "ready", diff --git a/tests/unit/features/editor/useTrackEditorSession.test.ts b/tests/unit/features/editor/useTrackEditorSession.test.ts index 29d0690e..63e9b337 100644 --- a/tests/unit/features/editor/useTrackEditorSession.test.ts +++ b/tests/unit/features/editor/useTrackEditorSession.test.ts @@ -30,6 +30,7 @@ const readyFile = (id: string, title: string): TagiumFile => { const file = new File([id], `${id}.mp3`); return { id, + format: "mp3", filename: file.name, file, originalFile: file, @@ -93,6 +94,7 @@ describe("track editor session", () => { }, undefined); const pending: TagiumFile = { id: "remote", + format: "mp3", filename: "edited.mp3", status: "pending", downloadStatus: "downloading", @@ -111,6 +113,7 @@ describe("track editor session", () => { const downloaded = new File(["download"], "downloaded.mp3"); const parsedFile: TagiumFile = { id: "parsed", + format: "mp3", filename: downloaded.name, file: downloaded, originalFile: downloaded, diff --git a/tests/unit/features/export/downloadLibrary.test.ts b/tests/unit/features/export/downloadLibrary.test.ts index f2b3c2d3..be988285 100644 --- a/tests/unit/features/export/downloadLibrary.test.ts +++ b/tests/unit/features/export/downloadLibrary.test.ts @@ -24,6 +24,7 @@ const metadata = (filename: string): AudioMetadata => ({ const file = (id: string, filename: string, contents = id): TagiumFile => ({ id, + format: "mp3", filename, file: new File([contents], filename, { type: "audio/mpeg" }), originalFile: new File([contents], filename, { type: "audio/mpeg" }), @@ -35,6 +36,7 @@ const file = (id: string, filename: string, contents = id): TagiumFile => ({ const missingFile = (id: string, filename: string): TagiumFile => ({ id, + format: "mp3", filename, status: "pending", downloadStatus: "downloading", @@ -43,6 +45,7 @@ const missingFile = (id: string, filename: string): TagiumFile => ({ const missingMetadata = (id: string, filename: string): TagiumFile => ({ id, + format: "mp3", filename, file: new File([id], filename, { type: "audio/mpeg" }), originalFile: new File([id], filename, { type: "audio/mpeg" }), diff --git a/tests/unit/features/export/exportMetadataWrites.test.ts b/tests/unit/features/export/exportMetadataWrites.test.ts index cdbc1d23..ab9f5857 100644 --- a/tests/unit/features/export/exportMetadataWrites.test.ts +++ b/tests/unit/features/export/exportMetadataWrites.test.ts @@ -18,6 +18,7 @@ const metadata = (title: string): AudioMetadata => ({ const readyFile = (id: string): TagiumFile => ({ id, + format: "mp3", file: new File([id], `${id}.mp3`, { type: "audio/mpeg" }), filename: `${id}.mp3`, status: "pending", diff --git a/tests/unit/features/export/useExportSession.test.ts b/tests/unit/features/export/useExportSession.test.ts index 77d0aaaf..65c26548 100644 --- a/tests/unit/features/export/useExportSession.test.ts +++ b/tests/unit/features/export/useExportSession.test.ts @@ -52,6 +52,7 @@ describe("export session", () => { it("keeps the interface busy until a failed export is routed through cleanup", async () => { const file: TagiumFile = { id: "track-1", + format: "mp3", filename: "track.mp3", file: new File(["audio"], "track.mp3"), originalFile: new File(["audio"], "track.mp3"), diff --git a/tests/unit/features/import/importQueuePresentation.test.ts b/tests/unit/features/import/importQueuePresentation.test.ts index 2d665302..49044905 100644 --- a/tests/unit/features/import/importQueuePresentation.test.ts +++ b/tests/unit/features/import/importQueuePresentation.test.ts @@ -22,6 +22,7 @@ const snapshot = ( }); const retryableFile = (id: string): TagiumFile => ({ id, + format: "mp3", filename: `${id}.mp3`, status: "error", downloadStatus: "error", diff --git a/tests/unit/features/library/albumOps.test.ts b/tests/unit/features/library/albumOps.test.ts index 343aafaf..0db041ca 100644 --- a/tests/unit/features/library/albumOps.test.ts +++ b/tests/unit/features/library/albumOps.test.ts @@ -28,6 +28,7 @@ const upload = ( ): UploadedTrack => ({ file: { id, + format: "mp3", file: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), originalFile: new File(["a"], `${id}.mp3`, { type: "audio/mpeg" }), filename: `${id}.mp3`, diff --git a/tests/unit/features/library/fileMetadataOps.test.ts b/tests/unit/features/library/fileMetadataOps.test.ts index 97c94a18..cf212adb 100644 --- a/tests/unit/features/library/fileMetadataOps.test.ts +++ b/tests/unit/features/library/fileMetadataOps.test.ts @@ -37,6 +37,7 @@ const readyFile = (overrides: Partial = {}): TagiumFile => ({ hasBufferedChanges: false, metadata: metadata({ filename: "track-1", title: "Track 1" }), ...overrides, + format: overrides.format ?? "mp3", }); describe("fileMetadataOps", () => { @@ -44,6 +45,7 @@ describe("fileMetadataOps", () => { const files = [ { id: "track-1", + format: "mp3" as const, file: new File(["a"], "track-1.mp3", { type: "audio/mpeg" }), originalFile: new File(["a"], "track-1.mp3", { type: "audio/mpeg" }), filename: "track-1.mp3", @@ -66,6 +68,7 @@ describe("fileMetadataOps", () => { }, { id: "track-2", + format: "mp3" as const, file: new File(["b"], "track-2.mp3", { type: "audio/mpeg" }), originalFile: new File(["b"], "track-2.mp3", { type: "audio/mpeg" }), filename: "track-2.mp3", @@ -121,6 +124,7 @@ describe("fileMetadataOps", () => { const files = [ { id: "track-1", + format: "mp3" as const, file: new File(["a"], "track-1.mp3", { type: "audio/mpeg" }), originalFile: new File(["a"], "track-1.mp3", { type: "audio/mpeg" }), filename: "track-1.mp3", diff --git a/tests/unit/features/library/libraryState.test.ts b/tests/unit/features/library/libraryState.test.ts index 881d8b4c..61554e00 100644 --- a/tests/unit/features/library/libraryState.test.ts +++ b/tests/unit/features/library/libraryState.test.ts @@ -9,6 +9,7 @@ import type { AlbumGroup, TagiumFile } from "@/features/library/types"; const file = (id: string): TagiumFile => ({ id, + format: "mp3", filename: `${id}.mp3`, status: "saved", downloadStatus: "ready", diff --git a/tests/unit/features/library/metadataCleanup.test.ts b/tests/unit/features/library/metadataCleanup.test.ts index d88dac40..38204948 100644 --- a/tests/unit/features/library/metadataCleanup.test.ts +++ b/tests/unit/features/library/metadataCleanup.test.ts @@ -23,6 +23,7 @@ const metadata = (title: string, artist = "Burial"): AudioMetadata => ({ const file = (title: string): TagiumFile => ({ id: "track-1", + format: "mp3", status: "saved", downloadStatus: "ready", filename: `${title}.mp3`, diff --git a/tests/unit/features/library/useLibraryStore.test.ts b/tests/unit/features/library/useLibraryStore.test.ts index 4811bfa2..cda1bd2c 100644 --- a/tests/unit/features/library/useLibraryStore.test.ts +++ b/tests/unit/features/library/useLibraryStore.test.ts @@ -9,12 +9,14 @@ describe("library store", () => { const store = hook.result; const first = { id: "first", + format: "mp3" as const, filename: "first.mp3", status: "saved" as const, downloadStatus: "ready" as const, }; const second = { id: "second", + format: "mp3" as const, filename: "second.mp3", status: "saved" as const, downloadStatus: "ready" as const, diff --git a/tests/unit/features/workspace/useAudioImportSession.test.ts b/tests/unit/features/workspace/useAudioImportSession.test.ts index 2f60926f..042f193f 100644 --- a/tests/unit/features/workspace/useAudioImportSession.test.ts +++ b/tests/unit/features/workspace/useAudioImportSession.test.ts @@ -48,6 +48,7 @@ const createLibrary = (): LibraryStore => { const parsedUpload = (file: File) => ({ file: { id: "track-1", + format: "mp3" as const, filename: file.name, file, originalFile: file, diff --git a/tests/unit/features/workspace/useAudioWorkspace.test.tsx b/tests/unit/features/workspace/useAudioWorkspace.test.tsx index 1fde9dcf..6f456340 100644 --- a/tests/unit/features/workspace/useAudioWorkspace.test.tsx +++ b/tests/unit/features/workspace/useAudioWorkspace.test.tsx @@ -38,6 +38,7 @@ const readyFile = (id: string, title: string): TagiumFile => { const file = new File([id], `${id}.mp3`); return { id, + format: "mp3", filename: file.name, file, originalFile: file, From 3e2721e5bcd0b873f051f14d1cf82a9435939457 Mon Sep 17 00:00:00 2001 From: flamboh Date: Fri, 17 Jul 2026 05:23:17 -0800 Subject: [PATCH 2/2] style(audio): apply project formatting --- src/features/audio/audioFormat.ts | 3 +-- src/features/import/downloadTrack.ts | 4 +--- tests/unit/features/audio/audioMetadataIO.test.ts | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/features/audio/audioFormat.ts b/src/features/audio/audioFormat.ts index 419aa40d..d8fcd356 100644 --- a/src/features/audio/audioFormat.ts +++ b/src/features/audio/audioFormat.ts @@ -14,8 +14,7 @@ const audioFormatInfo = { }, } as const satisfies Record; -export const getAudioFormatInfo = (format: AudioFormat): AudioFormatInfo => - audioFormatInfo[format]; +export const getAudioFormatInfo = (format: AudioFormat): AudioFormatInfo => audioFormatInfo[format]; export const getAudioUploadAccept = () => Object.values(audioFormatInfo) diff --git a/src/features/import/downloadTrack.ts b/src/features/import/downloadTrack.ts index fd8c3954..78a83063 100644 --- a/src/features/import/downloadTrack.ts +++ b/src/features/import/downloadTrack.ts @@ -229,9 +229,7 @@ export const fetchImportedCover = async ( export const createQueuedDownloadTrack = (file: PendingDownloadTrack): QueuedDownloadTrack => ({ fileId: file.id, title: - file.metadata.title || - withoutAudioExtension(file.filename, file.format) || - "downloading audio", + file.metadata.title || withoutAudioExtension(file.filename, file.format) || "downloading audio", downloadRequest: file.downloadRequest, }); diff --git a/tests/unit/features/audio/audioMetadataIO.test.ts b/tests/unit/features/audio/audioMetadataIO.test.ts index 67c9443d..69300696 100644 --- a/tests/unit/features/audio/audioMetadataIO.test.ts +++ b/tests/unit/features/audio/audioMetadataIO.test.ts @@ -200,8 +200,7 @@ describe("AudioMetadataIO", () => { format: "mp3", filename: "extension-lied.wav", status: "error", - downloadError: - "extension-lied.wav is not an mp3. tagium currently supports mp3 files only.", + downloadError: "extension-lied.wav is not an mp3. tagium currently supports mp3 files only.", }); });