diff --git a/README.md b/README.md index 1e43ba1..f450b69 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ From there you can: - turn notifications on or off - choose success and failure sounds - preview sounds -- upload a custom sound +- upload a sound - adjust volume - choose whether notifications fire on every prompt or only when the queue finishes @@ -51,7 +51,7 @@ Defaults: - success: `ping-success.wav` - failure: `ping-failure.wav` -## Custom sounds +## Uploads Supported formats: @@ -65,4 +65,6 @@ Maximum upload size: - `10 MiB` -If a custom sound is missing or unreadable, `comfyui-ping` logs a warning and stays silent. +If a selected sound is missing or unreadable, `comfyui-ping` logs a warning and stays silent. +Uploaded sounds are stored alongside the bundled sounds in `sounds/`. +Older installs with `sounds/bundled`, `sounds/custom`, or `input/ping` are migrated into `sounds/` automatically. diff --git a/comfyui_ping/notifications.py b/comfyui_ping/notifications.py index 5cfea05..ae35c4f 100644 --- a/comfyui_ping/notifications.py +++ b/comfyui_ping/notifications.py @@ -33,8 +33,8 @@ def normalize_sound_id(sound_id: str | None) -> str | None: if sound_id is None or sound_id == "": return None if sound_id.startswith(SOUND_PREFIXES): - return sound_id - return f"bundled:{sound_id}" + return sound_id.split(":", 1)[1] + return sound_id def build_notification_payload( diff --git a/comfyui_ping/routes.py b/comfyui_ping/routes.py index f3dbb03..de7d515 100644 --- a/comfyui_ping/routes.py +++ b/comfyui_ping/routes.py @@ -3,20 +3,18 @@ from .runtime import get_runtime_settings, update_runtime_settings from .sounds import ( - BUNDLED_SOUNDS_DIR, - CUSTOM_SOUNDS_DIR, - LEGACY_CUSTOM_SOUNDS_DIR, + LEGACY_INPUT_SOUNDS_DIR, MAX_UPLOAD_BYTES, - ensure_custom_sound_storage, + SOUNDS_DIR, + ensure_sound_storage, is_allowed_upload, list_available_sounds, - list_bundled_sounds, - list_custom_sounds, + list_sound_files, ) SOUND_CATALOG_ROUTE = "/comfyui-ping/sounds" SOUND_UPLOAD_ROUTE = "/comfyui-ping/sounds/upload" -SOUND_FILE_ROUTE = "/comfyui-ping/sounds/{source}/{filename}" +SOUND_FILE_ROUTE = "/comfyui-ping/sounds/{filename}" SETTINGS_ROUTE = "/comfyui-ping/settings" _SOUND_ROUTES_REGISTERED = False @@ -28,48 +26,32 @@ def post(self, path: str): ... def build_sound_catalog_payload( - *, - bundled: list[str], - custom: list[str], + sounds: list[str], ) -> dict[str, list[dict[str, str]]]: - return { - "sounds": list_available_sounds( - bundled=bundled, - custom=custom, - ) - } + return {"sounds": list_available_sounds(sounds)} def resolve_sound_path( *, filename: str, - source: str, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> Path | None: safe_name = Path(filename).name - if source == "bundled": - candidate = bundled_dir / safe_name - return candidate if candidate.is_file() else None - if source == "custom": - ensure_custom_sound_storage( - custom_dir=custom_dir, - legacy_custom_dir=legacy_custom_dir, - bundled_dir=bundled_dir, - ) - candidate = custom_dir / safe_name - return candidate if candidate.is_file() else None - return None + ensure_sound_storage( + sounds_dir=sounds_dir, + legacy_custom_dir=legacy_custom_dir, + ) + candidate = sounds_dir / safe_name + return candidate if candidate.is_file() else None def save_uploaded_sound( *, filename: str, data: bytes, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> Path: if not is_allowed_upload(filename): raise ValueError("Invalid audio file format") @@ -77,35 +59,26 @@ def save_uploaded_sound( raise ValueError("Sound file exceeds 10 MiB limit") safe_name = Path(filename).name - if (bundled_dir / safe_name).is_file(): - raise ValueError("Filename collides with bundled sound") - - ensure_custom_sound_storage( - custom_dir=custom_dir, + ensure_sound_storage( + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, - bundled_dir=bundled_dir, ) - target_path = custom_dir / safe_name + target_path = sounds_dir / safe_name if target_path.is_file(): - raise ValueError("Filename already exists in custom sounds") + raise ValueError("Filename already exists") target_path.write_bytes(data) return target_path def list_sounds_route_payload( - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> dict[str, list[dict[str, str]]]: - ensure_custom_sound_storage( - custom_dir=custom_dir, + ensure_sound_storage( + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, - bundled_dir=bundled_dir, - ) - return build_sound_catalog_payload( - bundled=list_bundled_sounds(bundled_dir), - custom=list_custom_sounds(custom_dir), ) + return build_sound_catalog_payload(list_sound_files(sounds_dir)) def runtime_settings_route_payload() -> dict[str, object]: @@ -114,16 +87,12 @@ def runtime_settings_route_payload() -> dict[str, object]: def serve_sound_route_path( filename: str, - source: str, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> Path | None: return resolve_sound_path( filename=filename, - source=source, - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) @@ -132,16 +101,14 @@ def attach_sound_routes( *, routes: RouteRegistrar, web_module: object, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> None: @routes.get(SOUND_CATALOG_ROUTE) async def list_sounds(_request): return web_module.json_response( list_sounds_route_payload( - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) ) @@ -165,9 +132,7 @@ async def update_runtime_settings_route(request): async def serve_sound(request): path = serve_sound_route_path( filename=request.match_info.get("filename", ""), - source=request.match_info.get("source", ""), - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) if path is None: @@ -195,8 +160,7 @@ async def upload_sound(request): save_uploaded_sound( filename=filename, data=data, - custom_dir=custom_dir, - bundled_dir=bundled_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) except ValueError as exc: @@ -207,8 +171,7 @@ async def upload_sound(request): return web_module.json_response( list_sounds_route_payload( - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ), status=201, @@ -217,9 +180,8 @@ async def upload_sound(request): def register_sound_routes( *, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> bool: global _SOUND_ROUTES_REGISTERED @@ -240,8 +202,7 @@ def register_sound_routes( attach_sound_routes( routes=routes, web_module=web, - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) _SOUND_ROUTES_REGISTERED = True diff --git a/comfyui_ping/runtime.py b/comfyui_ping/runtime.py index 4cafcde..bb83547 100644 --- a/comfyui_ping/runtime.py +++ b/comfyui_ping/runtime.py @@ -19,8 +19,8 @@ class RuntimeSettings(TypedDict): "notify_mode": "queue_drained", "success_enabled": True, "failure_enabled": True, - "success_sound": "bundled:ping-success.wav", - "failure_sound": "bundled:ping-failure.wav", + "success_sound": "ping-success.wav", + "failure_sound": "ping-failure.wav", "volume": 0.8, } diff --git a/comfyui_ping/sounds.py b/comfyui_ping/sounds.py index 8c972e6..5ebb0fc 100644 --- a/comfyui_ping/sounds.py +++ b/comfyui_ping/sounds.py @@ -1,41 +1,27 @@ from collections.abc import Iterable from pathlib import Path -from typing import Literal, TypedDict +from typing import TypedDict REPO_ROOT = Path(__file__).resolve().parents[1] SOUNDS_DIR = REPO_ROOT / "sounds" -BUNDLED_SOUNDS_DIR = SOUNDS_DIR / "bundled" -CUSTOM_SOUNDS_DIR = SOUNDS_DIR / "custom" -LEGACY_CUSTOM_SOUNDS_DIR = REPO_ROOT / "input" / "ping" +LEGACY_BUNDLED_SOUNDS_DIR = SOUNDS_DIR / "bundled" +LEGACY_CUSTOM_SOUNDS_DIR = SOUNDS_DIR / "custom" +LEGACY_INPUT_SOUNDS_DIR = REPO_ROOT / "input" / "ping" ALLOWED_EXTENSIONS = {".wav", ".mp3", ".ogg", ".m4a", ".flac"} MAX_UPLOAD_BYTES = 10 * 1024 * 1024 class SoundCatalogEntry(TypedDict): name: str - source: Literal["bundled", "custom"] def is_allowed_upload(filename: str) -> bool: return Path(filename).suffix.lower() in ALLOWED_EXTENSIONS -def list_available_sounds( - *, - bundled: Iterable[str], - custom: Iterable[str], -) -> list[SoundCatalogEntry]: - return [ - *( - {"name": name, "source": "bundled"} - for name in bundled - ), - *( - {"name": name, "source": "custom"} - for name in custom - ), - ] +def list_available_sounds(sounds: Iterable[str]) -> list[SoundCatalogEntry]: + return [{"name": name} for name in sounds] def list_sound_files(directory: Path) -> list[str]: @@ -49,36 +35,29 @@ def list_sound_files(directory: Path) -> list[str]: ) -def list_bundled_sounds(bundled_dir: Path = BUNDLED_SOUNDS_DIR) -> list[str]: - return list_sound_files(bundled_dir) - - -def list_custom_sounds(custom_dir: Path = CUSTOM_SOUNDS_DIR) -> list[str]: - return list_sound_files(custom_dir) - - -def ensure_custom_sound_storage( - custom_dir: Path = CUSTOM_SOUNDS_DIR, - legacy_custom_dir: Path = LEGACY_CUSTOM_SOUNDS_DIR, - bundled_dir: Path = BUNDLED_SOUNDS_DIR, +def ensure_sound_storage( + sounds_dir: Path = SOUNDS_DIR, + legacy_custom_dir: Path = LEGACY_INPUT_SOUNDS_DIR, ) -> Path: - custom_dir.mkdir(parents=True, exist_ok=True) - if not legacy_custom_dir.exists(): - return custom_dir - - for legacy_file in legacy_custom_dir.iterdir(): - if not legacy_file.is_file() or not is_allowed_upload(legacy_file.name): + sounds_dir.mkdir(parents=True, exist_ok=True) + + for legacy_dir in ( + LEGACY_BUNDLED_SOUNDS_DIR, + LEGACY_CUSTOM_SOUNDS_DIR, + legacy_custom_dir, + ): + if legacy_dir == sounds_dir or not legacy_dir.exists(): continue - if (bundled_dir / legacy_file.name).exists(): - legacy_file.unlink(missing_ok=True) - continue + for legacy_file in legacy_dir.iterdir(): + if not legacy_file.is_file() or not is_allowed_upload(legacy_file.name): + continue - target_path = custom_dir / legacy_file.name - if target_path.exists(): - legacy_file.unlink(missing_ok=True) - continue + target_path = sounds_dir / legacy_file.name + if target_path.exists(): + legacy_file.unlink(missing_ok=True) + continue - legacy_file.replace(target_path) + legacy_file.replace(target_path) - return custom_dir + return sounds_dir diff --git a/sounds/bundled/beep-ping.wav b/sounds/beep-ping.wav similarity index 100% rename from sounds/bundled/beep-ping.wav rename to sounds/beep-ping.wav diff --git a/sounds/bundled/harmonic-beep.wav b/sounds/harmonic-beep.wav similarity index 100% rename from sounds/bundled/harmonic-beep.wav rename to sounds/harmonic-beep.wav diff --git a/sounds/bundled/notification-soft.wav b/sounds/notification-soft.wav similarity index 100% rename from sounds/bundled/notification-soft.wav rename to sounds/notification-soft.wav diff --git a/sounds/bundled/ping-failure.wav b/sounds/ping-failure.wav similarity index 100% rename from sounds/bundled/ping-failure.wav rename to sounds/ping-failure.wav diff --git a/sounds/bundled/ping-ringtone.wav b/sounds/ping-ringtone.wav similarity index 100% rename from sounds/bundled/ping-ringtone.wav rename to sounds/ping-ringtone.wav diff --git a/sounds/bundled/ping-success.wav b/sounds/ping-success.wav similarity index 100% rename from sounds/bundled/ping-success.wav rename to sounds/ping-success.wav diff --git a/src/api.ts b/src/api.ts index cfac466..36104a8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,22 +1,16 @@ -import { SOUND_SOURCES } from "./constants"; - -export type SoundSource = - (typeof SOUND_SOURCES)[keyof typeof SOUND_SOURCES]; - export interface FetchApiClient { fetchApi: (route: string, options?: RequestInit) => Promise; } export interface SoundCatalogEntry { name: string; - source: SoundSource; } export interface SoundCatalogPayload { sounds: SoundCatalogEntry[]; } -export type SoundOptionId = `${SoundSource}:${string}`; +export type SoundOptionId = string; export interface RuntimeNotificationSettings { enabled: boolean; notify_mode: "every_prompt" | "queue_drained"; @@ -32,7 +26,7 @@ export const SOUND_UPLOAD_ROUTE = "/comfyui-ping/sounds/upload"; export const SETTINGS_ROUTE = "/comfyui-ping/settings"; export function toSoundOptionId(entry: SoundCatalogEntry): SoundOptionId { - return `${entry.source}:${entry.name}`; + return entry.name; } export function normalizeSoundOptionId( @@ -42,28 +36,21 @@ export function normalizeSoundOptionId( return null; } - if (soundId.includes(":")) { - return toSoundOptionId(parseSoundOptionId(soundId)); + if (soundId.startsWith("bundled:") || soundId.startsWith("custom:")) { + return soundId.split(":")[1] ?? null; } - return `bundled:${soundId}`; + return soundId; } export function parseSoundOptionId(soundId: string): SoundCatalogEntry { - const separatorIndex = soundId.indexOf(":"); - const source = soundId.slice(0, separatorIndex); - const name = soundId.slice(separatorIndex + 1); - - if ( - source !== SOUND_SOURCES.BUNDLED && - source !== SOUND_SOURCES.CUSTOM - ) { - throw new Error(`Unsupported sound source: ${source}`); + const normalizedSoundId = normalizeSoundOptionId(soundId); + if (!normalizedSoundId) { + throw new Error("Missing sound option id"); } return { - name, - source, + name: normalizedSoundId, }; } @@ -80,31 +67,25 @@ export function buildSettingsRoute(): string { } export function buildSoundFileRoute(entry: SoundCatalogEntry): string { - return `${SOUND_CATALOG_ROUTE}/${encodeURIComponent(entry.source)}/${encodeURIComponent(entry.name)}`; + return `${SOUND_CATALOG_ROUTE}/${encodeURIComponent(entry.name)}`; } export function parseSoundCatalogPayload(input: { sounds?: Array<{ name?: string; - source?: string; }>; }): SoundCatalogPayload { const sounds = input.sounds ?? []; return { sounds: sounds.flatMap((entry) => { - if ( - typeof entry.name !== "string" || - (entry.source !== SOUND_SOURCES.BUNDLED && - entry.source !== SOUND_SOURCES.CUSTOM) - ) { + if (typeof entry.name !== "string") { return []; } return [ { name: entry.name, - source: entry.source, }, ]; }), @@ -121,7 +102,6 @@ async function parseCatalogResponse( const payload: { sounds?: Array<{ name?: string; - source?: string; }>; } = await response.json(); diff --git a/src/audio.ts b/src/audio.ts index 32abcdf..30661a4 100644 --- a/src/audio.ts +++ b/src/audio.ts @@ -20,7 +20,6 @@ export interface NotificationLogger { export interface ShouldPlayNotificationInput { selectedSound: SoundOptionId | string | null; soundExists: boolean; - isCustom: boolean; } export interface PlayNotificationInput extends ShouldPlayNotificationInput { @@ -53,8 +52,7 @@ export function catalogHasSound( } return catalog.some( - (catalogEntry) => - catalogEntry.name === entry.name && catalogEntry.source === entry.source + (catalogEntry) => catalogEntry.name === entry.name ); } @@ -76,11 +74,7 @@ export function shouldPlayNotification( return false; } - if (input.isCustom && !input.soundExists) { - return false; - } - - return input.soundExists || !input.isCustom; + return input.soundExists; } export async function playNotification( @@ -92,9 +86,9 @@ export async function playNotification( input.currentAudio.currentTime = 0; } - if (input.isCustom && input.selectedSound && !input.soundExists) { + if (input.selectedSound && !input.soundExists) { input.logger?.warn( - `Selected custom sound '${input.selectedSound}' is unavailable; skipping playback.` + `Selected sound '${input.selectedSound}' is unavailable; skipping playback.` ); } @@ -142,7 +136,6 @@ export async function handleNotificationEvent(input: { }): Promise { const selectedSound = normalizeSoundOptionId(input.detail.sound_id); const parsedSound = selectedSound ? parseSoundOptionId(selectedSound) : null; - const isCustom = parsedSound?.source === "custom"; const audioUrl = resolveNotificationAudioUrl(selectedSound); const soundExists = parsedSound ? catalogHasSound(input.catalog, parsedSound) @@ -152,7 +145,6 @@ export async function handleNotificationEvent(input: { audioUrl, createAudio: input.createAudio, currentAudio: input.currentAudio, - isCustom, logger: input.logger, selectedSound, soundExists, diff --git a/src/index.ts b/src/index.ts index 119d04a..f0c0bec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { } from "./audio"; import { fetchSoundCatalog, + normalizeSoundOptionId, storeRuntimeSettings, toSoundOptionId, uploadSoundFile, @@ -83,17 +84,17 @@ interface PingLogger { warn: (message: string) => void; } -export const DEFAULT_SUCCESS_SOUND_ID = "bundled:ping-success.wav"; -export const DEFAULT_FAILURE_SOUND_ID = "bundled:ping-failure.wav"; +export const DEFAULT_SUCCESS_SOUND_ID = "ping-success.wav"; +export const DEFAULT_FAILURE_SOUND_ID = "ping-failure.wav"; export const PING_EVENT_NAME = "comfyui-ping.notification"; const DEFAULT_SOUND_CATALOG: SoundCatalogPayload = { sounds: [ - { name: "beep-ping.wav", source: "bundled" }, - { name: "harmonic-beep.wav", source: "bundled" }, - { name: "notification-soft.wav", source: "bundled" }, - { name: "ping-success.wav", source: "bundled" }, - { name: "ping-failure.wav", source: "bundled" }, - { name: "ping-ringtone.wav", source: "bundled" }, + { name: "beep-ping.wav" }, + { name: "harmonic-beep.wav" }, + { name: "notification-soft.wav" }, + { name: "ping-success.wav" }, + { name: "ping-failure.wav" }, + { name: "ping-ringtone.wav" }, ], }; @@ -108,7 +109,7 @@ export const PING_SETTINGS_IDS = { SUCCESS_SOUND: `${SETTINGS_PREFIX}.Success Sound`, FAILURE_SOUND: `${SETTINGS_PREFIX}.Failure Sound`, VOLUME: `${SETTINGS_PREFIX}.Notification Volume`, - UPLOAD_ACTION: `${SETTINGS_PREFIX}.Upload Custom Sound`, + UPLOAD_ACTION: `${SETTINGS_PREFIX}.Upload Sound`, SECTION_ADVANCED: `${SETTINGS_PREFIX}.Advanced`, DEBUG_LOGGING: SETTINGS_IDS.DEBUG_LOGGING, } as const; @@ -160,7 +161,11 @@ function coerceSettingString( value: unknown, fallbackValue: SoundOptionId, ): string { - return typeof value === "string" && value.length > 0 ? value : fallbackValue; + if (typeof value !== "string" || value.length === 0) { + return fallbackValue; + } + + return normalizeSoundOptionId(value) ?? fallbackValue; } function coerceSettingBoolean(value: unknown, fallbackValue: boolean): boolean { @@ -179,22 +184,17 @@ export function buildSoundSettingOptions( catalog: SoundCatalogPayload, ): PingSettingOption[] { return catalog.sounds.map((entry) => ({ - text: `${entry.source === "bundled" ? "Bundled" : "Custom"} / ${entry.name}`, + text: entry.name, value: toSoundOptionId(entry), })); } function buildUnavailableSoundOption(soundId: string): PingSettingOption { - const separatorIndex = soundId.indexOf(":"); - const source = separatorIndex === -1 ? "" : soundId.slice(0, separatorIndex); - const name = - separatorIndex === -1 ? soundId : soundId.slice(separatorIndex + 1); - const sourceLabel = - source === "custom" ? "Custom" : source === "bundled" ? "Bundled" : "Sound"; + const name = normalizeSoundOptionId(soundId) ?? soundId; return { - text: `${sourceLabel} / ${name} (unavailable)`, - value: soundId, + text: `${name} (unavailable)`, + value: name, }; } @@ -202,15 +202,16 @@ export function buildRenderedSoundOptions( options: PingSettingOption[], selectedValue: string, ): PingSettingOption[] { - if (selectedValue.length === 0) { + const normalizedSelectedValue = normalizeSoundOptionId(selectedValue) ?? ""; + if (normalizedSelectedValue.length === 0) { return options; } - if (options.some((option) => option.value === selectedValue)) { + if (options.some((option) => option.value === normalizedSelectedValue)) { return options; } - return [buildUnavailableSoundOption(selectedValue), ...options]; + return [buildUnavailableSoundOption(normalizedSelectedValue), ...options]; } function findSetting(settingId: string): PingSetting | undefined { @@ -263,19 +264,23 @@ function populateSoundSelect( options: PingSettingOption[], selectedValue: string, ): void { - const renderedOptions = buildRenderedSoundOptions(options, selectedValue); + const normalizedSelectedValue = normalizeSoundOptionId(selectedValue) ?? ""; + const renderedOptions = buildRenderedSoundOptions( + options, + normalizedSelectedValue, + ); selectEl.replaceChildren(); for (const option of renderedOptions) { const optionEl = document.createElement("option"); optionEl.textContent = option.text; optionEl.value = option.value; - optionEl.selected = option.value === selectedValue; + optionEl.selected = option.value === normalizedSelectedValue; selectEl.append(optionEl); } - if (selectedValue.length > 0) { - selectEl.value = selectedValue; + if (normalizedSelectedValue.length > 0) { + selectEl.value = normalizedSelectedValue; } else if (selectEl.options.length > 0 && selectEl.selectedIndex === -1) { selectEl.value = selectEl.options[0].value; } @@ -484,7 +489,7 @@ export async function tryUploadCustomSound( return await uploadCustomSound(comfyApp, file); } catch (error) { logger.warn( - `Unable to upload custom sound: ${ + `Unable to upload sound: ${ error instanceof Error ? error.message : String(error) }`, ); @@ -513,7 +518,7 @@ function createSettingsSyncOnChange(): ( function createUploadActionRenderer(): HTMLButtonElement { const button = document.createElement("button"); button.type = "button"; - button.textContent = "Upload Custom Sound"; + button.textContent = "Upload Sound"; button.addEventListener("click", () => { const runtimeApp = getRuntimeApp(); if (!runtimeApp?.api) { @@ -636,10 +641,10 @@ export const PING_SETTINGS: PingSetting[] = [ }, { id: PING_SETTINGS_IDS.UPLOAD_ACTION, - name: "Upload Custom Sound", + name: "Upload Sound", type: createUploadActionRenderer, defaultValue: undefined, - tooltip: "Upload a custom browser-played notification sound.", + tooltip: "Upload a browser-played notification sound.", sortOrder: 2, }, { diff --git a/src/resolve-sound.ts b/src/resolve-sound.ts index 879cbde..ab1f51d 100644 --- a/src/resolve-sound.ts +++ b/src/resolve-sound.ts @@ -1,4 +1,4 @@ -import type { SoundOptionId } from "./api"; +import { normalizeSoundOptionId, type SoundOptionId } from "./api"; type NotificationStatus = "success" | "failure"; @@ -17,16 +17,16 @@ export function resolveSoundSelection( input: ResolveSoundSelectionInput ): SoundOptionId | null { if (input.status === "success") { - return ( + return normalizeSoundOptionId( input.nodeOverrides?.successSound ?? - input.globalSettings.successSound ?? - null + input.globalSettings.successSound ?? + null ); } - return ( + return normalizeSoundOptionId( input.nodeOverrides?.failureSound ?? - input.globalSettings.failureSound ?? - null + input.globalSettings.failureSound ?? + null ); } diff --git a/tests/api.test.ts b/tests/api.test.ts index 1915db7..c18a54a 100644 --- a/tests/api.test.ts +++ b/tests/api.test.ts @@ -5,6 +5,7 @@ import { buildSettingsRoute, buildSoundUploadRoute, fetchSoundCatalog, + normalizeSoundOptionId, parseSoundCatalogPayload, parseSoundOptionId, storeRuntimeSettings, @@ -13,26 +14,22 @@ import { } from "../src/api"; describe("sound API helpers", () => { - it("creates a stable option id from a source-aware catalog entry", () => { + it("creates a stable option id from a flat catalog entry", () => { const entry: SoundCatalogEntry = { name: "ping-success.wav", - source: "bundled", }; - expect(toSoundOptionId(entry)).toBe("bundled:ping-success.wav"); + expect(toSoundOptionId(entry)).toBe("ping-success.wav"); }); - it("parses an option id back into a source-aware entry", () => { + it("normalizes a legacy prefixed option id into a flat entry", () => { expect(parseSoundOptionId("custom:my-sound.wav")).toEqual({ name: "my-sound.wav", - source: "custom", }); }); - it("rejects unsupported sound sources", () => { - expect(() => parseSoundOptionId("unknown:my-sound.wav")).toThrow( - "Unsupported sound source: unknown" - ); + it("leaves plain option ids unchanged when normalizing", () => { + expect(normalizeSoundOptionId("my-sound.wav")).toBe("my-sound.wav"); }); it("builds the real backend routes for catalog, file, and upload requests", () => { @@ -42,18 +39,17 @@ describe("sound API helpers", () => { expect( buildSoundFileRoute({ name: "my sound.wav", - source: "custom", }) - ).toBe("/comfyui-ping/sounds/custom/my%20sound.wav"); + ).toBe("/comfyui-ping/sounds/my%20sound.wav"); }); - it("parses the source-aware catalog payload", () => { + it("parses the flat catalog payload", () => { expect( parseSoundCatalogPayload({ - sounds: [{ name: "custom.wav", source: "custom" }], + sounds: [{ name: "custom.wav" }], }) ).toEqual({ - sounds: [{ name: "custom.wav", source: "custom" }], + sounds: [{ name: "custom.wav" }], }); }); @@ -61,7 +57,7 @@ describe("sound API helpers", () => { const fetchApi = async (): Promise => new Response( JSON.stringify({ - sounds: [{ name: "uploaded.wav", source: "custom" }], + sounds: [{ name: "uploaded.wav" }], }), { status: 200, @@ -70,7 +66,7 @@ describe("sound API helpers", () => { ); await expect(fetchSoundCatalog({ fetchApi })).resolves.toEqual({ - sounds: [{ name: "uploaded.wav", source: "custom" }], + sounds: [{ name: "uploaded.wav" }], }); }); @@ -78,10 +74,10 @@ describe("sound API helpers", () => { const settings = { enabled: true, failure_enabled: false, - failure_sound: "bundled:ping-failure.wav", + failure_sound: "ping-failure.wav", notify_mode: "queue_drained", success_enabled: true, - success_sound: "custom:uploaded.wav", + success_sound: "uploaded.wav", volume: 0.45, } as const; diff --git a/tests/audio.test.ts b/tests/audio.test.ts index 686dc62..3ba74d3 100644 --- a/tests/audio.test.ts +++ b/tests/audio.test.ts @@ -6,50 +6,45 @@ import { } from "../src/audio"; describe("shouldPlayNotification", () => { - it("returns false when selected custom sound is invalid", () => { + it("returns false when the selected sound is unavailable", () => { expect( shouldPlayNotification({ - selectedSound: "custom:missing.wav", + selectedSound: "missing.wav", soundExists: false, - isCustom: true, }) ).toBe(false); }); - it("returns true for bundled sounds when a selection exists", () => { + it("returns true when a selected sound exists", () => { expect( shouldPlayNotification({ - selectedSound: "bundled:ping-success.wav", + selectedSound: "ping-success.wav", soundExists: true, - isCustom: false, }) ).toBe(true); }); }); describe("playNotification", () => { - it("warns and plays nothing for an invalid custom sound", async () => { + it("warns and plays nothing for an invalid selected sound", async () => { const warn = vi.fn(); const createAudio = vi.fn(); const result = await playNotification({ audioUrl: "unused", createAudio, - isCustom: true, logger: { warn }, - selectedSound: "custom:missing.wav", + selectedSound: "missing.wav", soundExists: false, volume: 0.7, }); expect(result.played).toBe(false); expect(createAudio).not.toHaveBeenCalled(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining("custom sound") - ); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("Selected sound")); }); - it("clears stale current audio for an invalid custom sound", async () => { + it("clears stale current audio for an invalid selected sound", async () => { const pause = vi.fn(); const warn = vi.fn(); const currentAudio = { @@ -63,9 +58,8 @@ describe("playNotification", () => { audioUrl: "unused", createAudio: vi.fn(), currentAudio, - isCustom: true, logger: { warn }, - selectedSound: "custom:missing.wav", + selectedSound: "missing.wav", soundExists: false, volume: 0.7, }); @@ -74,9 +68,7 @@ describe("playNotification", () => { expect(currentAudio.currentTime).toBe(0); expect(result.played).toBe(false); expect(result.audio).toBeNull(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining("custom sound") - ); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("Selected sound")); }); it("stops the previous audio before playing a new sound", async () => { @@ -100,8 +92,7 @@ describe("playNotification", () => { audioUrl: "memory://ping-success", createAudio, currentAudio, - isCustom: false, - selectedSound: "bundled:ping-success.wav", + selectedSound: "ping-success.wav", soundExists: true, stopPrevious: true, volume: 0.4, @@ -136,9 +127,8 @@ describe("playNotification", () => { audioUrl: "memory://ping-failure", createAudio: vi.fn(() => nextAudio), currentAudio, - isCustom: false, logger: { warn }, - selectedSound: "bundled:ping-failure.wav", + selectedSound: "ping-failure.wav", soundExists: true, volume: 0.5, }); @@ -152,7 +142,7 @@ describe("playNotification", () => { }); describe("handleNotificationEvent", () => { - it("plays custom sounds through the real backend sound route", async () => { + it("plays flat sound ids through the real backend sound route", async () => { const play = vi.fn().mockResolvedValue(undefined); const audio = { currentTime: 0, @@ -163,30 +153,34 @@ describe("handleNotificationEvent", () => { const createAudio = vi.fn(() => audio); const result = await handleNotificationEvent({ - catalog: [{ name: "uploaded.wav", source: "custom" }], + catalog: [{ name: "uploaded.wav" }], createAudio, detail: { event_kind: "global", - sound_id: "custom:uploaded.wav", + sound_id: "uploaded.wav", source: "queue_drained", status: "success", volume: 0.8, }, }); - expect(createAudio).toHaveBeenCalledWith( - "/comfyui-ping/sounds/custom/uploaded.wav" - ); + expect(createAudio).toHaveBeenCalledWith("/comfyui-ping/sounds/uploaded.wav"); expect(play).toHaveBeenCalledTimes(1); expect(result.played).toBe(true); }); - it("warns and stays silent for custom sounds when the catalog is empty", async () => { - const warn = vi.fn(); - const createAudio = vi.fn(); + it("normalizes legacy prefixed sound ids before playback", async () => { + const play = vi.fn().mockResolvedValue(undefined); + const audio = { + currentTime: 0, + pause: vi.fn(), + play, + volume: 0, + }; + const createAudio = vi.fn(() => audio); const result = await handleNotificationEvent({ - catalog: [], + catalog: [{ name: "uploaded.wav" }], createAudio, detail: { event_kind: "global", @@ -195,42 +189,32 @@ describe("handleNotificationEvent", () => { status: "success", volume: 0.8, }, - logger: { warn }, }); - expect(createAudio).not.toHaveBeenCalled(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining("custom sound") - ); - expect(result.played).toBe(false); + expect(createAudio).toHaveBeenCalledWith("/comfyui-ping/sounds/uploaded.wav"); + expect(play).toHaveBeenCalledTimes(1); + expect(result.played).toBe(true); }); - it("still plays bundled sounds when the catalog is empty", async () => { - const play = vi.fn().mockResolvedValue(undefined); - const audio = { - currentTime: 0, - pause: vi.fn(), - play, - volume: 0, - }; - const createAudio = vi.fn(() => audio); + it("warns and stays silent when the selected sound is not in the catalog", async () => { + const warn = vi.fn(); + const createAudio = vi.fn(); const result = await handleNotificationEvent({ - catalog: [], + catalog: [{ name: "ping-success.wav" }], createAudio, detail: { event_kind: "global", - sound_id: "bundled:ping-success.wav", + sound_id: "uploaded.wav", source: "queue_drained", status: "success", volume: 0.8, }, + logger: { warn }, }); - expect(createAudio).toHaveBeenCalledWith( - "/comfyui-ping/sounds/bundled/ping-success.wav" - ); - expect(play).toHaveBeenCalledTimes(1); - expect(result.played).toBe(true); + expect(createAudio).not.toHaveBeenCalled(); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("Selected sound")); + expect(result.played).toBe(false); }); }); diff --git a/tests/python/test_bundled_sounds.py b/tests/python/test_bundled_sounds.py index 0b61385..88a02be 100644 --- a/tests/python/test_bundled_sounds.py +++ b/tests/python/test_bundled_sounds.py @@ -4,7 +4,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] -SOUNDS_DIR = REPO_ROOT / "sounds" / "bundled" +SOUNDS_DIR = REPO_ROOT / "sounds" def wav_peak_amplitude(path: Path) -> int: diff --git a/tests/python/test_global_completion.py b/tests/python/test_global_completion.py index c8e4c16..ba3d8f8 100644 --- a/tests/python/test_global_completion.py +++ b/tests/python/test_global_completion.py @@ -53,7 +53,7 @@ def test_build_global_completion_payload_uses_global_event_kind(): assert payload.event_kind == "global" assert payload.status == "failure" - assert payload.sound_id == "bundled:global-failure.wav" + assert payload.sound_id == "global-failure.wav" def test_resolve_global_completion_payload_normalizes_error_status(): @@ -73,7 +73,7 @@ def test_resolve_global_completion_payload_normalizes_error_status(): assert payload is not None assert payload.status == "failure" - assert payload.sound_id == "custom:uploaded.wav" + assert payload.sound_id == "uploaded.wav" assert payload.volume == 0.45 @@ -131,7 +131,7 @@ def task_done(self, item_id, history_result, status): assert result == "done" assert emitted_payloads[0].event_kind == "global" assert emitted_payloads[0].status == "success" - assert emitted_payloads[0].sound_id == "bundled:global-success.wav" + assert emitted_payloads[0].sound_id == "global-success.wav" assert emitted_payloads[0].volume == 0.65 assert len(emitted_payloads) == 1 diff --git a/tests/python/test_notification_policy.py b/tests/python/test_notification_policy.py index 3a69574..3d90f57 100644 --- a/tests/python/test_notification_policy.py +++ b/tests/python/test_notification_policy.py @@ -18,7 +18,7 @@ def test_notification_payload_includes_status_and_sound_id(): ) assert payload.status == "failure" - assert payload.sound_id == "bundled:buzz.wav" + assert payload.sound_id == "buzz.wav" assert payload.volume == 0.8 assert payload.source == "global" @@ -31,7 +31,7 @@ def test_notification_policy_prefers_node_override(): node_overrides={"success_sound": "node-success.wav"}, ) - assert payload.sound_id == "bundled:node-success.wav" + assert payload.sound_id == "node-success.wav" def test_invalid_custom_sound_does_not_fallback(): @@ -52,11 +52,11 @@ def test_resolve_sound_id_uses_global_value_without_override(): global_settings={"failure_sound": "global-failure.wav"}, node_overrides=None, ) - == "bundled:global-failure.wav" + == "global-failure.wav" ) -def test_notification_policy_preserves_source_aware_sound_id(): +def test_notification_policy_normalizes_legacy_prefixed_sound_id(): payload = build_notification_payload( event_kind="node", status="failure", @@ -64,4 +64,4 @@ def test_notification_policy_preserves_source_aware_sound_id(): node_overrides={"failure_sound": "custom:alert.wav"}, ) - assert payload.sound_id == "custom:alert.wav" + assert payload.sound_id == "alert.wav" diff --git a/tests/python/test_ping_node.py b/tests/python/test_ping_node.py index 64f550e..636c3b6 100644 --- a/tests/python/test_ping_node.py +++ b/tests/python/test_ping_node.py @@ -130,5 +130,5 @@ def test_ping_node_execute_emits_runtime_notification_when_enabled(): assert output is not None assert len(emitted_payloads) == 1 assert emitted_payloads[0].status == "success" - assert emitted_payloads[0].sound_id == "bundled:ding.wav" + assert emitted_payloads[0].sound_id == "ding.wav" assert emitted_payloads[0].volume == 0.35 diff --git a/tests/python/test_sound_routes.py b/tests/python/test_sound_routes.py index a945259..379aafe 100644 --- a/tests/python/test_sound_routes.py +++ b/tests/python/test_sound_routes.py @@ -20,124 +20,102 @@ def test_build_sound_catalog_payload_returns_sound_list(): - payload = build_sound_catalog_payload( - bundled=["ping-success.wav"], - custom=["custom.wav"], - ) + payload = build_sound_catalog_payload(["ping-success.wav", "custom.wav"]) assert payload == { "sounds": [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "custom.wav", "source": "custom"}, + {"name": "ping-success.wav"}, + {"name": "custom.wav"}, ], } def test_list_sounds_route_payload_returns_structured_catalog(tmp_path: Path): - bundled_dir = tmp_path / "bundled" - custom_dir = tmp_path / "custom" + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "legacy" - bundled_dir.mkdir() - custom_dir.mkdir() - (bundled_dir / "ping-success.wav").write_bytes(b"bundled") - (custom_dir / "custom.wav").write_bytes(b"custom") + sounds_dir.mkdir() + (sounds_dir / "ping-success.wav").write_bytes(b"bundled") + (sounds_dir / "custom.wav").write_bytes(b"custom") from comfyui_ping.routes import list_sounds_route_payload payload = list_sounds_route_payload( - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) assert payload == { "sounds": [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "custom.wav", "source": "custom"}, + {"name": "custom.wav"}, + {"name": "ping-success.wav"}, ], } -def test_resolve_sound_path_uses_requested_source(): - bundled_dir = REPO_ROOT / "sounds" / "bundled" - custom_dir = REPO_ROOT / "sounds" / "custom" +def test_resolve_sound_path_uses_flat_directory(): + sounds_dir = REPO_ROOT / "sounds" legacy_custom_dir = REPO_ROOT / "input" / "ping-unused-test" - custom_dir.mkdir(parents=True, exist_ok=True) - custom_file = custom_dir / "ping-success.wav" + custom_file = sounds_dir / "temp-flat-route-test.wav" custom_file.write_bytes(b"custom") try: - bundled_path = resolve_sound_path( - filename="ping-success.wav", - source="bundled", - bundled_dir=bundled_dir, - custom_dir=custom_dir, - legacy_custom_dir=legacy_custom_dir, - ) custom_path = resolve_sound_path( - filename="ping-success.wav", - source="custom", - bundled_dir=bundled_dir, - custom_dir=custom_dir, + filename="temp-flat-route-test.wav", + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) finally: custom_file.unlink(missing_ok=True) - assert bundled_path == bundled_dir / "ping-success.wav" assert custom_path == custom_file def test_list_sounds_route_payload_migrates_legacy_custom_uploads(tmp_path: Path): - bundled_dir = tmp_path / "sounds" / "bundled" - custom_dir = tmp_path / "sounds" / "custom" + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "input" / "ping" - bundled_dir.mkdir(parents=True) + sounds_dir.mkdir(parents=True) legacy_custom_dir.mkdir(parents=True) - (bundled_dir / "ping-success.wav").write_bytes(b"bundled") + (sounds_dir / "ping-success.wav").write_bytes(b"bundled") (legacy_custom_dir / "ringtone5.mp3").write_bytes(b"legacy") from comfyui_ping.routes import list_sounds_route_payload payload = list_sounds_route_payload( - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) assert payload == { "sounds": [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "ringtone5.mp3", "source": "custom"}, + {"name": "ping-success.wav"}, + {"name": "ringtone5.mp3"}, ], } - assert (custom_dir / "ringtone5.mp3").read_bytes() == b"legacy" + assert (sounds_dir / "ringtone5.mp3").read_bytes() == b"legacy" assert (legacy_custom_dir / "ringtone5.mp3").exists() is False -def test_list_sounds_route_payload_skips_legacy_files_promoted_to_bundled( +def test_list_sounds_route_payload_skips_duplicate_legacy_files( tmp_path: Path, ): - bundled_dir = tmp_path / "sounds" / "bundled" - custom_dir = tmp_path / "sounds" / "custom" + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "input" / "ping" - bundled_dir.mkdir(parents=True) + sounds_dir.mkdir(parents=True) legacy_custom_dir.mkdir(parents=True) - (bundled_dir / "ping-ringtone.wav").write_bytes(b"bundled") + (sounds_dir / "ping-ringtone.wav").write_bytes(b"bundled") (legacy_custom_dir / "ping-ringtone.wav").write_bytes(b"legacy") from comfyui_ping.routes import list_sounds_route_payload payload = list_sounds_route_payload( - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) assert payload == { - "sounds": [{"name": "ping-ringtone.wav", "source": "bundled"}], + "sounds": [{"name": "ping-ringtone.wav"}], } - assert (custom_dir / "ping-ringtone.wav").exists() is False + assert (sounds_dir / "ping-ringtone.wav").read_bytes() == b"bundled" assert (legacy_custom_dir / "ping-ringtone.wav").exists() is False @@ -146,7 +124,7 @@ def test_save_uploaded_sound_rejects_invalid_extension(tmp_path: Path): save_uploaded_sound( filename="not-a-sound.txt", data=b"bad", - custom_dir=tmp_path, + sounds_dir=tmp_path, legacy_custom_dir=tmp_path / "legacy", ) except ValueError as exc: @@ -160,7 +138,7 @@ def test_save_uploaded_sound_rejects_oversized_file(tmp_path: Path): save_uploaded_sound( filename="too-large.wav", data=b"0" * (MAX_UPLOAD_BYTES + 1), - custom_dir=tmp_path / "custom", + sounds_dir=tmp_path / "sounds", legacy_custom_dir=tmp_path / "legacy", ) except ValueError as exc: @@ -169,58 +147,33 @@ def test_save_uploaded_sound_rejects_oversized_file(tmp_path: Path): raise AssertionError("save_uploaded_sound should reject oversized files") -def test_save_uploaded_sound_rejects_bundled_name_collision(tmp_path: Path): - bundled_dir = tmp_path / "bundled" +def test_save_uploaded_sound_rejects_existing_filename(tmp_path: Path): + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "legacy" - bundled_dir.mkdir() - (bundled_dir / "ping-success.wav").write_bytes(b"bundled") + sounds_dir.mkdir() + (sounds_dir / "ping-success.wav").write_bytes(b"existing") try: save_uploaded_sound( filename="ping-success.wav", data=b"custom", - custom_dir=tmp_path / "custom", - bundled_dir=bundled_dir, - legacy_custom_dir=legacy_custom_dir, - ) - except ValueError as exc: - assert str(exc) == "Filename collides with bundled sound" - else: - raise AssertionError("save_uploaded_sound should reject bundled name collisions") - - -def test_save_uploaded_sound_rejects_existing_custom_filename(tmp_path: Path): - custom_dir = tmp_path / "custom" - legacy_custom_dir = tmp_path / "legacy" - custom_dir.mkdir() - (custom_dir / "custom.wav").write_bytes(b"old") - - try: - save_uploaded_sound( - filename="custom.wav", - data=b"new", - custom_dir=custom_dir, - bundled_dir=tmp_path / "bundled", + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) except ValueError as exc: - assert str(exc) == "Filename already exists in custom sounds" + assert str(exc) == "Filename already exists" else: - raise AssertionError("save_uploaded_sound should reject existing custom filenames") + raise AssertionError("save_uploaded_sound should reject existing filenames") def test_resolve_sound_path_rejects_non_files(tmp_path: Path): - bundled_dir = tmp_path / "bundled" - custom_dir = tmp_path / "custom" + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "legacy" - (bundled_dir / "ping-success.wav").mkdir(parents=True) - custom_dir.mkdir() + (sounds_dir / "ping-success.wav").mkdir(parents=True) path = resolve_sound_path( filename="ping-success.wav", - source="bundled", - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) @@ -293,18 +246,15 @@ async def post(self) -> dict[str, object]: def test_attach_sound_routes_registers_live_handlers(tmp_path: Path): routes = FakeRoutes() - bundled_dir = tmp_path / "bundled" - custom_dir = tmp_path / "custom" + sounds_dir = tmp_path / "sounds" legacy_custom_dir = tmp_path / "legacy" - bundled_dir.mkdir() - custom_dir.mkdir() - (bundled_dir / "ping-success.wav").write_bytes(b"bundled") + sounds_dir.mkdir() + (sounds_dir / "ping-success.wav").write_bytes(b"bundled") attach_sound_routes( routes=routes, web_module=FakeWeb, - bundled_dir=bundled_dir, - custom_dir=custom_dir, + sounds_dir=sounds_dir, legacy_custom_dir=legacy_custom_dir, ) @@ -315,7 +265,7 @@ def test_attach_sound_routes_registers_live_handlers(tmp_path: Path): list_handler = routes.handlers[("GET", SOUND_CATALOG_ROUTE)] list_response = run(list_handler(FakeRequest())) assert list_response.payload == { - "sounds": [{"name": "ping-success.wav", "source": "bundled"}] + "sounds": [{"name": "ping-success.wav"}] } upload_handler = routes.handlers[("POST", SOUND_UPLOAD_ROUTE)] @@ -334,8 +284,8 @@ def test_attach_sound_routes_registers_live_handlers(tmp_path: Path): assert upload_response.status == 201 assert upload_response.payload == { "sounds": [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "custom.wav", "source": "custom"}, + {"name": "custom.wav"}, + {"name": "ping-success.wav"}, ] } @@ -344,10 +294,9 @@ def test_attach_sound_routes_registers_live_handlers(tmp_path: Path): serve_handler( FakeRequest( match_info={ - "source": "custom", "filename": "custom.wav", } ) ) ) - assert serve_response.path == custom_dir / "custom.wav" + assert serve_response.path == sounds_dir / "custom.wav" diff --git a/tests/python/test_sound_storage.py b/tests/python/test_sound_storage.py index 3ff7043..8bf1f48 100644 --- a/tests/python/test_sound_storage.py +++ b/tests/python/test_sound_storage.py @@ -12,15 +12,12 @@ ) -def test_list_sounds_returns_bundled_and_custom_entries(): - sounds = list_available_sounds( - bundled=["ping-success.wav"], - custom=["my.wav"], - ) +def test_list_sounds_returns_flat_entries(): + sounds = list_available_sounds(["ping-success.wav", "my.wav"]) assert sounds == [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "my.wav", "source": "custom"}, + {"name": "ping-success.wav"}, + {"name": "my.wav"}, ] @@ -35,21 +32,18 @@ def test_accepts_expected_audio_extensions(): def test_bundled_sound_files_exist(): - assert (REPO_ROOT / "sounds" / "bundled" / "ping-success.wav").exists() - assert (REPO_ROOT / "sounds" / "bundled" / "ping-failure.wav").exists() - assert (REPO_ROOT / "sounds" / "bundled" / "ping-ringtone.wav").exists() - assert (REPO_ROOT / "sounds" / "bundled" / "notification-soft.wav").exists() - assert (REPO_ROOT / "sounds" / "bundled" / "beep-ping.wav").exists() - assert (REPO_ROOT / "sounds" / "bundled" / "harmonic-beep.wav").exists() + assert (REPO_ROOT / "sounds" / "ping-success.wav").exists() + assert (REPO_ROOT / "sounds" / "ping-failure.wav").exists() + assert (REPO_ROOT / "sounds" / "ping-ringtone.wav").exists() + assert (REPO_ROOT / "sounds" / "notification-soft.wav").exists() + assert (REPO_ROOT / "sounds" / "beep-ping.wav").exists() + assert (REPO_ROOT / "sounds" / "harmonic-beep.wav").exists() -def test_catalog_preserves_duplicate_names_by_source(): - sounds = list_available_sounds( - bundled=["ping-success.wav"], - custom=["ping-success.wav"], - ) +def test_catalog_preserves_duplicate_names(): + sounds = list_available_sounds(["ping-success.wav", "ping-success.wav"]) assert sounds == [ - {"name": "ping-success.wav", "source": "bundled"}, - {"name": "ping-success.wav", "source": "custom"}, + {"name": "ping-success.wav"}, + {"name": "ping-success.wav"}, ] diff --git a/tests/settings.test.ts b/tests/settings.test.ts index 8671a24..9d30fe4 100644 --- a/tests/settings.test.ts +++ b/tests/settings.test.ts @@ -116,7 +116,7 @@ describe("PING_SETTINGS", () => { expect( PING_SETTINGS.map((setting) => setting.name) ).toEqual([ - "Version 1.0.1", + "Version 1.0.2", "Notifications", "Enable Workflow Notifications", "Global Notify Mode", @@ -126,7 +126,7 @@ describe("PING_SETTINGS", () => { "Success Sound", "Failure Sound", "Notification Volume", - "Upload Custom Sound", + "Upload Sound", "Advanced", "Enable Debug Logging", ]); @@ -167,7 +167,7 @@ describe("PING_SETTINGS", () => { ); }); - it("uses bundled defaults for success and failure sounds", () => { + it("uses flat filename defaults for success and failure sounds", () => { const successSetting = PING_SETTINGS.find( (setting) => setting.id === PING_SETTINGS_IDS.SUCCESS_SOUND ); @@ -179,11 +179,11 @@ describe("PING_SETTINGS", () => { expect(failureSetting?.defaultValue).toBe(DEFAULT_FAILURE_SOUND_ID); }); - it("applies source-aware catalog options to both sound settings", () => { + it("applies flat catalog options to both sound settings", () => { applySoundCatalogToSettings({ sounds: [ - { name: "ping-success.wav", source: "bundled" }, - { name: "uploaded.wav", source: "custom" }, + { name: "ping-success.wav" }, + { name: "uploaded.wav" }, ], }); @@ -195,27 +195,27 @@ describe("PING_SETTINGS", () => { ); expect(successSetting?.options).toEqual([ - { text: "Bundled / ping-success.wav", value: "bundled:ping-success.wav" }, - { text: "Custom / uploaded.wav", value: "custom:uploaded.wav" }, + { text: "ping-success.wav", value: "ping-success.wav" }, + { text: "uploaded.wav", value: "uploaded.wav" }, ]); expect(failureSetting?.options).toEqual([ - { text: "Bundled / ping-success.wav", value: "bundled:ping-success.wav" }, - { text: "Custom / uploaded.wav", value: "custom:uploaded.wav" }, + { text: "ping-success.wav", value: "ping-success.wav" }, + { text: "uploaded.wav", value: "uploaded.wav" }, ]); }); - it("preserves a persisted custom selection while the live catalog is still loading", () => { + it("preserves a persisted legacy selection while the live catalog is still loading", () => { expect( buildRenderedSoundOptions( - [{ text: "Bundled / ping-success.wav", value: "bundled:ping-success.wav" }], + [{ text: "ping-success.wav", value: "ping-success.wav" }], "custom:uploaded.wav" ) ).toEqual([ { - text: "Custom / uploaded.wav (unavailable)", - value: "custom:uploaded.wav", + text: "uploaded.wav (unavailable)", + value: "uploaded.wav", }, - { text: "Bundled / ping-success.wav", value: "bundled:ping-success.wav" }, + { text: "ping-success.wav", value: "ping-success.wav" }, ]); }); @@ -223,7 +223,7 @@ describe("PING_SETTINGS", () => { const fetchApi = vi.fn(async () => new Response( JSON.stringify({ - sounds: [{ name: "uploaded.wav", source: "custom" }], + sounds: [{ name: "uploaded.wav" }], }), { status: 200, @@ -240,7 +240,7 @@ describe("PING_SETTINGS", () => { }, }) ).resolves.toEqual({ - sounds: [{ name: "uploaded.wav", source: "custom" }], + sounds: [{ name: "uploaded.wav" }], }); expect(fetchApi).toHaveBeenCalledWith("/comfyui-ping/sounds", undefined); }); @@ -292,10 +292,10 @@ describe("PING_SETTINGS", () => { ).resolves.toEqual({ enabled: false, failure_enabled: false, - failure_sound: "bundled:ping-failure.wav", + failure_sound: "ping-failure.wav", notify_mode: "every_prompt", success_enabled: true, - success_sound: "custom:uploaded.wav", + success_sound: "uploaded.wav", volume: 0.35, }); @@ -312,7 +312,7 @@ describe("PING_SETTINGS", () => { const fetchApi = vi.fn(async () => new Response( JSON.stringify({ - error: "Filename already exists in custom sounds", + error: "Filename already exists", }), { status: 400, @@ -334,9 +334,7 @@ describe("PING_SETTINGS", () => { ) ).resolves.toBeNull(); - expect(warn).toHaveBeenCalledWith( - expect.stringContaining("Unable to upload custom sound") - ); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("Unable to upload sound")); expect(fetchApi).toHaveBeenCalledWith( "/comfyui-ping/sounds/upload", expect.objectContaining({ @@ -345,7 +343,7 @@ describe("PING_SETTINGS", () => { ); }); - it("syncs runtime settings when the custom success sound renderer changes", async () => { + it("syncs runtime settings when the success sound renderer changes", async () => { let successSoundValue = DEFAULT_SUCCESS_SOUND_ID; const fetchApi = vi.fn( async (_route: string, options?: RequestInit): Promise => @@ -401,7 +399,7 @@ describe("PING_SETTINGS", () => { throw new Error("Expected the renderer to return a select element"); } - selectEl.value = "bundled:ping-failure.wav"; + selectEl.value = "ping-failure.wav"; selectEl.dispatchEvent(new Event("change")); await Promise.resolve(); @@ -450,8 +448,8 @@ describe("PING_SETTINGS", () => { applySoundCatalogToSettings({ sounds: [ - { name: "ping-success.wav", source: "bundled" }, - { name: "uploaded.wav", source: "custom" }, + { name: "ping-success.wav" }, + { name: "uploaded.wav" }, ], }); @@ -480,7 +478,7 @@ describe("PING_SETTINGS", () => { await Promise.resolve(); expect(FakeAudio.created).toHaveLength(1); - expect(FakeAudio.created[0]?.src).toBe("/comfyui-ping/sounds/custom/uploaded.wav"); + expect(FakeAudio.created[0]?.src).toBe("/comfyui-ping/sounds/uploaded.wav"); expect(FakeAudio.created[0]?.volume).toBe(0.35); expect(FakeAudio.created[0]?.play).toHaveBeenCalledTimes(1); @@ -490,13 +488,14 @@ describe("PING_SETTINGS", () => { it("subscribes to ping notification events", async () => { const addEventListener = vi.fn(); + vi.stubGlobal("document", new FakeDocument()); vi.stubGlobal("app", { api: { addEventListener, fetchApi: vi.fn(async () => new Response( JSON.stringify({ - sounds: [{ name: "ping-success.wav", source: "bundled" }], + sounds: [{ name: "ping-success.wav" }], }), { status: 200, @@ -578,7 +577,7 @@ describe("PING_SETTINGS", () => { expect(event?.type).toBe(PING_EVENT_NAME); expect(event?.detail).toEqual({ event_kind: "queue_error", - sound_id: "bundled:ping-failure.wav", + sound_id: "ping-failure.wav", source: "prompt_no_outputs", status: "failure", volume: 0.45, diff --git a/tests/sound-resolution.test.ts b/tests/sound-resolution.test.ts index cab5b62..823f8ab 100644 --- a/tests/sound-resolution.test.ts +++ b/tests/sound-resolution.test.ts @@ -9,7 +9,7 @@ describe("resolveSoundSelection", () => { nodeOverrides: { successSound: "bundled:node.wav" }, globalSettings: { successSound: "bundled:global.wav" }, }) - ).toBe("bundled:node.wav"); + ).toBe("node.wav"); }); it("falls back to the matching global sound", () => { @@ -19,7 +19,7 @@ describe("resolveSoundSelection", () => { nodeOverrides: {}, globalSettings: { failureSound: "custom:failure.wav" }, }) - ).toBe("custom:failure.wav"); + ).toBe("failure.wav"); }); it("returns null when no matching sound is configured", () => {