Commit Graph
212 Commits
Author SHA1 Message Date
HoneyandClaude Opus 4.8 77bbb18c8d chore(backend): include resolved path in delete-song "file not found" error
The delete-song endpoint returned a bare "File not found on disk" with no
context, making path-resolution mismatches impossible to diagnose. Now
reports the Navidrome path, music root, and searched filename — matching the
detail the edit-metadata endpoint already provides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:32:46 +02:00
HoneyandClaude Opus 4.8 e93c0c7ab1 fix(backend): drop YTM Songs filter param — it dropped the exact track
The &params= Songs-tab filter re-ranked YTM results toward a same-named
artist's discography and excluded the official track (qNffDItRUeo) entirely,
even logged-out. The default blended search (what the browser shows) ranks
the exact track first. Removing the param so yt-dlp gets the same result set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:15:12 +02:00
HoneyandClaude Opus 4.8 c4e4c8c090 fix(backend): use space (not dash) between artist and title in search query
"MINA - recognize" skewed YTM ranking toward a same-named artist's full
discography, so the actual track (qNffDItRUeo) never appeared in results.
"MINA recognize" — matching how users type in the YTM search box — ranks
the exact official track first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:06:00 +02:00
HoneyandClaude Opus 4.8 c34123af38 fix(backend): apply web_music client to YTM search extraction too
The YTM search (getYtmVideoUrls) ran without the web_music client, so yt-dlp
parsed the search page as a generic YouTube search and returned unrelated
videos instead of the native Songs shelf — the actual artist track (e.g.
qNffDItRUeo for MINA - recognize) never appeared in results. Apply the same
player_client=web_music,default arg to the search step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:48:26 +02:00
HoneyandClaude Opus 4.8 9014659112 fix(backend): use web_music player client for YTM track access
yt-dlp's android_vr player client returns "This video is not available" for
many YouTube Music Art Track / topic uploads (e.g. MINA - recognize) that
play fine in the browser. Switch to player_client=web_music with default
fallback so these tracks become downloadable, fixing the cascade falling
through to wrong YouTube videos.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:46:53 +02:00
HoneyandClaude Sonnet 4.6 ce4577e8f4 fix(backend): strip release suffix from title metadata tag
searchTitle (e.g. "recognize") is now passed to embedMetadata instead of
the raw title ("recognize - Single"), so the ID3 title tag matches the
actual song name. Album tag stays unchanged ("recognize - Single").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:44:13 +02:00
HoneyandClaude Sonnet 4.6 bb0be52f46 fix(backend): fix getYtmVideoUrls always returning empty + remove unsupported ytmsearch5
[youtube:tab] in stderr fires for all YTM search page processing, not just
artist channel expansion, causing channelExpanded=true for every search and
returning [] — bypassing all YTM cascade steps entirely. Fix: only check
stdout for /browse/UC (actual channel URL in results). Remove ytmsearch5:
scheme which is not supported by this yt-dlp version. Revert pool to 10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:32:22 +02:00
HoneyandClaude Sonnet 4.6 3c54850cca fix(backend): add ytmsearch5 step + expand YTM pool to 20 for better coverage
ytmsearch5 (yt-dlp native YTM search) added as step 2 — returns different
result ordering than the manual YTM URL, recovering songs that the URL
approach ranks too low. YTM pool expanded from 10 → 20 results to reduce
fallthrough to YouTube.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:25:37 +02:00
HoneyandClaude Sonnet 4.6 4804023a98 fix(backend): don't abort cascade on yt-dlp non-zero exit (e.g. video not available)
spawnYtdlp now always resolves so the hasAudio() cascade continues when a
single YTM result is geo-blocked or unavailable. Error is still logged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:23:59 +02:00
HoneyandClaude Sonnet 4.6 88d3e336c8 fix(backend): use searchTitle in YTM URL (was using raw title with release suffix)
YTM search used `title` directly, so "recognize - Single" was searched on
YouTube Music instead of "recognize" — causing no YTM results and falling
through to the less reliable YouTube search cascade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:19:43 +02:00
HoneyandClaude Sonnet 4.6 d5bc887f31 fix(backend): tighten duration filter margin from ±15% to ±10% (min 10s)
Matches spotdl behaviour; still covers YouTube intros/outros while
reducing false positives from similarly-lengthed wrong songs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:07:19 +02:00
HoneyandClaude Sonnet 4.6 4cbfa35655 fix(backend): normalize iTunes lookup across all download paths
- Track path: always fetch iTunes when durationMs/trackNum missing (not just genre)
- Both paths: strip release suffixes from title before iTunes lookup
- Log "Album:" instead of "Album aus Spotify:" (also fires for iTunes global search)
- Log duration when fetched from iTunes so it's visible in job log

