Moved to uv, not convinced. Added ConfigParser and Argparser

This commit is contained in:
Christoph Stahl 2025-08-10 21:35:53 +02:00
parent 41b363bb37
commit 52e1bfbfed
4 changed files with 80 additions and 32 deletions

View file

@ -1,16 +1,23 @@
[project] [project]
name = "qobuz-dl-remote" name = "qobuz-dl-remote"
version = "0.1.0" version = "0.1.0"
authors = [
{name = "Christoph Stahl", email = "christoph.stahl@tu-dortmund.de"}
]
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
authors = [
{ name = "Christoph Stahl", email = "christoph.stahl@tu-dortmund.de" }
]
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"music-tag>=0.4.3", "musicbrainzngs>=0.7.1",
"mutagen>=1.47.0", "mutagen>=1.47.0",
"pillow>=11.3.0", "pillow>=11.3.0",
"requests>=2.32.4", "requests>=2.32.4",
"tqdm>=4.67.1", "tqdm>=4.67.1",
] ]
[project.scripts]
qobuz-dl-remote = "qobuz_dl_remote.main:main"
[build-system]
requires = ["uv_build>=0.8.4,<0.9.0"]
build-backend = "uv_build"

View file

@ -1,16 +1,15 @@
#!/usr/bin/env python3 import datetime
import os import os
from dataclasses import dataclass
from io import BytesIO
from typing import Any, Literal from typing import Any, Literal
from configparser import ConfigParser
from argparse import ArgumentParser
import requests import requests
import tqdm import tqdm
from mutagen.flac import FLAC, Picture
import music_tag from PIL import Image
import sys
from dataclasses import dataclass
import datetime
QOBUZ_API_BASE = "https://us.qobuz.squid.wtf/api"
QUALITY = 27
@dataclass @dataclass
@ -93,12 +92,12 @@ class Album:
class Qobuz: class Qobuz:
def __init__(self, api_base=QOBUZ_API_BASE, quality: int = QUALITY) -> None: def __init__(self, api_base, quality: int) -> None:
self.api_base = api_base self.api_base = api_base
self.quality = quality self.quality = quality
def get_download_url(self, track_id: str) -> str: def get_download_url(self, track_id: str) -> str:
url = f"{QOBUZ_API_BASE}/download-music" url = f"{self.api_base}/download-music"
result = requests.get( result = requests.get(
url, params={"track_id": track_id, "quality": self.quality} url, params={"track_id": track_id, "quality": self.quality}
) )
@ -110,24 +109,35 @@ class Qobuz:
def download_track(self, track: Track, filename: str, dest: str) -> None: def download_track(self, track: Track, filename: str, dest: str) -> None:
download_url = self.get_download_url(track.id) download_url = self.get_download_url(track.id)
dest_path = os.path.join(dest, filename)
response = requests.get(download_url, stream=True) response = requests.get(download_url, stream=True)
if response.status_code == 200: if response.status_code == 200:
total_size = int(response.headers.get("content-length", 0)) total_size = int(response.headers.get("content-length", 0))
with tqdm.tqdm( with tqdm.tqdm(
total=total_size, unit="B", unit_scale=True, desc=filename total=total_size, unit="B", unit_scale=True, desc=filename
) as pbar: ) as pbar:
with open(os.path.join(dest, filename), "wb") as file: with open(dest_path, "wb") as file:
for chunk in response.iter_content(chunk_size=8192): for chunk in response.iter_content(chunk_size=8192):
file.write(chunk) file.write(chunk)
pbar.update(len(chunk)) pbar.update(len(chunk))
# Set metadata using mutagen # Set metadata using mutagen
audio = music_tag.load_file(filename) audio = FLAC(dest_path)
audio["tracktitle"] = track.title audio["title"] = track.title
audio["artist"] = track.album.artist audio["artist"] = track.album.artist
audio["tracknumber"] = str(track.nr) audio["tracknumber"] = str(track.nr)
audio["album"] = track.album.title audio["album"] = track.album.title
audio["year"] = track.album.release_date.year audio["release_date"] = track.album.release_date.isoformat()
audio["artwork"] = track.album.cover_data if track.album.cover_data is not None:
picture = Picture()
pil_image = Image.open(BytesIO(track.album.cover_data))
picture.type = 3 # Front cover
picture.data = track.album.cover_data
picture.mime = pil_image.get_format_mimetype()
picture.desc = "Cover"
picture.width = pil_image.width
picture.height = pil_image.height
picture.depth = len(pil_image.getbands()) * 8
audio.add_picture(picture)
audio.save() audio.save()
print(f"Downloaded {filename}") print(f"Downloaded {filename}")
@ -167,11 +177,42 @@ class Qobuz:
self.download_album(album, f"{album.artist} - {album.title}") self.download_album(album, f"{album.artist} - {album.title}")
def get_config() -> ConfigParser:
config = ConfigParser()
if os.path.exists("config.ini"):
config_file = "config.ini"
else:
config_file = os.path.expanduser("~/.config/qobuz_dl_remote/config.ini")
if not os.path.exists(config_file):
raise FileNotFoundError(f"Config file not found: {config_file}")
config.read(config_file)
return config
def main() -> None: def main() -> None:
# trackid = sys.argv[1] config = get_config()
# filename = sys.argv[2] base_url = config.get("qobuz", "api_base")
query = sys.argv[1] if not base_url:
qb = Qobuz() print("No API base URL configured, please set api_base under [qobuz].")
quality = config.getint("qobuz", "quality", fallback=27)
parser = ArgumentParser(description="Qobuz Remote Downloader")
parser.add_argument(
"--type",
"-t",
type=str,
choices=["album", "discography"],
default="album",
help="Type of search to perform",
)
parser.add_argument(
"query", type=str, help="Search query for the album to download"
)
parser.add_argument("--beet", "-b", action="store_true", help="Import into Beet")
args = parser.parse_args()
query = args.query
qb = Qobuz(api_base=base_url, quality=quality)
print(qb.search_and_download(query)) print(qb.search_and_download(query))

