Commit graph

1 commit

Author SHA1 Message Date
Broque Thomas
d944a166f8 Reorganize: move orphan-format siblings alongside the canonical
Discord report (Foxxify): users with the lossy-copy feature enabled
have `track.flac` AND `track.opus` side-by-side in their library.
Reorganize is DB-driven and only knows about ONE file per track
(the lossy copy). The other format used to get left behind in the
old location while the canonical moved to its new destination.
Empty-folder cleanup never fired because the source dir still had
audio.

# What was happening

1. User downloads album → SoulSync transcodes `.flac` → `.opus`,
   embeds `.lrc` lyrics
2. DB row points at `.opus` (the lossy library copy)
3. User runs Library Reorganize
4. Reorganize moves `.opus` to new template path → `Artist/Album/01 Track.opus`
5. `.flac` orphan stays at old location, `.lrc` follows `.opus`
6. Source dir still has the `.flac` → cleanup skips → empty folders pile up

# Fix

`_finalize_track` now finds sibling-stem audio files at the source
BEFORE removing the canonical and moves them to the same destination
dir, preserving both formats with the canonical's renamed stem.

Two new helpers in `core/library_reorganize.py`:

- `_find_sibling_audio_files(audio_path) -> list[str]` — returns
  paths to other audio files at the same directory that share the
  canonical's filename stem. Excludes the canonical itself, non-
  audio extensions (sidecars handled separately by
  `_delete_track_sidecars`), and different-stem tracks (different
  songs in the same dir).
- `_move_sibling_to_destination(sibling_src, canonical_dst) -> str`
  — moves a sibling-format file to the canonical's destination dir
  with the canonical's renamed stem + the sibling's original
  extension. Defensive — OS errors logged at warning, return None,
  doesn't raise (caller treats as best-effort).

After the fix:

1. `.opus` → moved to new dir
2. `.flac` sibling detected → moved to same new dir with same stem
3. Source `.opus` removed, `.lrc` sidecar deleted from source
4. Source dir empty → cleanup proceeds normally
5. Both formats end up paired at the new location

# Tests added (11)

`tests/test_reorganize_orphan_format_handling.py`:

- Sibling detection: finds `.flac` when `.opus` is canonical (and
  symmetric direction), excludes canonical itself, excludes
  different-stem tracks, excludes non-audio (`.lrc`/`.nfo`),
  finds multiple siblings (3+ formats), returns empty when source
  dir missing
- Sibling move: renames to canonical stem + preserves sibling
  extension, creates destination dir if missing, no-op when source
  already at destination, returns None on OS failure (caller
  treats as best-effort)

# Verification

- 11/11 new tests pass
- 97/97 reorganize-related tests pass total (no regression in
  existing helpers)
- Ruff clean

# Follow-up in same PR

Next commit: cleanup repair job for legacy "Unknown Artist /
album_id" rows from the pre-#524 manual-import bug. Reorganize
correctly leaves those alone (they're DB-broken, not file-broken),
but a separate maintenance job to find + re-enrich them is needed.
2026-05-10 19:58:31 -07:00