Ensures all importers have consistent yt-dlp search filters (duration filter
now always runs when iTunes finds the track).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:00:32 +02:00
HoneyandClaude Sonnet 4.6 86d3999628 fix(download): pass Spotify durationMs through to yt-dlp duration filter
- downloads.js: use durationMs (Spotify) before trackTimeMillis (iTunes)
- server: read durationMs from top-level body, not only from tracks[0]
- server: also grab durationMs + trackNum from iTunes in the Spotify path

Without durationMs the duration filter never ran, allowing a 58s fan video
to pass all text filters and be downloaded instead of the actual song.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:55:17 +02:00
HoneyandClaude Sonnet 4.6 5f257bf451 fix(backend): generalize release-suffix stripping from search title
Handles all Spotify trailing suffixes: Single, EP, Remaster, Deluxe Edition,
Bonus Tracks, B-Sides, bare years (2024), Official Music Single, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:44:06 +02:00
HoneyandClaude Sonnet 4.6 bff652a415 fix(backend): strip release-type suffixes from YTM search title + extend stopwords
- "recognize - Single" title → search uses "recognize" (strips "- Single/EP/Album/…")
- Adds Single/EP/Remaster/Deluxe/… to titleKws STOPWORDS so they never become
  mandatory title-match filters (no YouTube video title contains "Single")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:42:07 +02:00
HoneyandClaude Sonnet 4.6 ecb7721e3b fix(backend): only append album to YTM query for Japanese titles
Western tracks now use artist + title only (matches spotdl behaviour).
Album suffix was causing noise for short English titles like singles
where album name = title + "Single".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:35:32 +02:00
HoneyandClaude Sonnet 4.6 319a3804f3 fix(backend): strip title-duplicate and generic words from album search suffix
Album "recognize - Single" produced query "MINA - recognize recognize Single"
because "recognize" appeared in both title and album name. Now filters album
words already present in the title, plus generic words (Single, EP, Remaster…).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:29:39 +02:00
HoneyandClaude Sonnet 4.6 f1bdd811ca chore: remove squid.wtf (Qobuz) backend — yt-dlp only
Deletes backend/lib/squid.js; strips all references from downloader.js,
config.js, novasonic-server.js, docker-compose.yml, .env.example, README,
and CHANGELOG. Default DOWNLOAD_BACKEND is now ytdlp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:59:30 +02:00
HoneyandClaude Sonnet 4.6 d47a3404b3 debug: add /api/events-debug/:artist endpoint to inspect raw Ticketmaster response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 21:06:10 +02:00
HoneyandClaude Sonnet 4.6 7fe755379b fix(events): fix Ticketmaster URL encoding + add error logging
Colons in startDateTime were being percent-encoded which Ticketmaster
may reject. Also log errors/status so backend logs reveal the cause
when no events appear.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:57:26 +02:00
HoneyandClaude Sonnet 4.6 adcbb2e883 feat: replace Bandsintown with Ticketmaster for concert events
Backend: swap /api/events/:artist to use Ticketmaster Discovery API
(startDateTime=now, classificationName=music, size=20, sort by date).
Maps TM response to the same {datetime, venue, offers} shape the modal
expects. Returns [] if TICKETMASTER_API_KEY is not set.

