SoundCloud sets (and similar) return flat-playlist entries without per-track artist/title. When a track Hit has no artist, download via an output template (-o <root>/%(artist,uploader,channel)s/<source>/...) so yt-dlp places the file under the real artist instead of "Unknown Artist". yt_download gains an optional outtmpl mode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
4.5 KiB
Python
94 lines
4.5 KiB
Python
import server.mf # noqa: F401 — loads musicfetch, registers musicfetch_core
|
|
import musicfetch_core as mf
|
|
|
|
|
|
# ---- _sanitize_source ----
|
|
def test_sanitize_source():
|
|
assert mf._sanitize_source("Youtube") == "youtube"
|
|
assert mf._sanitize_source("Soundcloud") == "soundcloud"
|
|
assert mf._sanitize_source("") == "downloads"
|
|
|
|
|
|
# ---- _entry_to_hit ----
|
|
def test_entry_to_hit_soundcloud_keeps_url_no_videoid():
|
|
h = mf._entry_to_hit({"id": "t1", "title": "Track", "uploader": "DJ",
|
|
"ie_key": "Soundcloud", "url": "https://soundcloud.com/dj/track"})
|
|
assert h.payload["extractor"] == "soundcloud"
|
|
assert h.payload["url"] == "https://soundcloud.com/dj/track"
|
|
assert h.payload["videoId"] is None
|
|
assert h.artist == "DJ"
|
|
|
|
|
|
def test_entry_to_hit_youtube_keeps_videoid():
|
|
h = mf._entry_to_hit({"id": "vid123", "title": "Song", "channel": "Chan",
|
|
"ie_key": "Youtube", "url": "https://youtube.com/watch?v=vid123"})
|
|
assert h.payload["extractor"] == "youtube"
|
|
assert h.payload["videoId"] == "vid123"
|
|
|
|
|
|
# ---- _track_url ----
|
|
def test_track_url_youtube_prefers_music_youtube():
|
|
h = mf.Hit(source="youtube", kind="track", title="T", artist="A",
|
|
payload={"videoId": "vid", "extractor": "youtube", "url": "https://youtube.com/watch?v=vid"})
|
|
assert mf._track_url(h) == "https://music.youtube.com/watch?v=vid"
|
|
|
|
|
|
def test_track_url_soundcloud_uses_native_url():
|
|
h = mf.Hit(source="youtube", kind="track", title="T", artist="A",
|
|
payload={"videoId": None, "extractor": "soundcloud", "url": "https://soundcloud.com/a/t"})
|
|
assert mf._track_url(h) == "https://soundcloud.com/a/t"
|
|
|
|
|
|
def test_track_url_ytmusic_search_hit_default_youtube():
|
|
# ytmusicapi search hits carry only videoId (no extractor) -> music.youtube.
|
|
h = mf.Hit(source="youtube", kind="track", title="T", artist="A", payload={"videoId": "vid"})
|
|
assert mf._track_url(h) == "https://music.youtube.com/watch?v=vid"
|
|
|
|
|
|
# ---- act_youtube routes to per-source folder ----
|
|
def test_act_youtube_soundcloud_folder(monkeypatch):
|
|
captured = {}
|
|
monkeypatch.setattr(mf, "yt_download",
|
|
lambda url, target, quality, dry_run, hit=None: captured.update(url=url, target=target) or True)
|
|
h = mf.Hit(source="youtube", kind="track", title="T", artist="DJ, Other",
|
|
payload={"videoId": None, "extractor": "soundcloud", "url": "https://soundcloud.com/dj/t"})
|
|
mf.act_youtube(h, "/media/music", "best", False)
|
|
assert captured["target"] == "/media/music/DJ/soundcloud" # first artist only
|
|
assert captured["url"] == "https://soundcloud.com/dj/t"
|
|
|
|
|
|
def test_act_youtube_youtube_folder(monkeypatch):
|
|
captured = {}
|
|
monkeypatch.setattr(mf, "yt_download",
|
|
lambda url, target, quality, dry_run, hit=None, outtmpl=None:
|
|
captured.update(target=target) or True)
|
|
h = mf.Hit(source="youtube", kind="track", title="T", artist="A",
|
|
payload={"videoId": "vid", "extractor": "youtube"})
|
|
mf.act_youtube(h, "/media/music", "best", False)
|
|
assert captured["target"] == "/media/music/A/youtube"
|
|
|
|
|
|
def test_act_youtube_unknown_artist_uses_metadata_template(monkeypatch):
|
|
captured = {}
|
|
monkeypatch.setattr(mf, "yt_download",
|
|
lambda url, target, quality, dry_run, hit=None, outtmpl=None:
|
|
captured.update(target=target, outtmpl=outtmpl) or True)
|
|
h = mf.Hit(source="youtube", kind="track", title="", artist="",
|
|
payload={"videoId": None, "extractor": "soundcloud", "url": "https://soundcloud.com/a/t"})
|
|
mf.act_youtube(h, "/media/music", "best", False)
|
|
assert captured["target"] is None
|
|
assert "%(artist,uploader,channel)s" in captured["outtmpl"]
|
|
assert captured["outtmpl"].endswith("/soundcloud/%(title)s [%(id)s].%(ext)s")
|
|
|
|
|
|
# ---- download_single per-source folder ----
|
|
def test_download_single_bandcamp_folder(monkeypatch):
|
|
monkeypatch.setattr(mf, "run_yt_dlp_get_metadata",
|
|
lambda url: {"title": "Song", "artist": "Band", "extractor": "Bandcamp"})
|
|
captured = {}
|
|
monkeypatch.setattr(mf, "yt_download",
|
|
lambda url, target, quality, dry_run, hit=None: captured.update(target=target) or True)
|
|
info = mf.download_single("https://band.bandcamp.com/track/song", "/media/music", "best", False)
|
|
assert captured["target"] == "/media/music/Band/bandcamp"
|
|
assert info == {"title": "Song", "artist": "Band", "ok": True}
|