grabify-cli
Advanced tools
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| __version__ = "1.1.0" |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import sys | ||
| import click | ||
| from .cli import grabify | ||
| def main() -> click.Group: | ||
| """Grabify entry point""" | ||
| return grabify() | ||
| if __name__ == "__main__": | ||
| sys.exit(main()) # type: ignore |
+135
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import click | ||
| import requests | ||
| from bs4 import BeautifulSoup | ||
| from rich.console import Console | ||
| from . import __version__ | ||
| from .config import cfg | ||
| from .utils import get_data_dict, save, theme | ||
| console = Console(theme=theme) | ||
| @click.group("grabify") | ||
| @click.version_option(__version__, "-v", "--version") | ||
| def grabify() -> None: | ||
| """A command-line tool that allows you to download | ||
| artwork and metadata from Spotify tracks and albums. | ||
| """ | ||
| @grabify.group("config") | ||
| def _config() -> None: | ||
| """Manage config settings""" | ||
| pass | ||
| @_config.command("set") | ||
| @click.argument("key") | ||
| @click.argument("value") | ||
| def _set(key, value) -> None: | ||
| """Set config value""" | ||
| try: | ||
| if key not in cfg.to_dict(): | ||
| return console.print(f"[err]โ[/] No '{key}' found in the config.") | ||
| cfg.set(key, value) | ||
| console.print(f"[ok]โ[/] Config updated! '{key}' is set to '{value}'") | ||
| except Exception as err: | ||
| console.print(f"[err]โ[/] Unknown error: {err}") | ||
| @_config.command("view") | ||
| def _view() -> None: | ||
| """View config values""" | ||
| console.print(f"Config path: {cfg._filepath}\n") | ||
| console.print(cfg.to_str()) | ||
| @_config.command("reset") | ||
| def _reset() -> None: | ||
| """Restore default settings""" | ||
| cfg.create() | ||
| console.print("[ok]โ[/] Config restored to default settings") | ||
| @grabify.command("art") | ||
| @click.argument("url") | ||
| @click.option("--path", "-p", help="Download path", default=cfg.get("download_path")) | ||
| def _art(url, path) -> None: | ||
| """Downloads album/playlist/track artwork""" | ||
| try: | ||
| image_url = "" | ||
| name = "" | ||
| response = requests.get(url, timeout=60) | ||
| soup = BeautifulSoup(response.text, features="lxml") | ||
| for meta in soup.find_all("meta"): | ||
| if meta.get("property") == "og:title": | ||
| name = meta.get("content") | ||
| if meta.get("property") == "og:image": | ||
| image_url = meta.get("content") | ||
| if None in (image_url, name): | ||
| console.print("[err]โ[/] Failed to fetch data") | ||
| saved_path = save(path, name, image_url=image_url) | ||
| console.print(f"[ok]โ[/] Saved to {saved_path}") | ||
| except requests.exceptions.MissingSchema: | ||
| console.print("[err]โ[/] Incorrect URL") | ||
| except requests.exceptions.Timeout: | ||
| console.print("[err]โ[/] Connection timed out, try again later") | ||
| @grabify.command("data") | ||
| @click.argument("url") | ||
| @click.option("--path", "-p", help="Download path", default=cfg.get("download_path")) | ||
| def _data(url: str, path: str) -> None: | ||
| """Downloads album/playlist/track metadata""" | ||
| try: | ||
| name = "" | ||
| image_url = "" | ||
| desc = "" | ||
| _type = "" | ||
| raw_data = "" | ||
| response = requests.get(url, timeout=60) | ||
| soup = BeautifulSoup(response.text, features="lxml") | ||
| for meta in soup.find_all("meta"): | ||
| if meta.get("property") == "og:title": | ||
| name = meta.get("content") | ||
| if meta.get("property") == "og:image": | ||
| image_url = meta.get("content") | ||
| if meta.get("property") == "og:description": | ||
| raw_data = meta.get("content") | ||
| if meta.get("property") == "og:type": | ||
| _type = meta.get("content") | ||
| if None in (image_url, name, desc): | ||
| console.print("[err]โ[/] Failed to fetch data") | ||
| data = get_data_dict(raw_data, _type, name, image_url) | ||
| if cfg.get("format_filenames") is True: | ||
| name = name.lower().replace(" ", "_") | ||
| saved_path = save(path, name, json_data=data) | ||
| console.print(f"[ok]โ[/] Saved to {saved_path}") | ||
| except requests.exceptions.MissingSchema: | ||
| console.print("[err]โ[/] Incorrect URL") | ||
| except requests.exceptions.Timeout: | ||
| console.print("[err]โ[/] Connection timed out, try again later") |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| class Config: | ||
| def __init__(self) -> None: | ||
| self._path = os.getenv('LOCALAPPDATA') + "\\Grabify" | ||
| self._filepath = self._path + "\\settings.json" | ||
| self._download_path = (Path.home() / "Downloads\\Grabify").resolve() | ||
| self._default_config = { | ||
| "download_path": str(self._download_path), | ||
| "format_filenames": True, | ||
| } | ||
| if not os.path.exists(self._path): | ||
| self.create() | ||
| def create(self) -> None: | ||
| os.makedirs(self._path, exist_ok=True) | ||
| with open(self._filepath, "w") as file: | ||
| json.dump(self._default_config, file) | ||
| def set(self, key, value) -> None: | ||
| with open(self._filepath, "r") as file: | ||
| data = json.load(file) | ||
| data[key] = value | ||
| data.update() | ||
| with open(self._filepath, "w") as file: | ||
| json.dump(data, file) | ||
| def get(self, key) -> None: | ||
| with open(self._filepath, "r") as file: | ||
| data = json.load(file) | ||
| return data[key] | ||
| def to_dict(self) -> dict: | ||
| with open(self._filepath, "r") as file: | ||
| return json.load(file) | ||
| def to_str(self) -> str: | ||
| data = self.to_dict() | ||
| res = "" | ||
| for key in data.keys(): | ||
| value = data[key] | ||
| res += f"{key}: {value}\n" | ||
| return res | ||
| cfg = Config() |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| from typing import Any, Optional, Union | ||
| import requests | ||
| from rich.theme import Theme | ||
| theme = Theme( | ||
| { | ||
| "info": "bold blue", | ||
| "warn": "bold yellow", | ||
| "err": "bold red", | ||
| "ok": "bold green", | ||
| } | ||
| ) | ||
| def uniquify(path: str) -> str: | ||
| """ | ||
| Ensure a filename is unique by adding a number to the end if necessary. | ||
| This is useful for generating filenames that are unique across the filesystem. | ||
| """ | ||
| base, ext = os.path.splitext(path) | ||
| counter = 1 | ||
| while os.path.exists(path): | ||
| path = f"{base}_{counter}{ext}" | ||
| counter += 1 | ||
| return path | ||
| def save( | ||
| path: str, | ||
| filename: str, | ||
| image_url: Optional[str] = None, | ||
| json_data: Optional[dict] = None, | ||
| ) -> Union[str, Any]: | ||
| """ | ||
| Save file to path. If image_url is provided it will be downloaded | ||
| and saved as jpg file. | ||
| """ | ||
| dirc = str(Path(path).resolve()) + os.sep | ||
| os.makedirs(dirc, exist_ok=True) | ||
| ext = ".jpg" if image_url else ".json" | ||
| dist = uniquify(dirc + filename + ext) | ||
| if image_url: | ||
| data = requests.get(image_url, timeout=60).content | ||
| with open(dist, "wb+") as file: | ||
| file.write(data) | ||
| else: | ||
| data = json.dumps(json_data) | ||
| with open(dist, "w+", encoding="utf-8") as file: | ||
| file.write(data) | ||
| return dist | ||
| def get_data_dict(raw_data, _type, name, image_url) -> dict: | ||
| """ | ||
| Converts data to dict. This is a helper function to make it easier to use in tests | ||
| """ | ||
| raw_data = str(raw_data).split(" ยท ") | ||
| songs = int(raw_data[-1].replace("songs", "").replace(".", "")) | ||
| data = { | ||
| "name": name, | ||
| "image_url": image_url, | ||
| "type": _type, | ||
| "songs": songs, | ||
| } | ||
| if _type != "music.playlist": | ||
| data.update({"year": raw_data[2], "author": raw_data[0]}) | ||
| if _type not in ("music.playlist", "music.song"): | ||
| data.update({"songs": songs}) | ||
| if _type == "music.song": | ||
| del data["songs"] | ||
| return data |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: grabify-cli | ||
| Version: 1.0.1 | ||
| Version: 1.1.0 | ||
| Summary: ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify tracks and albums without authentication. | ||
@@ -5,0 +5,0 @@ Project-URL: Homepage, https://github.com/woidzero/grabify |
+2
-5
@@ -38,6 +38,6 @@ [build-system] | ||
| [project.scripts] | ||
| grabify = "src.grabify_cli.__main__:main" | ||
| grabify = "grabify.__main__:main" | ||
| [tool.hatch.version] | ||
| path = "src/grabify_cli/__init__.py" | ||
| path = "grabify/__init__.py" | ||
@@ -74,4 +74,1 @@ [tool.black] | ||
| known-first-party = ["grabify"] | ||
| [tool.ruff.flake8-tidy-imports] | ||
| ban-relative-imports = "all" |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| __version__ = "1.0.1" |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import sys | ||
| import click | ||
| from src.grabify_cli.cli import COMMANDS, grabify | ||
| for cmd in COMMANDS: | ||
| grabify.add_command(cmd) | ||
| def main() -> click.Group: | ||
| """Entry point for grabify.""" | ||
| return grabify() | ||
| if __name__ == "__main__": | ||
| sys.exit(main()) # type: ignore |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import click | ||
| import requests | ||
| from bs4 import BeautifulSoup | ||
| from rich.console import Console | ||
| from . import __version__ | ||
| from .config import DEFAULT_PATH | ||
| from .utils import get_data_dict, save, theme | ||
| console = Console(theme=theme) | ||
| @click.group("grabify") | ||
| @click.version_option(__version__, "-V", "--version") | ||
| def grabify() -> None: | ||
| """A command-line tool that allows you to download | ||
| artwork and metadata from Spotify tracks and albums. | ||
| """ | ||
| @click.command("art") | ||
| @click.argument("url") | ||
| @click.option("--path", "-p", help="Provide download path", default=DEFAULT_PATH) | ||
| def _art(url, path) -> None: | ||
| """Downloads album/playlist/track artwork""" | ||
| try: | ||
| image_url = "" | ||
| name = "" | ||
| response = requests.get(url, timeout=60) | ||
| soup = BeautifulSoup(response.text, features="lxml") | ||
| for meta in soup.find_all("meta"): | ||
| if meta.get("property") == "og:title": | ||
| name = meta.get("content") | ||
| if meta.get("property") == "og:image": | ||
| image_url = meta.get("content") | ||
| if None in (image_url, name): | ||
| console.print("[err]โ[/] Failed to fetch data") | ||
| saved_path = save(path, name, image_url=image_url) | ||
| console.print(f"[ok]โ[/] Saved to {saved_path}") | ||
| except requests.exceptions.MissingSchema: | ||
| console.print("[err]โ[/] Incorrect URL") | ||
| except requests.exceptions.Timeout: | ||
| console.print("[err]โ[/] Connection timed out, try again later") | ||
| @click.command("data") | ||
| @click.argument("url") | ||
| @click.option("--path", "-p", help="Set download path", default=DEFAULT_PATH) | ||
| def _data(url: str, path: str) -> None: | ||
| """Downloads album/playlist/track metadata""" | ||
| try: | ||
| name = "" | ||
| image_url = "" | ||
| desc = "" | ||
| _type = "" | ||
| raw_data = "" | ||
| response = requests.get(url, timeout=60) | ||
| soup = BeautifulSoup(response.text, features="lxml") | ||
| for meta in soup.find_all("meta"): | ||
| if meta.get("property") == "og:title": | ||
| name = meta.get("content") | ||
| if meta.get("property") == "og:image": | ||
| image_url = meta.get("content") | ||
| if meta.get("property") == "og:description": | ||
| raw_data = meta.get("content") | ||
| if meta.get("property") == "og:type": | ||
| _type = meta.get("content") | ||
| if None in (image_url, name, desc): | ||
| console.print("[err]โ[/] Failed to fetch data") | ||
| data = get_data_dict(raw_data, _type, name, image_url) | ||
| saved_path = save(path, name.lower().replace(" ", "_"), json_data=data) | ||
| console.print(f"[ok]โ[/] Saved to {saved_path}") | ||
| except requests.exceptions.MissingSchema: | ||
| console.print("[err]โ[/] Incorrect URL") | ||
| except requests.exceptions.Timeout: | ||
| console.print("[err]โ[/] Connection timed out, try again later") | ||
| COMMANDS = (_art, _data) |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| from pathlib import Path | ||
| DEFAULT_PATH = (Path.home() / "Downloads\\grabify").resolve() |
| """ | ||
| Grabify | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
| ๐ผ A command-line tool that allows you to download artwork and metadata from Spotify | ||
| tracks and albums without authentication. | ||
| :copyright: (c) 2023 woidzero | ||
| :license: MIT, see LICENSE for more details. | ||
| """ | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
| from typing import Any, Optional, Union | ||
| import requests | ||
| from rich.theme import Theme | ||
| theme = Theme( | ||
| { | ||
| "info": "bold blue", | ||
| "warn": "bold yellow", | ||
| "err": "bold red", | ||
| "ok": "bold green", | ||
| } | ||
| ) | ||
| def uniquify(path: str) -> str: | ||
| """ | ||
| Ensure a filename is unique by adding a number to the end if necessary. | ||
| This is useful for generating filenames that are unique across the filesystem. | ||
| """ | ||
| base, ext = os.path.splitext(path) | ||
| counter = 1 | ||
| while os.path.exists(path): | ||
| path = f"{base}_{counter}{ext}" | ||
| counter += 1 | ||
| return path | ||
| def save( | ||
| path: str, | ||
| filename: str, | ||
| image_url: Optional[str] = None, | ||
| json_data: Optional[dict] = None, | ||
| ) -> Union[str, Any]: | ||
| """ | ||
| Save file to path. If image_url is provided it will be downloaded | ||
| and saved as jpg file. | ||
| """ | ||
| dirc = str(Path(path).resolve()) + os.sep | ||
| os.makedirs(dirc, exist_ok=True) | ||
| ext = ".jpg" if image_url else ".json" | ||
| dist = uniquify(dirc + filename + ext) | ||
| if image_url: | ||
| data = requests.get(image_url, timeout=60).content | ||
| with open(dist, "wb+") as file: | ||
| file.write(data) | ||
| else: | ||
| data = json.dumps(json_data) | ||
| with open(dist, "w+", encoding="utf-8") as file: | ||
| file.write(data) | ||
| return dist | ||
| def get_data_dict(raw_data, _type, name, image_url) -> dict: | ||
| """ | ||
| Converts data to dict. This is a helper function to make it easier to use in tests | ||
| """ | ||
| raw_data = str(raw_data).split(" ยท ") | ||
| songs = int(raw_data[-1].replace("songs", "").replace(".", "")) | ||
| data = { | ||
| "name": name, | ||
| "image_url": image_url, | ||
| "type": _type, | ||
| "songs": songs, | ||
| } | ||
| if _type != "music.playlist": | ||
| data.update({"year": raw_data[2], "author": raw_data[0]}) | ||
| if _type not in ("music.playlist", "music.song"): | ||
| data.update({"songs": songs}) | ||
| if _type == "music.song": | ||
| del data["songs"] | ||
| return data |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
19167
12.82%252
33.33%