Frontend: remove all debug console.logs from events.js; drop redundant
client-side date filter (TM already filters by startDateTime server-side).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:51:51 +02:00
HoneyandClaude Sonnet 4.6 98c964ecae fix: proxy Bandsintown events through backend to bypass 403, always show ticket link with sold-out badge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 01:23:00 +02:00
HoneyandClaude Sonnet 4.6 08ca8303f3 chore: remove unused _syncNpDirect import; add express to backend/package.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 01:21:41 +02:00
HoneyandClaude Sonnet 4.6 36a0b38eca feat: server-side romaji overrides (shared list); preload ExoPlayer from SharedPreferences on cold start
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 10:47:25 +02:00
HoneyandClaude Sonnet 4.6 59120fd7b6 feat: rename song file artist segment when applying romaji override fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:29:21 +02:00
HoneyandClaude Sonnet 4.6 ff3bb782d3 fix: romaji override delete button (JSON.stringify breaks HTML attr); fix file stem search for en-dash track prefixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:26:39 +02:00
HoneyandClaude Sonnet 4.6 7496c28663 fix: surface first error detail in fix-artist-sort job message for debugging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 00:17:48 +02:00
HoneyandClaude Sonnet 4.6 d777301c20 feat: batch fix artist sort tags + rename folder when override is added
- edit-metadata: use albumArtistSort (when provided) for artist folder naming
  instead of auto-converted romajiAlbumArtist — ensures manual overrides sent
  by the download pipeline also place files in the correct folder
- Add POST /api/fix-artist-sort endpoint: searches Navidrome for all songs
  where artist or albumArtist matches the kanji, re-writes ARTISTSORT +
  ALBUMARTISTSORT via ffmpeg (-map_metadata 0 preserves all other existing
  tags), moves each file to the correct artist folder, cleans up empty old
  dirs, triggers Navidrome rescan — returns a job ID for progress tracking
- addRomajiOverrideUI: after saving a new override, calls fix-artist-sort and
  shows a job card with live progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 22:33:12 +02:00
HoneyandClaude Sonnet 4.6 63ff8920bf fix: repair Japanese song download pipeline
Five separate root causes fixed:

1. buildTitleKeywords (ytdlp.js): For Japanese titles \w strips all
   kana/kanji leaving no keywords. Now uses the first whitespace-
   delimited segment as a literal regex keyword so --match-filter
   title ~= requires the YTM video to contain the Japanese text.

2. Tokenizer wait (ytdlp.js): If kuromoji hasn't finished loading when
   a Japanese download starts, romajiForStorage returns the original
   Japanese text unchanged — producing Japanese folder/filenames and
   skipping sort tags. Now waits up to 30 s for the tokenizer.

3. titleOnlyMatch guard (metadata.js): iTunes titleOnlyMatch (no artist
   check) was used as a fallback for all searches. For Japanese
   titles/artists this matched wrong US songs with identical titles
   (common in anime OSTs). Now titleOnlyMatch is skipped when the
   search title or artist contains Japanese characters.

4. squid.js rawArtist: rawArtist was not in squid's signature and
   never forwarded to embedMetadata, so Qobuz-downloaded J-Pop tracks
   stored the iTunes-resolved English name as artist instead of the
   Japanese original. Fixed by accepting and forwarding rawArtist.

5. squid.js romaji paths: Folder and filename used raw artist/title
   with no romaji conversion, creating Japanese-named paths
   inconsistent with the yt-dlp path. Now uses romajiForStorage same
   as ytdlp.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:40:46 +02:00
HoneyandClaude Sonnet 4.6 52451f5086 fix: resolve Japanese-path songs stored as romaji files
When Navidrome has a Japanese path in its DB but the file was saved with
a romaji filename by the download pipeline, the existing fallback chain
compared the Japanese title stem against romaji filenames and always
failed. Added _findFileByStemOrRomaji which converts the stem via
romajiForStorage before the final recursive search, covering all three
endpoints: edit-metadata, delete-song, song-sorttags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:30:04 +02:00
HoneyandClaude Sonnet 4.6 a2714dddaa feat: delete song from disk via edit-metadata modal
- /api/delete-song endpoint: resolves path via same fallback chain as
  edit-metadata, validates file is inside music root + is an audio file,
  deletes it, then prunes the album folder if empty and the artist folder
  if that too becomes empty — no recursive rm, depth strictly limited to
  two levels above the file so no accidental mass-deletes
