Remove tests/ from repo, update .gitignore, improve ripper

- Remove tests/ directory from version control (added to .gitignore)
- Add .idea/ to .gitignore
- Ripper: CDDB lookup, non-interactive mode, English UI, file renaming
- Config: abcde format mapping, per-format quality options
- CLI: English help texts, new --no-cddb / --pipes / --parallel / --quality options

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-02-17 17:35:34 +01:00
commit 851dbf3a46
9 changed files with 511 additions and 217 deletions

View file

View file

@ -1,42 +0,0 @@
"""Tests für die Datenmodelle."""
from musiksammlung.models import Album
def test_album_folder_name_with_year():
album = Album(artist="Test", album="Mein Album", year=1987, discs=[])
assert album.folder_name == "Mein Album (1987)"
def test_album_folder_name_without_year():
album = Album(artist="Test", album="Mein Album", year=None, discs=[])
assert album.folder_name == "Mein Album"
def test_sanitize_name():
album = Album(artist='Art:ist', album='Al/bum?', year=None, discs=[])
assert ":" not in album.artist
assert "/" not in album.album
assert "?" not in album.album
def test_album_from_json():
data = {
"artist": "Die Toten Hosen",
"album": "Opium fürs Volk",
"year": 1996,
"discs": [
{
"disc_number": 1,
"tracks": [
{"track_number": 1, "title": "Bonnie & Clyde"},
{"track_number": 2, "title": "Zehn kleine Jägermeister"},
],
}
],
}
album = Album.model_validate(data)
assert album.artist == "Die Toten Hosen"
assert len(album.discs) == 1
assert len(album.discs[0].tracks) == 2
assert album.discs[0].tracks[1].title == "Zehn kleine Jägermeister"

View file

@ -1,78 +0,0 @@
"""Tests für den Organizer."""
from pathlib import Path
from musiksammlung.models import Album, Disc, Track
from musiksammlung.organizer import build_mapping, discover_audio_files
def test_discover_audio_files(tmp_path: Path):
"""Findet und sortiert Audiodateien korrekt."""
(tmp_path / "Track_03.flac").touch()
(tmp_path / "Track_01.flac").touch()
(tmp_path / "Track_02.flac").touch()
(tmp_path / "cover.jpg").touch() # soll ignoriert werden
files = discover_audio_files(tmp_path)
assert len(files) == 3
assert files[0].name == "Track_01.flac"
assert files[2].name == "Track_03.flac"
def test_build_mapping_single_disc(tmp_path: Path):
"""Mapping für ein Single-CD-Album."""
(tmp_path / "Track_01.flac").touch()
(tmp_path / "Track_02.flac").touch()
album = Album(
artist="TestArtist",
album="TestAlbum",
year=2000,
discs=[
Disc(
disc_number=1,
tracks=[
Track(track_number=1, title="Erster Song"),
Track(track_number=2, title="Zweiter Song"),
],
)
],
)
output = tmp_path / "output"
mapping = build_mapping(album, tmp_path, output)
assert len(mapping) == 2
targets = list(mapping.values())
assert targets[0].name == "01 Erster Song.flac"
assert targets[1].name == "02 Zweiter Song.flac"
# Single-Disc: kein CD1-Unterordner
assert "CD1" not in str(targets[0])
def test_build_mapping_multi_disc(tmp_path: Path):
"""Mapping für ein Multi-CD-Album."""
cd1 = tmp_path / "CD1"
cd2 = tmp_path / "CD2"
cd1.mkdir()
cd2.mkdir()
(cd1 / "Track_01.flac").touch()
(cd2 / "Track_01.flac").touch()
album = Album(
artist="Artist",
album="Box Set",
year=1999,
discs=[
Disc(disc_number=1, tracks=[Track(track_number=1, title="Song A")]),
Disc(disc_number=2, tracks=[Track(track_number=1, title="Song B")]),
],
)
output = tmp_path / "output"
mapping = build_mapping(album, tmp_path, output)
assert len(mapping) == 2
targets = list(mapping.values())
assert "CD1" in str(targets[0])
assert "CD2" in str(targets[1])

View file

@ -1,37 +0,0 @@
"""Tests für die Playlist-Generierung."""
from pathlib import Path
from musiksammlung.models import Album, Disc, Track
from musiksammlung.playlist import generate_playlist
def test_generate_playlist_single_disc(tmp_path: Path):
"""Erzeugt eine M3U-Playlist für ein Single-CD-Album."""
album = Album(
artist="Artist",
album="TestAlbum",
year=2000,
discs=[
Disc(
disc_number=1,
tracks=[
Track(track_number=1, title="Song Eins"),
Track(track_number=2, title="Song Zwei"),
],
)
],
)
# Dummy-Audiodateien anlegen
(tmp_path / "01 Song Eins.flac").touch()
(tmp_path / "02 Song Zwei.flac").touch()
playlist_path = generate_playlist(album, tmp_path)
assert playlist_path.exists()
content = playlist_path.read_text()
assert "#EXTM3U" in content
assert "01 Song Eins.flac" in content
assert "02 Song Zwei.flac" in content
# Kein CD-Prefix bei Single-Disc
assert "CD1/" not in content

View file

@ -1,37 +0,0 @@
"""Tests für die Vision-LLM JSON-Extraktion."""
import pytest
from musiksammlung.vision_llm import _extract_json
def test_extract_pure_json():
text = '{"artist": "Test", "album": "Album"}'
assert '"Test"' in _extract_json(text)
def test_extract_json_from_markdown_block():
text = 'Hier ist das Ergebnis:\n```json\n{"artist": "Test"}\n```\nFertig.'
assert '"Test"' in _extract_json(text)
def test_extract_json_with_thinking_tags():
text = '<think>Ich denke nach...</think>\n{"artist": "Test", "album": "X"}'
result = _extract_json(text)
assert '"Test"' in result
def test_extract_json_with_surrounding_text():
text = 'Das JSON:\n{"artist": "A", "album": "B"}\nEnde.'
result = _extract_json(text)
assert '"A"' in result
def test_extract_json_empty_raises():
with pytest.raises(ValueError, match="Leere Antwort"):
_extract_json("")
def test_extract_json_no_json_raises():
with pytest.raises(ValueError, match="Kein JSON"):
_extract_json("Hier ist kein JSON, nur Text.")