16
uv.lock generated
View file

@ -94,13 +94,13 @@ wheels = [
] ]
[[package]] [[package]]
name = "music-tag" name = "musicbrainzngs"
version = "0.4.3" version = "0.7.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ sdist = { url = "https://files.pythonhosted.org/packages/0a/67/3e74ae93d90ceeba72ed1a266dd3ca9abd625f315f0afd35f9b034acedd1/musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627", size = 117469, upload-time = "2020-01-11T17:38:47.581Z" }
{ name = "mutagen" }, wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/fd/cef7b2580436910ccd2f8d3deec0f3c81743e15c0eb5b97dde3fbf33c0c8/musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10", size = 25289, upload-time = "2020-01-11T17:38:45.469Z" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/fc/f4/ebcdd2fc9bfaf569b795250090e4f4088dc65a5a3e32c53baa9bfc3fc296/music-tag-0.4.3.tar.gz", hash = "sha256:0aab6e6eeda8df0f5316ec2d2190bd74561b7e03562ab091ce8d5687cdbcfff6", size = 23153, upload-time = "2021-06-05T17:20:23.552Z" }
[[package]] [[package]]
name = "mutagen" name = "mutagen"
@ -216,9 +216,9 @@ wheels = [
[[package]] [[package]]
name = "qobuz-dl-remote" name = "qobuz-dl-remote"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "music-tag" }, { name = "musicbrainzngs" },
{ name = "mutagen" }, { name = "mutagen" },
{ name = "pillow" }, { name = "pillow" },
{ name = "requests" }, { name = "requests" },
@ -227,7 +227,7 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "music-tag", specifier = ">=0.4.3" }, { name = "musicbrainzngs", specifier = ">=0.7.1" },
{ name = "mutagen", specifier = ">=1.47.0" }, { name = "mutagen", specifier = ">=1.47.0" },
{ name = "pillow", specifier = ">=11.3.0" }, { name = "pillow", specifier = ">=11.3.0" },
{ name = "requests", specifier = ">=2.32.4" }, { name = "requests", specifier = ">=2.32.4" },