- Safety: path.resolve() guard ensures no path-traversal outside music root;
  rmdirSync (non-recursive) fails if dir is non-empty as extra safety net
- Frontend: delete button (red outline, trash icon) at bottom of edit mode
  with confirmation dialog showing title + folder-cleanup warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:36:31 +02:00
HoneyandClaude Sonnet 4.6 c405465f5c fix: wire _findFileByStem into both file resolution fallback chains
Handles "File not found" for songs with non-standard folder/artist names
(e.g. artist separator • or year-only album folders) by doing a deep
recursive title-stem search as a last resort after rebase and dir scan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:17:41 +02:00
HoneyandClaude Sonnet 4.6 8989b15767 fix: pass id-based iTunes lookup requests through to proxy
/api/itunes/lookup was returning 400 for any request without artist+title,
including id-based lookups (?id=...&entity=album) used by the artist page.
Use next() to fall through to the /:endpoint proxy for those requests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:07:12 +02:00
HoneyandClaude Sonnet 4.6 2df5a54985 fix: prevent 9-second downloads caused by short iTunes duration match
When iTunes enrichment returns a jingle/preview as the first result (e.g.
trackTimeMillis=9000), the yt-dlp duration filter becomes impossible
(duration > 30 & duration < 24), all filtered steps fail, and the final
unfiltered fallback downloads whatever YouTube returns first — often a
short clip.

Fix 1: spotify.js — only copy iTunes trackTimeMillis to durationMs when
it is > 30 seconds (genuine song, not a jingle or preview).

Fix 2: ytdlp.js — only enter the duration-filtered search branch when
durationMs > 30 000ms, so a corrupted short value silently falls back to
the unfiltered search path which still finds the correct full song.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:06:33 +02:00
HoneyandClaude Sonnet 4.6 3db71c24a4 fix: restore playlist membership after metadata edit renames/moves file
When a file is renamed or moved to a new artist/album folder during
metadata edit, Navidrome assigns a new song ID, breaking all playlist
entries that referenced the old ID.

Before the edit: collect which playlists contain the song.
After rename + rescan: poll Navidrome until the new song appears,
then update each affected playlist (add new ID, remove old index).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:01:50 +02:00
HoneyandClaude Sonnet 4.6 fce53e409d fix: find renamed files by searching original Navidrome directory
When Navidrome has a stale index (e.g. file was renamed from
'01 - Idea 10.mp3' to '01 - Gibran Alcocer - Idea 10.mp3'),
_findFileInDir now falls back to the original file's directory
and matches by track-number prefix ('01 - ') so the renamed file
is found even without a Navidrome rescan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:44:58 +02:00
HoneyandClaude Sonnet 4.6 6fa9751fb6 fix: log music root in file-not-found error for diagnosis
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:14:46 +02:00
HoneyandClaude Sonnet 4.6 4f83055edd fix: also write totalTracks, discNum, discTotal for Spotify imports
Spotify enrichment only copies album/trackNum/duration from iTunes.
Now also fetch totalTracks, discNum, discTotal from iTunes for:
- alreadyEnriched playlist tracks
- single track downloads where album came from Spotify

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:00:25 +02:00
HoneyandClaude Sonnet 4.6 78024181f0 fix: always write English genre for all download paths
1. JP genre mapping table in metadata.js — when only JP iTunes has the song
   (niche Japanese artists), map localized JP genre names (アニメ, ポップ, etc.)
   to English equivalents so files never get Japanese characters in genre tag.

2. Spotify playlist import (alreadyEnriched path) — was skipping iTunes lookup
   entirely, leaving genre empty. Now does a lightweight genre-only lookup.

3. Single track download with Spotify album — was skipping iTunes when album
   already known from Spotify. Now also fetches genre from iTunes when missing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 22:58:08 +02:00
