diff --git a/.gitignore b/.gitignore index 724f9c4..ddb796b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,150 +1,2 @@ Qobuz Downloads -*__pycache* -.env -__pycache__/ -*.py[cod] -*$py.class -.bumpversion.cfg -/*.py -!/setup.py - -# C extensions -*.so - -*.json -*.txt -*.db -*.sh - -*.txt - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ +__pycache__ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b0e6893 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Module", + "type": "python", + "request": "launch", + "module": "qobuz_dl.cli", + "justMyCode": true, + "args": [ + "dl", + "dl.txt", + "--embed-art", + "-q", + "27", + "--no-db", + "--og-cover" + ], + "env": { + "PYTHONDONTWRITEBYTECODE": "1" + } + } + ] +} \ No newline at end of file diff --git a/dl.txt b/dl.txt new file mode 100644 index 0000000..8c86fc5 --- /dev/null +++ b/dl.txt @@ -0,0 +1 @@ +https://open.qobuz.com/track/652159 \ No newline at end of file diff --git a/qobuz_dl/downloader.py b/qobuz_dl/downloader.py index 66ef450..86ae9e5 100644 --- a/qobuz_dl/downloader.py +++ b/qobuz_dl/downloader.py @@ -138,7 +138,8 @@ class Download: if "sample" not in parse and parse["sampling_rate"]: meta = self.client.get_track_meta(self.item_id) track_title = _get_title(meta) - logger.info(f"\n{YELLOW}Downloading: {track_title}") + artist = _safe_get(meta, "performer", "name") + logger.info(f"\n{YELLOW}Downloading: {artist} - {track_title}") format_info = self._get_format(meta, is_track_id=True, track_url_dict=parse) file_format, quality_met, bit_depth, sampling_rate = format_info @@ -176,7 +177,7 @@ class Download: meta, True, is_mp3, - self.embed_art, + False, ) else: logger.info(f"{OFF}Demo. Skipping") @@ -221,8 +222,7 @@ class Download: logger.info(f"{OFF}{track_title} was already downloaded") return - desc = _get_description(track_url_dict, track_title, multiple) - tqdm_download(url, filename, desc) + tqdm_download(url, filename, filename) tag_function = metadata.tag_mp3 if is_mp3 else metadata.tag_flac try: tag_function( @@ -305,20 +305,26 @@ class Download: return ("Unknown", quality_met, None, None) -def tqdm_download(url, fname, track_name): +def tqdm_download(url, fname, desc): r = requests.get(url, allow_redirects=True, stream=True) total = int(r.headers.get("content-length", 0)) + download_size = 0 with open(fname, "wb") as file, tqdm( total=total, unit="iB", unit_scale=True, unit_divisor=1024, - desc=track_name, + desc=desc, bar_format=CYAN + "{n_fmt}/{total_fmt} /// {desc}", ) as bar: for data in r.iter_content(chunk_size=1024): size = file.write(data) bar.update(size) + download_size += size + + if total != download_size: + # https://stackoverflow.com/questions/69919912/requests-iter-content-thinks-file-is-complete-but-its-not + raise ConnectionError("File download was interrupted for " + fname) def _get_description(item: dict, track_title, multiple=None): diff --git a/qobuz_dl/metadata.py b/qobuz_dl/metadata.py index 9c52591..7b67378 100644 --- a/qobuz_dl/metadata.py +++ b/qobuz_dl/metadata.py @@ -45,8 +45,9 @@ def _get_title(track_dict): def _format_copyright(s: str) -> str: - s = s.replace("(P)", PHON_COPYRIGHT) - s = s.replace("(C)", COPYRIGHT) + if s: + s = s.replace("(P)", PHON_COPYRIGHT) + s = s.replace("(C)", COPYRIGHT) return s @@ -107,7 +108,9 @@ def _embed_id3_img(root_dir, audio: id3.ID3): # Use KeyError catching instead of dict.get to avoid empty tags -def tag_flac(filename, root_dir, final_name, d, album, istrack=True, em_image=False): +def tag_flac( + filename, root_dir, final_name, d: dict, album, istrack=True, em_image=False +): """ Tag a FLAC file @@ -147,14 +150,14 @@ def tag_flac(filename, root_dir, final_name, d, album, istrack=True, em_image=Fa audio["TRACKTOTAL"] = str(d["album"]["tracks_count"]) audio["ALBUM"] = d["album"]["title"] audio["DATE"] = d["album"]["release_date_original"] - audio["COPYRIGHT"] = _format_copyright(d.get("copyright", "n/a")) + audio["COPYRIGHT"] = _format_copyright(d.get("copyright") or "n/a") else: audio["GENRE"] = _format_genres(album["genres_list"]) audio["ALBUMARTIST"] = album["artist"]["name"] audio["TRACKTOTAL"] = str(album["tracks_count"]) audio["ALBUM"] = album["title"] audio["DATE"] = album["release_date_original"] - audio["COPYRIGHT"] = _format_copyright(album.get("copyright", "n/a")) + audio["COPYRIGHT"] = _format_copyright(album.get("copyright") or "n/a") if em_image: _embed_flac_img(root_dir, audio)