spots-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. spots_cli/__init__.py +125 -0
  2. spots_cli/app/__init__.py +5 -0
  3. spots_cli/app/domain_resolver.py +157 -0
  4. spots_cli/app/downloader.py +78 -0
  5. spots_cli/app/media_resolver.py +180 -0
  6. spots_cli/app/spotify_playlist_compilation.py +251 -0
  7. spots_cli/app/youtube_user_playlist.py +215 -0
  8. spots_cli/bootstrap/__init__.py +1 -0
  9. spots_cli/bootstrap/container.py +230 -0
  10. spots_cli/cli.py +42 -0
  11. spots_cli/clients/__init__.py +5 -0
  12. spots_cli/clients/deezer.py +80 -0
  13. spots_cli/clients/secrets_manager.py +25 -0
  14. spots_cli/clients/spotify.py +79 -0
  15. spots_cli/clients/youtube.py +66 -0
  16. spots_cli/clients/ytdl.py +72 -0
  17. spots_cli/config/__init__.py +0 -0
  18. spots_cli/core/__init__.py +6 -0
  19. spots_cli/core/history_manager.py +39 -0
  20. spots_cli/core/lyrics_finder.py +58 -0
  21. spots_cli/core/pattern_matcher.py +226 -0
  22. spots_cli/core/video_converter.py +146 -0
  23. spots_cli/core/web_scraper.py +82 -0
  24. spots_cli/core/youtube_extractor.py +162 -0
  25. spots_cli/engine/__init__.py +3 -0
  26. spots_cli/engine/file_storage.py +347 -0
  27. spots_cli/engine/persistence_model.py +3 -0
  28. spots_cli/engine/retry.py +34 -0
  29. spots_cli/integrations/__init__.py +1 -0
  30. spots_cli/integrations/spotify_user_playlist_modify.py +47 -0
  31. spots_cli/models/__init__.py +24 -0
  32. spots_cli/models/artist_metadata.py +23 -0
  33. spots_cli/models/errors.py +69 -0
  34. spots_cli/models/helper_models.py +25 -0
  35. spots_cli/models/media_resource.py +20 -0
  36. spots_cli/models/metadata.py +36 -0
  37. spots_cli/models/metadata_provider.py +52 -0
  38. spots_cli/models/playlist_info.py +27 -0
  39. spots_cli/models/search_provider.py +65 -0
  40. spots_cli/models/sentinel.py +4 -0
  41. spots_cli/models/yt_video_info.py +29 -0
  42. spots_cli/scripts/__init__.py +2 -0
  43. spots_cli/scripts/add_to_history.py +45 -0
  44. spots_cli/scripts/bootstrap.py +92 -0
  45. spots_cli/scripts/duplicate_songs_remover.py +54 -0
  46. spots_cli/services/__init__.py +8 -0
  47. spots_cli/services/deezer_metadata_service.py +108 -0
  48. spots_cli/services/deezer_search_service.py +132 -0
  49. spots_cli/services/provider_search_container.py +67 -0
  50. spots_cli/services/providers_metadata.py +22 -0
  51. spots_cli/services/spotify_metadata_service.py +151 -0
  52. spots_cli/services/spotify_search_service.py +228 -0
  53. spots_cli/services/youtube_metadata_service.py +73 -0
  54. spots_cli/services/youtube_search_service.py +211 -0
  55. spots_cli/setup_env.py +118 -0
  56. spots_cli/utils/__init__.py +4 -0
  57. spots_cli/utils/config_dir.py +17 -0
  58. spots_cli/utils/detect_browser.py +30 -0
  59. spots_cli/utils/fetch.py +73 -0
  60. spots_cli/utils/search_fallbacks.py +21 -0
  61. spots_cli-0.1.0.dist-info/METADATA +146 -0
  62. spots_cli-0.1.0.dist-info/RECORD +65 -0
  63. spots_cli-0.1.0.dist-info/WHEEL +4 -0
  64. spots_cli-0.1.0.dist-info/entry_points.txt +2 -0
  65. spots_cli-0.1.0.dist-info/licenses/LICENSE +675 -0