HoneyandClaude Sonnet 4.6 2ae439bfd2 fix: resolve Navidrome path mismatch across Docker containers for metadata edit
When Navidrome and the backend run in separate containers, Navidrome returns
absolute paths rooted at its own mount (e.g. /opt/navidrome/music) which
don't exist in the backend container (mounted at /music).

Add three-stage fallback:
1. _rebasePath: strip leading path components and try under CONFIG.musicPath
2. _findFileRecursive: exact filename search under CONFIG.musicPath
3. _findFileInDir: fuzzy search in album dir by track-number prefix or title stem
   (handles files renamed to include artist in the filename)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 21:48:05 +02:00
HoneyandClaude Sonnet 4.6 760d8d5e1f fix: use US iTunes genre to avoid Japanese-localized genre names
JP store returns localized genres (e.g. イージーリスニング instead of Easy Listening).
Fetch JP and US in parallel, use JP match for title/album/artwork but always
take genre from US result so genre is always in English/Western characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:14:43 +02:00
HoneyandClaude Sonnet 4.6 67f4be272a fix: ie-totaltracks element id + JP-strict/US-fallback iTunes matching
Prevent false Japanese matches by never using JP title-only fallback.
Strategy: JP strict → US strict → US title-only so niche Western tracks
still resolve but same-English-title Japanese songs don't pollute results.

Also fix JS error in autofillItunes: ie-total → ie-totaltracks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 18:15:04 +02:00
Honey 21159ad2c5 fix: iTunes route order (lookup before proxy); stable cover art URLs for browser caching 2026-05-27 18:01:09 +02:00
Honey 09cec18b33 fix: strip punctuation in normalizeTitle so comma variants match (e.g. 'Mr. Lawrence' vs 'Mr, Lawrence') 2026-05-27 17:49:31 +02:00
Honey 9821334cc8 fix: split multi-artist string before iTunes search query in lookupItunesTrackMeta 2026-05-27 17:40:14 +02:00
Honey 3837783225 fix: iTunes JP-first with US fallback; romaji filter in playlist/starred search 2026-05-27 17:16:57 +02:00
Honey a9463917e3 fix: use iTunes country=jp globally — returns Japanese original names, identical for non-Japanese music 2026-05-27 15:21:11 +02:00
HoneyandClaude Sonnet 4.6 b7afa19f70 feat: romaji artist names, Apple Music autofill, romaji index invalidation
- Artist grid and detail header now show romaji names when toggle is on.
  Builds a Map<artistId, artistRomaji> from the existing song romaji index
  so no extra API call is needed.

- Apple Music autofill button added to edit modal next to MusicBrainz.
  Reuses the existing lookupItunesTrackMeta backend function via a new
  GET /api/itunes/lookup endpoint — fills album, year, genre, track#,
  and cover art.

- Romaji index is invalidated after every successful download so newly
  added songs appear with correct sort tags without requiring a page reload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:26:51 +02:00
HoneyandClaude Sonnet 4.6 212491aa89 fix: convert katakana proper nouns (e.g. ヨルシカ) to romaji in sort tags
When kuromoji marks a katakana token as reading '*' (unknown word — common
for band names like ヨルシカ), the code fell back to the surface_form which
is still katakana. _hiraToRomaji can't handle katakana so it passed through
unchanged, causing sort tags to show ヨルシカ instead of Yorushika.

Fix: if the surface_form contains katakana and reading is *, convert it
mechanically via _kataToHira + _hiraToRomaji — katakana-to-romaji is always
unambiguous and needs no dictionary.

Also add a Japanese-char guard in the frontend when applying romaji index
values, so stale cache entries can never overwrite correct values with Japanese.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 02:52:35 +02:00
HoneyandClaude Sonnet 4.6 255ea1969c feat: load actual file sort tags in edit modal + remove settings hints
- backend: new GET /api/song-sorttags reads TITLESORT/ARTISTSORT/ALBUMARTISTSORT/
  ALBUMSORT directly from the file via ffprobe
- ui.js: pre-fill sort fields from romaji index, then overwrite with real file
  tags from the backend (shows actual values even if manually set)
- index.html: remove redundant hint texts in settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 22:42:34 +02:00