diff --git a/.gitignore b/.gitignore index b09f8ea..05e9015 100644 --- a/.gitignore +++ b/.gitignore @@ -109,5 +109,6 @@ venv.bak/ .idea noresults.txt *.ini +*.bin *.txt *.json \ No newline at end of file diff --git a/spotify_to_ytmusic/cache.py b/spotify_to_ytmusic/cache.py new file mode 100644 index 0000000..f5aba69 --- /dev/null +++ b/spotify_to_ytmusic/cache.py @@ -0,0 +1,38 @@ +import bz2 +import json +from pathlib import Path +from typing import Dict + + +class Cache: + _cache: Dict[str, str] + filepath: Path = Path(__file__).parent.joinpath("cache.bin") + + def __init__(self): + self._cache = {} + if self.filepath.is_file(): + self._load(self.filepath) + + def __contains__(self, item): + return item in self._cache + + def __getitem__(self, key): + return self._cache[key] + + def __setitem__(self, key, value): + self._cache[key] = value + + def save(self): + self._save(self.filepath) + + def _load(self, path: Path): + with open(path, "rb") as file: + data = bz2.decompress(file.read()) + + self._cache.update(json.loads(data)) + + def _save(self, path: Path): + byte_data = json.dumps(self._cache) + compressed_byte_data = bz2.compress(byte_data.encode("utf8")) + with open(path, "wb") as file: + file.write(compressed_byte_data) diff --git a/spotify_to_ytmusic/controllers.py b/spotify_to_ytmusic/controllers.py index 24856ee..0797492 100644 --- a/spotify_to_ytmusic/controllers.py +++ b/spotify_to_ytmusic/controllers.py @@ -48,7 +48,7 @@ def all(args): ) _print_success(p["name"], playlist_id) except Exception as ex: - print(f"Could not transfer playlist {p['name']}. {str(ex)}") + raise Exception(f"Could not transfer playlist {p['name']}") from ex def _create_ytmusic(args, playlist, ytmusic): diff --git a/spotify_to_ytmusic/settings.py b/spotify_to_ytmusic/settings.py index 58f984e..a6433d1 100644 --- a/spotify_to_ytmusic/settings.py +++ b/spotify_to_ytmusic/settings.py @@ -12,9 +12,7 @@ def __init__(self, filepath: Optional[Path] = None): if filepath: self.filepath = filepath if not self.filepath.is_file(): - raise FileNotFoundError( - f"No settings.ini not found! Please run \n\n spotify_to_ytmusic setup" - ) + raise FileNotFoundError(f"No settings.ini found! Please run spotify_to_ytmusic setup") self.config.read(self.filepath) def __getitem__(self, key): diff --git a/spotify_to_ytmusic/ytmusic.py b/spotify_to_ytmusic/ytmusic.py index ccd6d5c..a6a9c94 100644 --- a/spotify_to_ytmusic/ytmusic.py +++ b/spotify_to_ytmusic/ytmusic.py @@ -4,6 +4,7 @@ from ytmusicapi import YTMusic +from spotify_to_ytmusic.cache import Cache from spotify_to_ytmusic.match import get_best_fit_song_id from spotify_to_ytmusic.settings import Settings @@ -24,23 +25,28 @@ def search_songs(self, tracks): videoIds = [] songs = list(tracks) notFound = list() - print("Searching YouTube...") - for i, song in enumerate(songs): - name = re.sub(r" \(feat.*\..+\)", "", song["name"]) - query = song["artist"] + " " + name - query = query.replace(" &", "") - result = self.api.search(query) - if len(result) == 0: - notFound.append(query) - else: - targetSong = get_best_fit_song_id(result, song) - if targetSong is None: - notFound.append(query) + cache = Cache() + try: + for i, song in enumerate(songs): + if not i % 10: + print(f"YouTube tracks: {i}/{len(songs)}") + name = re.sub(r" \(feat.*\..+\)", "", song["name"]) + query = song["artist"] + " " + name + query = query.replace(" &", "") + if query in cache: + targetSong = cache[query] else: - videoIds.append(targetSong) + result = self.api.search(query) + if not len(result) or not (targetSong := get_best_fit_song_id(result, song)): + notFound.append(query) + continue + + cache[query] = targetSong + + videoIds.append(targetSong) - if i > 0 and i % 10 == 0: - print(f"YouTube tracks: {i}/{len(songs)}") + finally: + cache.save() with open(path + "noresults_youtube.txt", "w", encoding="utf-8") as f: f.write("\n".join(notFound)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5316c71..5375134 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -57,7 +57,7 @@ def test_setup(self): example_path = Settings.filepath.parent.joinpath("settings.ini.example") shutil.copy(example_path, tmp_path) with mock.patch("sys.argv", ["", "setup"]), mock.patch( - "builtins.input", return_value="3" + "builtins.input", side_effect=["3", "a", "b", "yes", ""] ), mock.patch( "ytmusicapi.auth.oauth.YTMusicOAuth.get_token_from_code", return_value=json.loads(Settings()["youtube"]["headers"]), @@ -65,8 +65,8 @@ def test_setup(self): main() assert tmp_path.is_file() settings = Settings() - assert settings["spotify"]["client_id"] == "3" - assert settings["spotify"]["client_secret"] == "3" + assert settings["spotify"]["client_id"] == "a" + assert settings["spotify"]["client_secret"] == "b" tmp_path.unlink() with mock.patch("sys.argv", ["", "setup", "--file", example_path.as_posix()]), mock.patch(