docstrings / formatting

This commit is contained in:
2025-09-23 13:17:34 -04:00
parent c2044711fb
commit 19afb287cd
16 changed files with 1165 additions and 428 deletions

View File

@@ -47,7 +47,13 @@ sr = SRUtil()
# ---------- Discord helper ----------
async def discord_notify(webhook_url: str, title: str, description: str, target: Optional[str] = None, color: int = 0x00FF00):
async def discord_notify(
webhook_url: str,
title: str,
description: str,
target: Optional[str] = None,
color: int = 0x00FF00,
):
embed = {
"title": title,
"description": description[:1900] if description else "",
@@ -64,15 +70,20 @@ async def discord_notify(webhook_url: str, title: str, description: str, target:
while True: # permanent retry
try:
async with aiohttp.ClientSession() as session:
async with session.post(webhook_url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:
async with session.post(
webhook_url, json=payload, timeout=aiohttp.ClientTimeout(total=10)
) as resp:
if resp.status >= 400:
text = await resp.text()
raise RuntimeError(f"Discord webhook failed ({resp.status}): {text}")
raise RuntimeError(
f"Discord webhook failed ({resp.status}): {text}"
)
break
except Exception as e:
print(f"Discord send failed, retrying: {e}")
await asyncio.sleep(5)
def send_log_to_discord(message: str, level: str, target: Optional[str] = None):
colors = {"WARNING": 0xFFA500, "ERROR": 0xFF0000, "CRITICAL": 0xFF0000}
color = colors.get(level.upper(), 0xFFFF00)
@@ -83,7 +94,7 @@ def send_log_to_discord(message: str, level: str, target: Optional[str] = None):
title=f"{level} in bulk_download",
description=message,
target=target,
color=color
color=color,
)
try:
@@ -98,6 +109,7 @@ def send_log_to_discord(message: str, level: str, target: Optional[str] = None):
# ---------- Helpers ----------
def tag_with_mediafile(file_path: str, meta: dict):
f = MediaFile(file_path)
def safe_set(attr, value, default=None, cast=None):
if value is None:
value = default
@@ -106,6 +118,7 @@ def tag_with_mediafile(file_path: str, meta: dict):
setattr(f, attr, cast(value))
else:
setattr(f, attr, str(value))
safe_set("title", meta.get("title"), default="Unknown Title")
safe_set("artist", meta.get("artist"), default="Unknown Artist")
safe_set("albumartist", meta.get("album_artist"), default="Unknown Artist")
@@ -136,6 +149,7 @@ def tag_with_mediafile(file_path: str, meta: dict):
if not cover_bytes and cover_url:
try:
import requests
resp = requests.get(cover_url, timeout=10)
resp.raise_for_status()
cover_bytes = resp.content
@@ -188,7 +202,7 @@ def ensure_unique_filename_in_dir(parent: Path, filename: str) -> Path:
# special-case .tar.gz
if filename.lower().endswith(".tar.gz"):
ext = ".tar.gz"
base = filename[:-len(ext)]
base = filename[: -len(ext)]
else:
p = Path(filename)
ext = p.suffix
@@ -235,13 +249,15 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
send_log_to_discord(f"Failed to init job.meta: {e}", "WARNING", target)
# Job started Discord message
asyncio.run(discord_notify(
DISCORD_WEBHOOK,
title=f"Job Started: {job_id}",
description=f"Processing `{len(track_list)}` track(s)",
target=target,
color=0x00FFFF
))
asyncio.run(
discord_notify(
DISCORD_WEBHOOK,
title=f"Job Started: {job_id}",
description=f"Processing `{len(track_list)}` track(s)",
target=target,
color=0x00FFFF,
)
)
async def process_tracks():
per_track_meta = []
@@ -253,7 +269,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
# Set up a one-time rate-limit callback to notify on the first 429 seen by SRUtil
async def _rate_limit_notify(exc: Exception):
try:
send_log_to_discord(f"Rate limit observed while fetching metadata: {exc}", "WARNING", target)
send_log_to_discord(
f"Rate limit observed while fetching metadata: {exc}",
"WARNING",
target,
)
except Exception:
pass
@@ -265,7 +285,13 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
pass
total = len(track_list or [])
for i, track_id in enumerate(track_list or []):
track_info = {"track_id": str(track_id), "status": "Pending", "file_path": None, "error": None, "attempts": 0}
track_info = {
"track_id": str(track_id),
"status": "Pending",
"file_path": None,
"error": None,
"attempts": 0,
}
attempt = 0
while attempt < MAX_RETRIES:
@@ -326,13 +352,19 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
# Try to fetch cover art via SRUtil (use album_id from metadata)
try:
album_field = md.get("album")
album_id = md.get("album_id") or (album_field.get("id") if isinstance(album_field, dict) else None)
album_id = md.get("album_id") or (
album_field.get("id")
if isinstance(album_field, dict)
else None
)
except Exception:
album_id = None
if album_id:
try:
cover_url = await sr.get_cover_by_album_id(album_id, size=640)
cover_url = await sr.get_cover_by_album_id(
album_id, size=640
)
except Exception:
cover_url = None
else:
@@ -344,7 +376,9 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
if cover_url:
try:
timeout = aiohttp.ClientTimeout(total=15)
async with session.get(cover_url, timeout=timeout) as img_resp:
async with session.get(
cover_url, timeout=timeout
) as img_resp:
if img_resp.status == 200:
img_bytes = await img_resp.read()
else:
@@ -375,23 +409,24 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
# Prefer music_tag if available (keeps compatibility with add_cover_art.py)
try:
from music_tag import load_file as mt_load_file # type: ignore
try:
mf = mt_load_file(str(final_file))
# set basic tags
if md.get('title'):
mf['title'] = md.get('title')
if md.get('artist'):
mf['artist'] = md.get('artist')
if md.get('album'):
mf['album'] = md.get('album')
tracknum = md.get('track_number')
if md.get("title"):
mf["title"] = md.get("title")
if md.get("artist"):
mf["artist"] = md.get("artist")
if md.get("album"):
mf["album"] = md.get("album")
tracknum = md.get("track_number")
if tracknum is not None:
try:
mf['tracknumber'] = int(tracknum)
mf["tracknumber"] = int(tracknum)
except Exception:
pass
if img_bytes:
mf['artwork'] = img_bytes
mf["artwork"] = img_bytes
mf.save()
embedded = True
except Exception:
@@ -438,7 +473,9 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
wait_time = min(60, 2**attempt)
await asyncio.sleep(wait_time)
else:
await asyncio.sleep(random.uniform(THROTTLE_MIN, THROTTLE_MAX))
await asyncio.sleep(
random.uniform(THROTTLE_MIN, THROTTLE_MAX)
)
except Exception as e:
tb = traceback.format_exc()
@@ -447,7 +484,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
track_info["error"] = str(e)
if attempt >= MAX_RETRIES:
track_info["status"] = "Failed"
send_log_to_discord(f"Track {track_id} failed after {attempt} attempts", "ERROR", target)
send_log_to_discord(
f"Track {track_id} failed after {attempt} attempts",
"ERROR",
target,
)
await asyncio.sleep(random.uniform(THROTTLE_MIN, THROTTLE_MAX))
finally:
@@ -464,7 +505,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
job.meta["tarball"] = None
job.meta["status"] = "Failed"
job.save_meta()
send_log_to_discord(f"No tracks were successfully downloaded for job `{job_id}`", "CRITICAL", target)
send_log_to_discord(
f"No tracks were successfully downloaded for job `{job_id}`",
"CRITICAL",
target,
)
return []
# Tarball creation
@@ -476,7 +521,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
except Exception:
artist = "Unknown Artist"
artist_counts[artist] = artist_counts.get(artist, 0) + 1
top_artist = sorted(artist_counts.items(), key=lambda kv: (-kv[1], kv[0]))[0][0] if artist_counts else "Unknown Artist"
top_artist = (
sorted(artist_counts.items(), key=lambda kv: (-kv[1], kv[0]))[0][0]
if artist_counts
else "Unknown Artist"
)
# Prefer `job.meta['target']` when provided by the enqueuer. Fall back to the top artist.
target_name = None
try:
@@ -485,7 +534,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
except Exception:
target_name = None
base_label = sanitize_filename(target_name) if target_name else sanitize_filename(top_artist)
base_label = (
sanitize_filename(target_name)
if target_name
else sanitize_filename(top_artist)
)
staged_tarball = staging_root / f"{base_label}.tar.gz"
counter = 1
@@ -504,14 +557,24 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
job.save_meta()
logging.info("Creating tarball: %s", staged_tarball)
await discord_notify(DISCORD_WEBHOOK,
title=f"Compressing: Job {job_id}",
description=f"Creating tarball: `{len(all_final_files)}` track(s).\nStaging path: {staged_tarball}",
color=0xFFA500,
target=target)
await discord_notify(
DISCORD_WEBHOOK,
title=f"Compressing: Job {job_id}",
description=f"Creating tarball: `{len(all_final_files)}` track(s).\nStaging path: {staged_tarball}",
color=0xFFA500,
target=target,
)
try:
subprocess.run(
["tar", "-I", "pigz -9", "-cf", str(staged_tarball), "-C", str(staging_root)]
[
"tar",
"-I",
"pigz -9",
"-cf",
str(staged_tarball),
"-C",
str(staging_root),
]
+ [str(f.relative_to(staging_root)) for f in all_final_files],
check=True,
)
@@ -521,7 +584,11 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
except Exception:
pass
except FileNotFoundError:
send_log_to_discord("pigz not available, falling back to tarfile (slower).", "WARNING", target)
send_log_to_discord(
"pigz not available, falling back to tarfile (slower).",
"WARNING",
target,
)
with tarfile.open(staged_tarball, "w:gz") as tar:
for f in all_final_files:
try:
@@ -535,7 +602,9 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
pass
if not staged_tarball.exists():
send_log_to_discord(f"Tarball was not created: `{staged_tarball}`", "CRITICAL", target)
send_log_to_discord(
f"Tarball was not created: `{staged_tarball}`", "CRITICAL", target
)
if job:
job.meta["status"] = "compress_failed"
job.save_meta()
@@ -556,13 +625,13 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
# Job completed Discord message
completed = len(all_final_files)
failed = (len(track_list) - completed)
failed = len(track_list) - completed
await discord_notify(
DISCORD_WEBHOOK,
title=f"Job Completed: {job_id}",
description=f"Processed `{len(track_list)}` track(s).\nCompleted: `{completed}`\nFailed: `{failed}`\nTarball: `{final_tarball}`",
target=target,
color=0x00FF00
color=0x00FF00,
)
return [str(final_tarball)]
@@ -572,7 +641,9 @@ def bulk_download(track_list: list, quality: str = "FLAC"):
try:
return loop.run_until_complete(process_tracks())
except Exception as e:
send_log_to_discord(f"bulk_download failed: {e}\n{traceback.format_exc()}", "CRITICAL", target)
send_log_to_discord(
f"bulk_download failed: {e}\n{traceback.format_exc()}", "CRITICAL", target
)
if job:
job.meta["status"] = "Failed"
job.save_meta()