Soulseek album-bundle (and any other release-staging path) was
importing every file with ``track_number=1`` because the staging
metadata reader used the auto-import-flavor filename extractor:
``extract_track_number_from_filename`` returns 1 when the basename
has no ``NN -`` prefix. That's the right default for the loose
auto-import flow (single file in, no upstream metadata to lean
on), but completely wrong for staging-cache reads:
- For an album-bundle download the user has authoritative track
numbers in the Spotify track list flowing through to
``track_info`` for each task.
- ``try_staging_match`` in ``core/downloads/staging.py`` was
meant to use those numbers when the staged file's own metadata
doesn't have them.
- But the staging cache populated ``track_number=1`` for every
untagged bare-title file (e.g. ``Cha-La Head-Cha-La.flac``), the
album-bundle resolution branch reads file-side first, sees 1,
and short-circuits the rest of the chain.
Fix:
- New ``extract_explicit_track_number`` in
``core/imports/filename.py`` — strict variant that returns
``0`` when no numeric prefix is visible. Docstring explicitly
contrasts with the legacy 1-defaulting helper so future
callers pick the right one.
- ``read_staging_file_metadata`` in ``core/imports/staging.py``
now uses the strict extractor, so the staging file dict
carries ``track_number=0`` ("unknown") instead of ``1`` for
untagged bare-title files.
- The legacy ``extract_track_number_from_filename`` keeps its
1-default behavior so auto-import callers + the post-process
template fallbacks are unchanged; it's now implemented in
terms of the strict variant.
- Tag-side parsing also tightened to require ``> 0`` before
overriding the filename-derived value.
3 new tests pin the contracts:
- ``test_extract_explicit_track_number_returns_zero_when_no_prefix``
- ``test_read_staging_file_metadata_returns_zero_track_when_unknown``
- existing ``test_extract_track_number_from_filename_handles_common_patterns``
now explicitly comments why bare filenames keep returning 1.
758 tests across imports + downloads + repair + staging-provenance
suites green. WHATS_NEW entry added under 2.6.3.
Reported against an album-bundle download of Ryoto's
"Cha-La Head-Cha-La" where slskd staged 15 untagged FLAC files
named after the song titles only.
171 lines
5.8 KiB
Python
171 lines
5.8 KiB
Python
import sys
|
|
import types
|
|
|
|
from core.imports.file_ops import (
|
|
cleanup_empty_directories,
|
|
safe_move_file,
|
|
)
|
|
from core.imports.filename import (
|
|
extract_explicit_track_number,
|
|
extract_track_number_from_filename,
|
|
)
|
|
from core.imports.staging import read_staging_file_metadata
|
|
|
|
|
|
def test_extract_track_number_from_filename_handles_common_patterns():
|
|
assert extract_track_number_from_filename("01 - Song.mp3") == 1
|
|
assert extract_track_number_from_filename("1-03 - Song.mp3") == 3
|
|
# Bare filename keeps the auto-import-friendly default of 1 — there's
|
|
# no upstream metadata to recover from in that flow.
|
|
assert extract_track_number_from_filename("Artist - Song.mp3") == 1
|
|
|
|
|
|
def test_extract_explicit_track_number_returns_zero_when_no_prefix():
|
|
"""Staging readers need to distinguish 'track 1' from 'unknown'.
|
|
|
|
Pinned because:
|
|
- the legacy extractor defaults to 1 (auto-import semantics),
|
|
- staging file scanners that conflate the two end up writing every
|
|
file in an untagged album bundle to track_number=1.
|
|
"""
|
|
# Bare titles with no numeric prefix → 0 (unknown).
|
|
assert extract_explicit_track_number("Artist - Song.mp3") == 0
|
|
assert extract_explicit_track_number("Cha-La Head-Cha-La.flac") == 0
|
|
assert extract_explicit_track_number("") == 0
|
|
# Real prefixes still parse correctly.
|
|
assert extract_explicit_track_number("01 - Song.mp3") == 1
|
|
assert extract_explicit_track_number("(03) Song.mp3") == 3
|
|
# Disc-track format requires a separator after the track number.
|
|
assert extract_explicit_track_number("1-07 - Song.mp3") == 7
|
|
|
|
|
|
def test_safe_move_file_replaces_existing_destination(tmp_path):
|
|
src = tmp_path / "source.flac"
|
|
dst_dir = tmp_path / "dest"
|
|
dst_dir.mkdir()
|
|
dst = dst_dir / "track.flac"
|
|
|
|
src.write_text("new")
|
|
dst.write_text("old")
|
|
|
|
safe_move_file(src, dst)
|
|
|
|
assert not src.exists()
|
|
assert dst.read_text() == "new"
|
|
|
|
|
|
def test_cleanup_empty_directories_removes_nested_empty_paths(tmp_path):
|
|
download_root = tmp_path / "downloads"
|
|
nested_dir = download_root / "Artist" / "Album"
|
|
nested_dir.mkdir(parents=True)
|
|
moved_file_path = nested_dir / "track.flac"
|
|
|
|
cleanup_empty_directories(str(download_root), str(moved_file_path))
|
|
|
|
assert not nested_dir.exists()
|
|
assert not (download_root / "Artist").exists()
|
|
assert download_root.exists()
|
|
|
|
|
|
def test_read_staging_file_metadata_reads_tags(monkeypatch, tmp_path):
|
|
file_path = tmp_path / "Song One.flac"
|
|
file_path.write_text("fake")
|
|
|
|
class DummyTags:
|
|
def __init__(self):
|
|
self.values = {
|
|
"title": ["Song One"],
|
|
"artist": ["Artist One"],
|
|
"albumartist": ["Album Artist"],
|
|
"album": ["Album One"],
|
|
"tracknumber": ["03/12"],
|
|
"discnumber": ["2/3"],
|
|
}
|
|
|
|
def get(self, key, default=None):
|
|
return self.values.get(key, default)
|
|
|
|
fake_mutagen = types.ModuleType("mutagen")
|
|
fake_mutagen.File = lambda path, easy=True: DummyTags()
|
|
monkeypatch.setitem(sys.modules, "mutagen", fake_mutagen)
|
|
|
|
metadata = read_staging_file_metadata(str(file_path), file_path.name)
|
|
|
|
assert metadata == {
|
|
"title": "Song One",
|
|
"artist": "Artist One",
|
|
"albumartist": "Album Artist",
|
|
"album": "Album One",
|
|
"track_number": 3,
|
|
"disc_number": 2,
|
|
}
|
|
|
|
|
|
def test_read_staging_file_metadata_falls_back_to_filename_track_number(monkeypatch, tmp_path):
|
|
file_path = tmp_path / "07 - Song Two.flac"
|
|
file_path.write_text("fake")
|
|
|
|
fake_mutagen = types.ModuleType("mutagen")
|
|
fake_mutagen.File = lambda path, easy=True: None
|
|
monkeypatch.setitem(sys.modules, "mutagen", fake_mutagen)
|
|
|
|
metadata = read_staging_file_metadata(str(file_path), file_path.name)
|
|
|
|
assert metadata["title"] == "07 - Song Two"
|
|
assert metadata["track_number"] == 7
|
|
assert metadata["disc_number"] == 1
|
|
|
|
|
|
def test_read_staging_file_metadata_returns_zero_track_when_unknown(monkeypatch, tmp_path):
|
|
"""Bare filename + no tags → track_number=0, not 1.
|
|
|
|
Pre-fix this returned 1 because the filename extractor's default
|
|
was 1. The bug caused every untagged file in an album-bundle
|
|
download to land in the staging cache with track_number=1, which
|
|
then short-circuited the downstream resolution chain that should
|
|
have picked up the real number from track_info.
|
|
"""
|
|
file_path = tmp_path / "Cha-La Head-Cha-La.flac"
|
|
file_path.write_text("fake")
|
|
|
|
fake_mutagen = types.ModuleType("mutagen")
|
|
fake_mutagen.File = lambda path, easy=True: None
|
|
monkeypatch.setitem(sys.modules, "mutagen", fake_mutagen)
|
|
|
|
metadata = read_staging_file_metadata(str(file_path), file_path.name)
|
|
|
|
assert metadata["track_number"] == 0
|
|
|
|
|
|
def test_read_staging_file_metadata_uses_filename_fallbacks_when_tags_are_invalid(monkeypatch, tmp_path):
|
|
file_path = tmp_path / "02 - Song Three.flac"
|
|
file_path.write_text("fake")
|
|
|
|
class DummyTags:
|
|
def __init__(self):
|
|
self.values = {
|
|
"title": [""],
|
|
"artist": "Artist One",
|
|
"albumartist": "",
|
|
"album": ["Album One"],
|
|
"tracknumber": ["not-a-number"],
|
|
"discnumber": ["bad/disc"],
|
|
}
|
|
|
|
def get(self, key, default=None):
|
|
return self.values.get(key, default)
|
|
|
|
fake_mutagen = types.ModuleType("mutagen")
|
|
fake_mutagen.File = lambda path, easy=True: DummyTags()
|
|
monkeypatch.setitem(sys.modules, "mutagen", fake_mutagen)
|
|
|
|
metadata = read_staging_file_metadata(str(file_path), file_path.name)
|
|
|
|
assert metadata == {
|
|
"title": "02 - Song Three",
|
|
"artist": "Artist One",
|
|
"albumartist": "Artist One",
|
|
"album": "Album One",
|
|
"track_number": 2,
|
|
"disc_number": 1,
|
|
}
|