spots_cli/setup_env.py ADDED
@@ -0,0 +1,118 @@
1
+ from os.path import exists
2
+ from typing import Literal
3
+
4
+ from spots_cli.utils import get_config_path, create_config_dir
5
+
6
+ FILENAME = get_config_path() / ".env"
7
+
8
+
9
+ def save_env(variables: dict[str, str]):
10
+ file_data = ""
11
+
12
+ for k, v in variables.items():
13
+ file_data += f"{k}={v}\n"
14
+
15
+ with open(FILENAME, "w") as f:
16
+ f.write(file_data)
17
+
18
+
19
+ def required_input(prompt: str):
20
+ value = ""
21
+
22
+ while not value:
23
+ value = input(f"[REQUIRED] {prompt}: ").strip()
24
+
25
+ return value
26
+
27
+
28
+ def optional_input(prompt: str, default: str | None = None):
29
+ value = input(
30
+ f"[OPTIONAL] {prompt}" + (f" [{default}]" if default else "") + ": "
31
+ ).strip()
32
+ return value or default
33
+
34
+
35
+ def main():
36
+ if exists(FILENAME):
37
+ setup_again = input(
38
+ "Environment variables already exist.\n"
39
+ "Would you like to reconfigure them? (press Enter to cancel): "
40
+ ).strip()
41
+
42
+ if not setup_again:
43
+ print("Setup cancelled.\n")
44
+ return
45
+
46
+ print("\n=== Environment Configuration ===\n")
47
+
48
+ create_config_dir()
49
+
50
+ static_cover_name = "https://i.postimg.cc/VvZTvvMV/single-cover.jpg"
51
+ static_playlist_cover_name = "https://i.postimg.cc/m2XzZsnq/youtube-playlist.jpg"
52
+ flask_port = 5000
53
+ main_provider: Literal["deezer", "spotify"] = "deezer"
54
+ spotify_features_available = "False"
55
+ youtube_account_access = "False"
56
+ avatar_icon = "https://i.postimg.cc/HnpL9ZrZ/avatar.jpg"
57
+ environment_mode = "PRODUCTION"
58
+
59
+ variables_map = {
60
+ "flask_port": flask_port,
61
+ "static_cover_name": static_cover_name,
62
+ "static_playlist_cover_name": static_playlist_cover_name,
63
+ "spotify_features_available": spotify_features_available,
64
+ "youtube_account_access": youtube_account_access,
65
+ "main_provider": main_provider,
66
+ "avatar_icon": avatar_icon,
67
+ "environment_mode": environment_mode,
68
+ }
69
+
70
+ print("---- Spotify Configuration ----")
71
+ has_spotify_membership = (
72
+ input(
73
+ "Do you have a paid Spotify API membership? (required for Spotify features) [y/N]: "
74
+ )
75
+ .strip()
76
+ .lower()
77
+ )
78
+
79
+ if has_spotify_membership == "y":
80
+ spotify_username = required_input("Spotify username")
81
+ spotify_client_id = required_input("Spotify client ID")
82
+ spotify_client_secret = required_input("Spotify client secret")
83
+
84
+ default_redirect = "https://open.spotify.com/?"
85
+ spotify_redirect_uri = optional_input("Spotify redirect URI", default_redirect)
86
+
87
+ variables_map["username"] = spotify_username
88
+ variables_map["SPOTIPY_CLIENT_ID"] = spotify_client_id
89
+ variables_map["SPOTIPY_CLIENT_SECRET"] = spotify_client_secret
90
+ variables_map["SPOTIPY_REDIRECT_URI"] = spotify_redirect_uri
91
+
92
+ variables_map["spotify_features_available"] = "True"
93
+ variables_map["main_provider"] = "spotify"
94
+
95
+ print("\n---- Genius (Lyrics) Configuration ----")
96
+ has_genius_key = (
97
+ input("Do you have a Genius API key? (optional, for lyrics) [y/N]: ")
98
+ .strip()
99
+ .lower()
100
+ )
101
+
102
+ if has_genius_key == "y":
103
+ genius_key = required_input("Genius API key")
104
+ variables_map["lyricsgenius_key"] = genius_key
105
+
106
+ print("\n---- YouTube Configuration ----")
107
+ use_youtube_features = (
108
+ input("Enable YouTube account features (likes, library access)? [y/N]: ")
109
+ .strip()
110
+ .lower()
111
+ )
112
+
113
+ if use_youtube_features == "y":
114
+ variables_map["youtube_account_access"] = "True"
115
+
116
+ save_env(variables_map)
117
+
118
+ print("\n✅ Environment variables have been successfully configured.\n")
@@ -0,0 +1,4 @@
1
+ from spots_cli.utils.config_dir import get_config_path, create_config_dir
2
+ from spots_cli.utils.detect_browser import detect_browser
3
+ from spots_cli.utils.fetch import fetch_data, FetchResponseFailure, FetchResponseSuccess
4
+ from spots_cli.utils.search_fallbacks import search_fallbacks
@@ -0,0 +1,17 @@
1
+ import os
2
+ from pathlib import Path
3
+ import sys
4
+
5
+
6
+ def get_config_path() -> Path:
7
+ if sys.platform == "win32":
8
+ return Path(os.getenv("APPDATA", Path.home() / "AppData/Roaming")) / "spots_cli"
9
+ elif sys.platform == "darwin":
10
+ return Path.home() / "Library/Application Support" / "spots_cli"
11
+ else:
12
+ return Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "spots_cli"
13
+
14
+
15
+ def create_config_dir():
16
+ config_dir = get_config_path()
17
+ config_dir.mkdir(parents=True, exist_ok=True)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from typing import Optional, Tuple
5
+
6
+
7
+ def detect_browser() -> Optional[Tuple[str, ...]]:
8
+ """
9
+ Detect an installed browser and return a yt-dlp compatible
10
+ cookiesfrombrowser tuple.
11
+
12
+ Returns:
13
+ Tuple usable in yt-dlp options, e.g. ("firefox",)
14
+ or ("chrome",), or None if nothing found.
15
+ """
16
+
17
+ # Order matters: prefer Firefox (more reliable on Linux)
18
+ browsers = [
19
+ ("firefox", ["firefox"]),
20
+ ("chrome", ["google-chrome", "chrome", "chromium", "chromium-browser"]),
21
+ ("edge", ["microsoft-edge"]),
22
+ ("brave", ["brave-browser"]),
23
+ ]
24
+
25
+ for browser_name, executables in browsers:
26
+ for exe in executables:
27
+ if shutil.which(exe):
28
+ return (browser_name,)
29
+
30
+ return None
@@ -0,0 +1,73 @@
1
+ from dataclasses import dataclass, field
2
+ from requests import get
3
+ from requests.exceptions import Timeout, ConnectionError, RequestException, HTTPError
4
+ from typing import Any, Literal
5
+
6
+
7
+ @dataclass
8
+ class FetchResponseFailure:
9
+ error: str
10
+ status_code: int = field(default=500)
11
+ success: Literal[False] = False
12
+ data: None = None
13
+ fallback: Any = None
14
+
15
+
16
+ @dataclass
17
+ class FetchResponseSuccess:
18
+ data: dict[str, Any] | str
19
+ status_code: int = field(default=200)
20
+ success: Literal[True] = True
21
+ error: None = None
22
+ fallback: Any = None
23
+
24
+
25
+ def fetch_data(
26
+ url: str,
27
+ *,
28
+ params: dict[str, Any] | None = None,
29
+ headers: dict[str, Any] | None = None,
30
+ timeout: int = 5,
31
+ ) -> FetchResponseFailure | FetchResponseSuccess:
32
+ try:
33
+ response = get(url, params=params, headers=headers, timeout=timeout)
34
+
35
+ # Check for HTTP errors (4xx, 5xx)
36
+ response.raise_for_status()
37
+
38
+ # Try to parse JSON (optional, adjust if expecting text)
39
+ try:
40
+ res_data = response.json()
41
+
42
+ if res_data.get("error"):
43
+ error_data = res_data["error"]
44
+ return FetchResponseFailure(
45
+ status_code=error_data.get("code"), error=error_data["message"]
46
+ )
47
+
48
+ return FetchResponseSuccess(status_code=response.status_code, data=res_data)
49
+ except ValueError:
50
+ return FetchResponseSuccess(
51
+ status_code=response.status_code,
52
+ data=response.text, # fallback if not JSON
53
+ )
54
+
55
+ except Timeout:
56
+ return FetchResponseFailure(
57
+ error="Request timed out",
58
+ fallback=None, # you can plug in cached data here
59
+ )
60
+
61
+ except ConnectionError:
62
+ return FetchResponseFailure(error="Connection error", fallback=None)
63
+
64
+ except HTTPError as e:
65
+ return FetchResponseFailure(
66
+ error=f"HTTP error: {e}",
67
+ status_code=getattr(e.response, "status_code", 500),
68
+ fallback=None,
69
+ )
70
+
71
+ except RequestException as e:
72
+ # Catch-all for other request issues
73
+ return FetchResponseFailure(error=f"Unexpected error: {e}", fallback=None)
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from spots_cli.models import SongNotFound
6
+
7
+ if TYPE_CHECKING:
8
+ from spots_cli.models import Metadata, SearchProvider
9
+
10
+
11
+ def search_fallbacks(*, query: str, providers: list[SearchProvider]) -> Metadata:
12
+ index = 0
13
+ while index < len(providers):
14
+ provider = providers[index]
15
+ try:
16
+ return provider.search_track(query)
17
+ except SongNotFound:
18
+ index += 1
19
+ continue
20
+
21
+ raise SongNotFound(query)
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: spots_cli
3
+ Version: 0.1.0
4
+ Summary: Spots is a modular music coordination library that unifies multiple providers (e.g. Deezer, Spotify, YouTube) into a single, consistent interface.
5
+ Project-URL: Homepage, https://github.com/mdesignscode/spots_flask
6
+ Project-URL: Issues, https://github.com/mdesignscode/spots_flask
7
+ Author-email: Marlon Baatjes <marlonbaatjes62@gmail.com>
8
+ License-Expression: GPL-3.0-or-later
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Python: >=3.9
16
+ Requires-Dist: beautifulsoup4
17
+ Requires-Dist: google-api-python-client
18
+ Requires-Dist: google-auth-oauthlib
19
+ Requires-Dist: lyricsgenius
20
+ Requires-Dist: moviepy
21
+ Requires-Dist: mutagen
22
+ Requires-Dist: python-dotenv
23
+ Requires-Dist: requests
24
+ Requires-Dist: spotipy
25
+ Requires-Dist: tenacity
26
+ Requires-Dist: typer
27
+ Requires-Dist: yt-dlp
28
+ Description-Content-Type: text/markdown
29
+
30
+ # 🎧 Spots
31
+
32
+ Spots is a modular music coordination library that unifies multiple providers (e.g. Deezer, Spotify, YouTube) into a single, consistent interface.
33
+
34
+ It resolves tracks across platforms, matches metadata intelligently, and enables downloading, playlist handling, and media conversion — all through a clean, configurable API.
35
+
36
+ ---
37
+
38
+ ## ✨ Features
39
+
40
+ - 🔍 **Multi-provider search**
41
+ - Supports multiple metadata providers with fallback resolution
42
+
43
+ - 🧠 **Intelligent track matching**
44
+ - Matches tracks across platforms using pattern-based scoring
45
+
46
+ - 🎵 **Unified media resolution**
47
+ - Seamlessly map metadata → YouTube → downloadable media
48
+
49
+ - ⚡ **Provider fallback system**
50
+ - Automatically falls back when a provider fails
51
+
52
+ - 🗂️ **Built-in caching**
53
+ - Reduces redundant API calls and speeds up repeated queries
54
+
55
+ - ⚙️ **Config-driven architecture**
56
+ - Enable/disable integrations (Spotify, YouTube, etc.)
57
+
58
+ - 📦 **Download + conversion pipeline**
59
+ - Handles media extraction and conversion (via yt-dlp)
60
+
61
+ ---
62
+
63
+ ## 📦 Installation
64
+
65
+ ```bash
66
+ pip install spots
67
+ ```
68
+
69
+ ---
70
+
71
+ ## ⚙️ Setup
72
+
73
+ ### Run the setup script:
74
+
75
+ ```bash
76
+ spots setup
77
+ ```
78
+
79
+ * follow the prompts to generate the config folder
80
+
81
+
82
+ ---
83
+
84
+ ## 🚀 Usage
85
+ ```bash
86
+ # search a title
87
+ # required search format: `Artist - Title`
88
+ spots download "Kelsey Lynn - Modern Day Marilyn Monroe"
89
+
90
+ # download a direct url
91
+ spots download "https://www.youtube.com/watch?v=UmZPa5Qudtw"
92
+ ```
93
+
94
+ Spots will:
95
+
96
+ * Resolve the track via metadata providers
97
+ * Match it to a YouTube video
98
+ * Download and convert it
99
+ * Store results locally
100
+
101
+ ---
102
+
103
+ ## 🎮 Available commands
104
+
105
+ * download - downloads a direct url or a search query
106
+ ```bash
107
+ spots download "Kelsey Lynn - Modern Day Marilyn Monroe"
108
+ ```
109
+ * migrate-likes - transfers likes from Spotify to YouTube (more providers coming soon...)
110
+ ```bash
111
+ spots migrate-likes
112
+ ```
113
+ * add-to-history - adds a user's downloaded songs to the Spots history. Avoids duplicate downloads.
114
+ ```bash
115
+ spots add-to-history path-to-music
116
+ ```
117
+ * remove-duplicates - removes duplicate songs in a given folder.
118
+ ```bash
119
+ spots remove-duplicates path-to-music
120
+ ```
121
+
122
+ ---
123
+
124
+ ## ⚠️ Important
125
+
126
+ The Spotify API content may not be downloaded using this project. This project is created for personal use only and is intended for educational purposes. Any use of this project to download or distribute copyrighted material without proper authorization is against the terms of service of Spotify and other involved platforms. The project's author and contributors are not responsible for any misuse of this software.
127
+
128
+ ## 🤝 Contributing
129
+
130
+ Contributions are welcome.
131
+
132
+ If you're adding a provider or improving matching logic, open a PR with a clear description of changes.
133
+
134
+ ---
135
+
136
+ ## 📄 License
137
+
138
+ GPL-3.0-or-later
139
+
140
+ ---
141
+
142
+ ## 💬 Summary
143
+
144
+ Spots is designed to make cross-platform music workflows simple:
145
+
146
+ Give it a track — it figures out the rest.
@@ -0,0 +1,65 @@
1
+ spots_cli/__init__.py,sha256=zABCq2Zh8M51aFrieVzvlCaLB6ra3nwW29RtU2vCrog,4381
2
+ spots_cli/cli.py,sha256=j85SwJKe3w6E8uBJ7zVgAhd2afiKkeFJ-JLgzT9Wl0A,700
3
+ spots_cli/setup_env.py,sha256=ITcUDM4r2OFgro3D6rNds5PBEITjh4yAynGpbJ6GR9I,3647
4
+ spots_cli/app/__init__.py,sha256=gZwcWMaPOMY152WWvX6d4SOxKXxb1qTQEvaN9seXi1c,310
5
+ spots_cli/app/domain_resolver.py,sha256=V7EdvDQYepC7fO8bxQ3Kcm1aizGbyxKM7_kXitv9228,5709
6
+ spots_cli/app/downloader.py,sha256=CFtYsHJ1giGl9er1iqNK__fjumgn5pskji4hmm_pL9E,2651
7
+ spots_cli/app/media_resolver.py,sha256=AqXQ4h6eY9sNrUifG6kHi2ML1-4XWpM9tkQvs0OFLUQ,6635
8
+ spots_cli/app/spotify_playlist_compilation.py,sha256=Ud1lpsikoTEUw-hXsNmEqBqMJnMSSC18jAkyTT3gCzA,8514
9
+ spots_cli/app/youtube_user_playlist.py,sha256=Z6UNxvOG8h_gjNw1Oi4mprt2sG6A-0VI8IfYfguM8ek,7320
10
+ spots_cli/bootstrap/__init__.py,sha256=CXvBouMV7Yx9TZMWU4BpY2Ccw0PFh1X6sBeDauYLJhA,52
11
+ spots_cli/bootstrap/container.py,sha256=pTDnH56u5x1NDDZThADaZP8hwFaC8REj0YQHr3ENyCY,6989
12
+ spots_cli/clients/__init__.py,sha256=MM4DLhZoZXxqAVys3VDn9XGRC_1M4NY0Qq6yOQpcVB4,265
13
+ spots_cli/clients/deezer.py,sha256=EZ8z6tiHin2W3syDjh80fx7z95-kSVmviWLg1a31ZWQ,2866
14
+ spots_cli/clients/secrets_manager.py,sha256=zLBziF24mHvxN6rII2iMZHQbrNsYLQKs9vGPswP5Qiw,579
15
+ spots_cli/clients/spotify.py,sha256=JFyscB27sD7gIH5dzM1XVN1dl_6YQOxtTe5r_7ZfpVA,2178
16
+ spots_cli/clients/youtube.py,sha256=SKrUGj27DI9_rbYwj8rDzZtzS1z0jEGOgQDpj0bQGUU,1886
17
+ spots_cli/clients/ytdl.py,sha256=BvdHr9g90z16PrtQn7NP8YG9_TMIfhEMTT9VjSKaCPY,2097
18
+ spots_cli/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ spots_cli/core/__init__.py,sha256=JywasH_HkZlJyNsp3BZEYONcaYCrk_ud15yomxRpPUc,340
20
+ spots_cli/core/history_manager.py,sha256=qkHUZ-tLWCVYg9yyCyOmYHMArmdCihyLMpb-cCap4mA,1071
21
+ spots_cli/core/lyrics_finder.py,sha256=UFBbUjn8elNmbg8cy1O-CQNNzIbbNJwOG7BM_PtMLvw,1886
22
+ spots_cli/core/pattern_matcher.py,sha256=AjTdYd2mwIe-aUtNtq__CnLDxBDC1-SrQkZ5CDB8H4U,5943
23
+ spots_cli/core/video_converter.py,sha256=ynmdoFeVSUncab60WCiUFTEpOaOV5JsBXvSZwfLyKrw,4333
24
+ spots_cli/core/web_scraper.py,sha256=gPyYeazP9x1jS2B_KhNmNyKtd3izU2uhXgBNy8qrnIc,2782
25
+ spots_cli/core/youtube_extractor.py,sha256=kmLzRD14aFY0FjgmQ3Nl3ZRPWHnPorEjobT4zh7dhWk,6131
26
+ spots_cli/engine/__init__.py,sha256=egIAqQuK5X1ZH-X4beRwiO00BkfRpJmsZ5YUyS_UTqg,150
27
+ spots_cli/engine/file_storage.py,sha256=UDUmCJiHCHiPSPqXkYpPhemNyBSZ_Opdhfk_0oWwEJc,10567
28
+ spots_cli/engine/persistence_model.py,sha256=LOII__m_4cjHrxQ7Ncrw7-4e-T0b9uAZf2B4jFjoIU4,69
29
+ spots_cli/engine/retry.py,sha256=8iydX-FaapYi8zC6Fe4yb0YKyiXcn8W8LvMuAq5Ge7g,811
30
+ spots_cli/integrations/__init__.py,sha256=f9qlGiEZlUhO59Ed2PborXklmhEYjqmHNCSBfCd6hkU,90
31
+ spots_cli/integrations/spotify_user_playlist_modify.py,sha256=AxNeCrX5djfJx01PnPRk19uFC6jm2crT1GQ2ky6p7Cc,1520
32
+ spots_cli/models/__init__.py,sha256=5LHfh4tRk4h9guPFK7n9LhiS-jIV6eFYwb0izphFrwA,857
33
+ spots_cli/models/artist_metadata.py,sha256=JzuYFBUVnAf_PJXD69_WrTWmdWH0mQFKOWJVH6zMYM8,490
34
+ spots_cli/models/errors.py,sha256=XzLO-cGOHEGSc_OxgnQPX2WEl24L-OfkhZZlBl4lTD8,1737
35
+ spots_cli/models/helper_models.py,sha256=5y6vaQvbCH7DeXGVjaFUft2O4UYzqb4SO8_NlLZcl_k,411
36
+ spots_cli/models/media_resource.py,sha256=DgPlIHWaKmANL3vvglUAv6nBaw5jrVpPvXTxYgvgZC8,441
37
+ spots_cli/models/metadata.py,sha256=Gb55kH1SekX_RTN-KaQ3c3GIVDa0N7OGjNqXymLS4TQ,1300
38
+ spots_cli/models/metadata_provider.py,sha256=PJbxy9eV2B5CWkP1dzoSKXElHYNVKAPJm7GXNr0oEmg,1372
39
+ spots_cli/models/playlist_info.py,sha256=ntWoXYXclISoBdF5uFvc4Zg1GdzZzfrdBNt-qXrK1PU,762
40
+ spots_cli/models/search_provider.py,sha256=0WPr58HjW5abG-T_18eJNjQ5VE117P_-xpYhzPBCikY,1597
41
+ spots_cli/models/sentinel.py,sha256=IayHOg7yErybvgEkzV9eExpTg0szoks7O6T9HnWNbng,71
42
+ spots_cli/models/yt_video_info.py,sha256=COkhWuZTRlyqi0k_0TlR_kNhEhRHeySdt8jNf918S-g,647
43
+ spots_cli/scripts/__init__.py,sha256=sSiavjYeJ1rIA6Fp9B3gaoL-51Do85j97JarXdfj8rY,153
44
+ spots_cli/scripts/add_to_history.py,sha256=PW7uOh6FAa-mx4Adzs13Clt5AQFmQYIHkLyxDG4h5_g,1419
45
+ spots_cli/scripts/bootstrap.py,sha256=OHvtqMHoFy1Ztxe5K2jxZkikU3-ZV5-0xHM_bclZHz4,2609
46
+ spots_cli/scripts/duplicate_songs_remover.py,sha256=0kZWmp6pZT-Z4W7hUkcxZAWqC3tHW0tIdBc6iQcNGEs,1526
47
+ spots_cli/services/__init__.py,sha256=7M_Touz-BuV0ggEI1Zj67aJLe2WBaJJSvWYWELETRDw,607
48
+ spots_cli/services/deezer_metadata_service.py,sha256=sOxmxDCLC_zhC_8yiTTfk28futJcwyTGeEzswehuL-Y,3491
49
+ spots_cli/services/deezer_search_service.py,sha256=6v402oGtOuCc4Xq8wa3yTWypqui3oegB3psigdBX8-4,4167
50
+ spots_cli/services/provider_search_container.py,sha256=hNlVowitTrCNNp0VudIe1SSTxm0C8CrAkCXVrceHONc,2261
51
+ spots_cli/services/providers_metadata.py,sha256=vjTCGYTqNEeMm68HMh28rdz8POAQmdrV6D1Rn-PJh_k,736
52
+ spots_cli/services/spotify_metadata_service.py,sha256=UlVyLkNtBwDGjSyzPFNy68pk62uEIldEW9hiRiPO-cw,4681
53
+ spots_cli/services/spotify_search_service.py,sha256=Ayf3Tljuon_-BZ8ueO7FLPT2K3FD0HWLF7IFg2pzGrc,7312
54
+ spots_cli/services/youtube_metadata_service.py,sha256=Yti_9MViJhY5e4G5mKvXpsHBsOUEgh9cJUyaetSFQNg,2164
55
+ spots_cli/services/youtube_search_service.py,sha256=YEkDuls0m5mKEyl8XpnhfC1A10h810sVzG0wddKrCs4,7013
56
+ spots_cli/utils/__init__.py,sha256=UjTFuPmsWvnduES7r_Y-Mx0TmJn4KW-N_-x_xUTHS-M,283
57
+ spots_cli/utils/config_dir.py,sha256=H7TvwPTThXYHIBJ_jDB1jNR0ISeDTUIfrYGO553I2lQ,519
58
+ spots_cli/utils/detect_browser.py,sha256=qxs-a6WZCJaGq9d6OphASM817UzdUhuek8KPk3H4XmI,815
59
+ spots_cli/utils/fetch.py,sha256=usqFnMFuF0sMLTYSsageOvKIULagMTvlzxT4KhwCCd0,2200
60
+ spots_cli/utils/search_fallbacks.py,sha256=TH8Nfkomh-ad_e5sG8ccLhjfSx-MmBoRC0dr_xH8ONo,522
61
+ spots_cli-0.1.0.dist-info/METADATA,sha256=2eToqgcJYJu5-RPl_1r8-suRe7gii0pBGkkQTLBGISU,4028
62
+ spots_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
63
+ spots_cli-0.1.0.dist-info/entry_points.txt,sha256=Sqau0y_5RbvwCSLU7vpGnPLIMg5CUETmgEIk-sqPpvI,49
64
+ spots_cli-0.1.0.dist-info/licenses/LICENSE,sha256=EeUxX9Af6TyAbyaTWp-HaboYttd8zzXfXmvxM471Evc,35158
65
+ spots_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ spots_cli = spots_cli.cli:main