initial commit

This commit is contained in:
Grizouille
2025-11-06 22:42:49 +01:00
parent 72dfd1e21e
commit 6399ab4af2
50 changed files with 4044 additions and 233 deletions

View File

@@ -0,0 +1,367 @@
import os
import unittest
import pytest
from pathlib import Path
from mutagen.mp3 import MP3
from mutagen.flac import FLAC
from mutagen import MutagenError
from deezer_downloader.configuration import load_config
if "DEEZER_DOWNLOADER_CONFIG_FILE" in os.environ:
config_file = Path(os.environ["DEEZER_DOWNLOADER_CONFIG_FILE"])
else:
config_file = (Path(__file__).parents[1] / Path("deezer_downloader") / Path("cli") / Path("deezer-downloader.ini.template")).resolve()
load_config(config_file)
from deezer_downloader.configuration import config
from deezer_downloader.deezer import init_deezer_session, TYPE_TRACK, TYPE_ALBUM
from deezer_downloader.deezer import deezer_search, get_song_infos_from_deezer_website, parse_deezer_playlist, download_song, get_deezer_favorites
from deezer_downloader.deezer import Deezer404Exception, DeezerApiException
from deezer_downloader.spotify import get_songs_from_spotify_website, SpotifyWebsiteParserException, parse_uri, SpotifyInvalidUrlException
from deezer_downloader.youtubedl import youtubedl_download, YoutubeDLFailedException, DownloadedFileNotFoundException
known_song_keys = ["SNG_ID", "DURATION", "MD5_ORIGIN", "SNG_TITLE", "TRACK_NUMBER",
"ALB_PICTURE", "MEDIA_VERSION", "ART_NAME", "ALB_TITLE"]
init_deezer_session(config['proxy']['server'],
config["deezer"]["quality"])
class TestDeezerMethods(unittest.TestCase):
# BEGIN: TEST deezer_search
def test_deezer_search_song_valid(self):
songs = deezer_search("Großstadtgeflüster diadem", TYPE_TRACK)
self.assertIsInstance(songs, list)
s = songs[0]
self.assertSetEqual(set(s.keys()), {'preview_url', 'artist', 'id', 'id_type', 'album_id', 'title', 'img_url', 'album'})
self.assertTrue(s['id'], '730393272')
self.assertTrue(s['title'], 'Diadem')
self.assertTrue(s['artist'], 'Grossstadtgeflüster')
self.assertTrue(s['album'], 'Tips & Tricks')
self.assertTrue(s['preview_url'], 'https://cdns-preview-6.dzcdn.net/stream/c-6abdd540dd7e7f02d2c4d21537709c23-3.mp3')
self.assertTrue(s['album_id'], '107261872')
self.assertTrue(s['id_type'], 'track')
def test_deezer_search_album_valid(self):
albums = deezer_search("Coldplay", TYPE_ALBUM)
self.assertIsInstance(albums, list)
for album in albums:
self.assertSetEqual(set(album.keys()), {'id', 'id_type', 'album', 'album_id', 'img_url', 'artist', 'title', 'preview_url'})
found_album_names = [x['album'] for x in albums]
known_album_names = ['Parachutes', 'X&Y', 'A Head Full of Dreams']
for known_album_name in known_album_names:
self.assertIn(known_album_name, found_album_names)
def test_deezer_search_invalid_search_typ(self):
songs = deezer_search("Coldplay", "this is a wrong search type")
self.assertIsInstance(songs, list)
self.assertListEqual(songs, [])
def test_deezer_search_song_invalid_no_song_found(self):
songs = deezer_search("8f49834zf934fdshfkhejw", TYPE_TRACK)
self.assertIsInstance(songs, list)
self.assertListEqual(songs, [])
def test_deezer_search_album_invalid_no_song_found(self):
songs = deezer_search("8f49834zf934fdshfkhejw", TYPE_ALBUM)
self.assertIsInstance(songs, list)
self.assertListEqual(songs, [])
# END: TEST deezer_search
# BEGIN: TEST get_song_infos_from_deezer_website
def test_get_track_infos_from_website(self):
song = get_song_infos_from_deezer_website(TYPE_TRACK, "69962764")
self.assertIsInstance(song, dict)
song_keys = list(song.keys())
for key in known_song_keys:
self.assertIn(key, song_keys)
self.assertEqual(song["SNG_ID"], "69962764")
self.assertEqual(song["ART_NAME"], "The Clash")
self.assertEqual(song["SNG_TITLE"], "Should I Stay or Should I Go")
# MD5_ORIGIN changes over time
#self.assertEqual(song["MD5_ORIGIN"], "df51967a8b9b88d079fb0d9f4a0c1c38")
self.assertEqual(len(song["MD5_ORIGIN"]), 32)
def test_get_album_infos_from_website(self):
songs = get_song_infos_from_deezer_website(TYPE_ALBUM, "1434890")
self.assertIsInstance(songs, list)
self.assertEqual(len(songs), 15)
for song in songs:
song_keys = list(song.keys())
for key in known_song_keys:
self.assertIn(key, song_keys)
self.assertEqual(songs[0]["SNG_ID"], "15523769")
self.assertEqual(songs[0]["ART_NAME"], "System of A Down")
self.assertEqual(songs[0]["SNG_TITLE"], "Prison Song")
self.assertEqual(len(songs[14]["MD5_ORIGIN"]), 32)
self.assertEqual(songs[1]["SNG_ID"], "15523770")
self.assertEqual(songs[1]["ART_NAME"], "System of A Down")
self.assertEqual(songs[1]["SNG_TITLE"], "Needles")
self.assertEqual(songs[2]["SNG_ID"], "15523772")
self.assertEqual(songs[2]["ART_NAME"], "System of A Down")
self.assertEqual(songs[2]["SNG_TITLE"], "Deer Dance")
self.assertEqual(songs[3]["SNG_ID"], "15523775")
self.assertEqual(songs[3]["ART_NAME"], "System of A Down")
self.assertEqual(songs[3]["SNG_TITLE"], "Jet Pilot")
self.assertEqual(songs[4]["SNG_ID"], "15523778")
self.assertEqual(songs[4]["ART_NAME"], "System of A Down")
self.assertEqual(songs[4]["SNG_TITLE"], "X")
self.assertEqual(songs[5]["SNG_ID"], "15523781")
self.assertEqual(songs[5]["ART_NAME"], "System of A Down")
self.assertEqual(songs[5]["SNG_TITLE"], "Chop Suey!")
self.assertEqual(songs[6]["SNG_ID"], "15523784")
self.assertEqual(songs[6]["ART_NAME"], "System of A Down")
self.assertEqual(songs[6]["SNG_TITLE"], "Bounce")
self.assertEqual(songs[7]["SNG_ID"], "15523788")
self.assertEqual(songs[7]["ART_NAME"], "System of A Down")
self.assertEqual(songs[7]["SNG_TITLE"], "Forest")
self.assertEqual(songs[8]["SNG_ID"], "15523790")
self.assertEqual(songs[8]["ART_NAME"], "System of A Down")
self.assertEqual(songs[8]["SNG_TITLE"], "ATWA")
self.assertEqual(songs[9]["SNG_ID"], "15523791")
self.assertEqual(songs[9]["ART_NAME"], "System of A Down")
self.assertEqual(songs[9]["SNG_TITLE"], "Science")
self.assertEqual(songs[10]["SNG_ID"], "15523792")
self.assertEqual(songs[10]["ART_NAME"], "System of A Down")
self.assertEqual(songs[10]["SNG_TITLE"], "Shimmy")
self.assertEqual(songs[11]["SNG_ID"], "15523793")
self.assertEqual(songs[11]["ART_NAME"], "System of A Down")
self.assertEqual(songs[11]["SNG_TITLE"], "Toxicity")
self.assertEqual(songs[12]["SNG_ID"], "15523796")
self.assertEqual(songs[12]["ART_NAME"], "System of A Down")
self.assertEqual(songs[12]["SNG_TITLE"], "Psycho")
self.assertEqual(songs[13]["SNG_ID"], "15523799")
self.assertEqual(songs[13]["ART_NAME"], "System of A Down")
self.assertEqual(songs[13]["SNG_TITLE"], "Aerials")
self.assertEqual(songs[14]["SNG_ID"], "15523803")
self.assertEqual(songs[14]["ART_NAME"], "System of A Down")
self.assertEqual(songs[14]["SNG_TITLE"], "Arto")
def test_get_invalid_track_infos_from_website(self):
with self.assertRaises(Deezer404Exception):
get_song_infos_from_deezer_website(TYPE_TRACK, "thisdoesnotexist")
def test_get_invalid_album_infos_from_website(self):
with self.assertRaises(Deezer404Exception):
get_song_infos_from_deezer_website(TYPE_ALBUM, "thisdoesnotexist")
# END: TEST get_song_infos_from_deezer_website
# BEGIN: parse_deezer_playlist
def _call_parse_valid_deezer_playlist(self, playlist):
playlist_name, songs = parse_deezer_playlist(playlist)
self.assertEqual(playlist_name, "test-playlist")
self.assertIsInstance(songs, list)
self.assertEqual(len(songs), 2)
for song in songs:
song_keys = list(song.keys())
for key in known_song_keys:
self.assertIn(key, song_keys)
self.assertEqual(songs[0]["SNG_ID"], "113951680")
self.assertEqual(songs[0]["ART_NAME"], 'Fredrika Stahl')
self.assertEqual(songs[0]["SNG_TITLE"], 'Make a Change')
# MD5_ORIGIN is only there if we are logged in
self.assertEqual(songs[0]["MD5_ORIGIN"], '57250623592ef44c8caeead79917f7e5')
def test_parse_valid_deezer_playlist_with_url(self):
playlist_url = "https://www.deezer.com/de/playlist/7639370122"
self._call_parse_valid_deezer_playlist(playlist_url)
def test_parse_valid_deezer_playlist_with_id(self):
playlist_id = "7639370122"
self._call_parse_valid_deezer_playlist(playlist_id)
def test_parse_invalid_deezer_playlist_with_id(self):
invalid_playlist_id = "999999999999999999999999999"
with self.assertRaises(DeezerApiException):
playlist_name, songs = parse_deezer_playlist(invalid_playlist_id)
def test_parse_invalid_input_for_deezer_playlist_with_id(self):
invalid_playlist_id = "!\"§$%&/((;-';k(()=+ü\\?"
with self.assertRaises(DeezerApiException):
playlist_name, songs = parse_deezer_playlist(invalid_playlist_id)
def test_parse_invalid_deezer_playlist_with_url(self):
invalid_playlist_url = "https://www.heise.de"
with self.assertRaises(DeezerApiException):
playlist_name, songs = parse_deezer_playlist(invalid_playlist_url)
# END: parse_deezer_playlist
# BEGIN: get_deezer_favorites
def test_get_deezer_favorites_userid_not_numeric(self):
user_id = "123notnumeric"
with self.assertRaises(Exception):
get_deezer_favorites(user_id)
def test_get_deezer_favorites_userid_api_error(self):
user_id = "0"
with self.assertRaises(Exception):
get_deezer_favorites(user_id)
def test_get_deezer_favorites_userid_valid(self):
user_id = "2517244282" # own of test (works)
songs = get_deezer_favorites(user_id)
self.assertIsInstance(songs, list)
for song in songs:
self.assertIsInstance(song, int)
# END: get_deezer_favorites
# BEGIN: download_song
@pytest.mark.skipif(config["deezer"].get("quality", "mp3") == "flac", reason="skiping deezer mp3 download. Quality set to flac")
def test_download_song_valid_mp3(self):
def check_mp3_metadata_deezer_song(test_file: Path):
self.assertTrue(test_song.exists())
try:
audio = MP3(test_file)
except MutagenError as e:
pytest.fail(f"File is not in mp3 format: {e}")
self.assertEqual(audio['TPE1'].text[0], 'Faber')
self.assertEqual(audio['TALB'].text[0], 'Alles Gute')
self.assertEqual(audio['TIT2'].text[0], 'Tausendfrankenlang')
self.assertEqual(audio['TRCK'].text[0], '4')
self.assertEqual(audio['TDRC'].text[0].year, 2015)
self.assertEqual(audio['TPOS'].text[0], '1')
self.assertTrue('APIC:Cover' in audio)
test_song = Path("/tmp/song-548935.mp3")
test_song.unlink(missing_ok=True)
song_infos = deezer_search("faber tausendfrankenlang", TYPE_TRACK)[0]
song = get_song_infos_from_deezer_website(TYPE_TRACK, song_infos['id'])
download_song(song, test_song.as_posix())
check_mp3_metadata_deezer_song(test_song)
test_song.unlink()
@pytest.mark.skipif(config["deezer"].get("quality", "mp3") == "mp3", reason="skiping deezer flac download. Quality set to mp3")
def test_download_song_valid_flac(self):
def check_flac_metadata_deezer_song(test_file: Path):
try:
audio = FLAC(test_file)
except MutagenError as e:
pytest.fail(f"File is not in mp3 format: {e}")
assert audio["artist"] == ["Faber",]
assert audio["title"] == ['Tausendfrankenlang',]
assert audio["album"] == ['Alles Gute',]
assert audio["tracknumber"] == ['4',]
assert audio["discnumber"] == ["1",]
assert len(audio.pictures) == 1
assert audio.pictures[0].data[:5] == b'\xff\xd8\xff\xe0\x00'
test_song = Path("/tmp/song-548935.flac")
test_song.unlink(missing_ok=True)
song_infos = deezer_search("faber tausendfrankenlang", TYPE_TRACK)[0]
song = get_song_infos_from_deezer_website(TYPE_TRACK, song_infos['id'])
download_song(song, test_song.as_posix())
check_flac_metadata_deezer_song(test_song)
test_song.unlink()
def test_download_song_invalid_song_type(self):
with self.assertRaises(AssertionError):
download_song("this sould be a dict", "/not/relevant/outputfile.mp3")
# END: download_song
class TestSpotifyMethods(unittest.TestCase):
# BEGIN parse_uri
def test_parse_url_spotify(self):
res = parse_uri("spotify:album:Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "album")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
res = parse_uri("spotify:playlist:Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "playlist")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
res = parse_uri("spotify:track:Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "track")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
def test_parse_url_open_domain(self):
res = parse_uri("https://open.spotify.com/track/Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "track")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
def test_parse_url_play_domain(self):
res = parse_uri("https://play.spotify.com/track/Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "track")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
def test_parse_url_embed_domain(self):
res = parse_uri("https://embed.spotify.com/?uri=spotify:track:Hksdhfaif23ffushef9823")
self.assertEqual(res['type'], "track")
self.assertEqual(res['id'], "Hksdhfaif23ffushef9823")
# END parse_uri
# BEGIN test invalid
def test_spotify_parser_invalid_playlist_id(self):
playlist_id = "thisdoesnotexist"
with self.assertRaises(SpotifyWebsiteParserException):
get_songs_from_spotify_website(playlist_id, None)
def test_spotify_parser_invalid_playlist_url(self):
playlist_url = "https://www.heise.de"
with self.assertRaises(SpotifyInvalidUrlException):
get_songs_from_spotify_website(playlist_url, None)
# END test invalid
# BEGIN valid stuff
def check_parse_spotify_playlist_website(self, playlist):
songs = get_songs_from_spotify_website(playlist, None)
self.assertIn("Cyndi Lauper Time After Time", songs)
def test_spotify_parser_valid_playlist_embed_url(self):
playlist_url = "https://open.spotify.com/embed/playlist/0wl9Q3oedquNlBAJ4MGZtS"
self.check_parse_spotify_playlist_website(playlist_url)
def test_spotify_parser_valid_playlist_url(self):
playlist_url = "https://open.spotify.com/playlist/0wl9Q3oedquNlBAJ4MGZtS"
self.check_parse_spotify_playlist_website(playlist_url)
def test_spotify_parser_valid_playlist_id(self):
playlist_id = "0wl9Q3oedquNlBAJ4MGZtS"
self.check_parse_spotify_playlist_website(playlist_id)
# END valid stuff
class TestYoutubeMethods(unittest.TestCase):
is_github_ci = len(os.environ.get("GITHUB_ACTION", "")) > 0
@pytest.mark.xfail(is_github_ci, reason="Fails with 'Sign in to confirm youre not a bot. This helps protect our community. Learn more'", raises=YoutubeDLFailedException)
def test_youtube_dl_valid_url(self):
out_file = Path("/tmp/Pharrell Williams - Happy (Video).mp3")
out_file.unlink(missing_ok=True)
url = "https://www.youtube.com/watch?v=ZbZSe6N_BXs"
destination_file = youtubedl_download(url, "/tmp")
self.assertEqual(out_file.as_posix(), destination_file)
self.assertTrue(out_file.exists())
try:
audio = MP3(out_file)
self.assertEqual(audio['TPE1'].text[0], 'Pharrell Williams')
self.assertEqual(audio['TIT2'].text[0], 'Pharrell Williams - Happy (Video)')
except MutagenError as e:
pytest.fail(f"File is not in mp3 format: {e}")
out_file.unlink()
def test_youtube_dl_invalid_url(self):
url = "https://www.heise.de"
with self.assertRaises(YoutubeDLFailedException):
youtubedl_download(url, "/tmp")
@pytest.mark.xfail(is_github_ci, reason="Fails with 'Sign in to confirm youre not a bot. This helps protect our community. Learn more'", raises=YoutubeDLFailedException)
def test_youtube_dl_command_execution(self):
url = "https://www.youtube.com/watch?v=ZbZSe6N_BXs&$(touch /tmp/pwned.txt)"
try:
youtubedl_download(url, "/tmp")
except DownloadedFileNotFoundException:
pytest.xfail("Fails if the file already exists from the previous test. TODO")
pwn_succeeded = os.path.exists("/tmp/pwned.txt")
self.assertEqual(pwn_succeeded, False)
if __name__ == '__main__':
unittest.main()