mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-05-17 14:21:36 +00:00
fix(api): sync REST API download endpoint with updated dl command (#98)
The REST API download endpoint was broken after recent dl command changes. - Add missing vbitrate_range, abitrate_range, and worst parameters to the API call and DEFAULT_DOWNLOAD_PARAMS - Convert wanted episode strings (S01E01) to internal SxE format via SeasonRange so episode filtering works correctly - Track completed output files via dl.completed_files instead of returning an empty list Closes #98
This commit is contained in:
@@ -66,6 +66,7 @@ dependencies = [
|
||||
"wasmtime>=41.0.0",
|
||||
"animeapi-py>=0.6.0",
|
||||
"rnet>=2.4.2",
|
||||
"bandit>=1.9.4",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
||||
@@ -573,6 +573,7 @@ class dl:
|
||||
raise ValueError("A subcommand to invoke was not specified, the main code cannot continue.")
|
||||
|
||||
self.log = logging.getLogger("download")
|
||||
self.completed_files: list[Path] = []
|
||||
|
||||
if not config.output_template:
|
||||
raise click.ClickException(
|
||||
@@ -2559,6 +2560,7 @@ class dl:
|
||||
final_path = final_dir / f"{base_filename}{track_path.suffix}"
|
||||
|
||||
shutil.move(track_path, final_path)
|
||||
self.completed_files.append(final_path)
|
||||
self.log.debug(f"Saved: {final_path.name}")
|
||||
else:
|
||||
# Handle muxed files
|
||||
@@ -2596,6 +2598,7 @@ class dl:
|
||||
final_path.unlink()
|
||||
shutil.move(muxed_path, final_path)
|
||||
used_final_paths.add(final_path)
|
||||
self.completed_files.append(final_path)
|
||||
tags.tag_file(final_path, title, self.tmdb_id, self.imdb_id)
|
||||
|
||||
title_dl_time = time_elapsed_since(dl_start_time)
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
@@ -149,6 +150,20 @@ def _perform_download(
|
||||
if params.get("export"):
|
||||
params["export"] = bool(params["export"])
|
||||
|
||||
# Convert wanted episode strings to internal "SxE" format
|
||||
# Accepts: "S01E01", "S01-S03", "s1e1", "1x1", or already-parsed format
|
||||
wanted_raw = params.get("wanted")
|
||||
if wanted_raw:
|
||||
from unshackle.core.utils.click_types import SeasonRange
|
||||
|
||||
if isinstance(wanted_raw, str):
|
||||
wanted_raw = [wanted_raw]
|
||||
# Only convert if not already in internal "SxE" format
|
||||
needs_conversion = any(not re.match(r"^\d+x\d+$", w) for w in wanted_raw)
|
||||
if needs_conversion:
|
||||
season_range = SeasonRange()
|
||||
params["wanted"] = season_range.parse_tokens(*wanted_raw)
|
||||
|
||||
# Load service configuration
|
||||
service_config_path = Services.get_path(service) / config.filenames.config
|
||||
if service_config_path.exists():
|
||||
@@ -271,6 +286,8 @@ def _perform_download(
|
||||
acodec=params.get("acodec"),
|
||||
vbitrate=params.get("vbitrate"),
|
||||
abitrate=params.get("abitrate"),
|
||||
vbitrate_range=params.get("vbitrate_range"),
|
||||
abitrate_range=params.get("abitrate_range"),
|
||||
range_=params.get("range", ["SDR"]),
|
||||
channels=params.get("channels"),
|
||||
no_atmos=params.get("no_atmos", False),
|
||||
@@ -306,6 +323,7 @@ def _perform_download(
|
||||
no_mux=params.get("no_mux", False),
|
||||
workers=params.get("workers"),
|
||||
downloads=params.get("downloads", 1),
|
||||
worst=params.get("worst", False),
|
||||
best_available=params.get("best_available", False),
|
||||
split_audio=params.get("split_audio"),
|
||||
)
|
||||
@@ -327,9 +345,10 @@ def _perform_download(
|
||||
log.error(f"Stderr: {stderr_str}")
|
||||
raise
|
||||
|
||||
log.info(f"Download completed for job {job_id}, files in {original_download_dir}")
|
||||
output_files = [str(p) for p in dl_instance.completed_files]
|
||||
log.info(f"Download completed for job {job_id}, {len(output_files)} file(s) in {original_download_dir}")
|
||||
|
||||
return []
|
||||
return output_files
|
||||
|
||||
|
||||
class DownloadQueueManager:
|
||||
|
||||
@@ -29,6 +29,8 @@ DEFAULT_DOWNLOAD_PARAMS = {
|
||||
"acodec": None,
|
||||
"vbitrate": None,
|
||||
"abitrate": None,
|
||||
"vbitrate_range": None,
|
||||
"abitrate_range": None,
|
||||
"range": ["SDR"],
|
||||
"channels": None,
|
||||
"no_atmos": False,
|
||||
@@ -62,6 +64,7 @@ DEFAULT_DOWNLOAD_PARAMS = {
|
||||
"no_mux": False,
|
||||
"workers": None,
|
||||
"downloads": 1,
|
||||
"worst": False,
|
||||
"best_available": False,
|
||||
"repack": False,
|
||||
"imdb_id": None,
|
||||
@@ -981,6 +984,14 @@ def validate_download_parameters(data: Dict[str, Any]) -> Optional[str]:
|
||||
if not isinstance(data["abitrate"], int) or data["abitrate"] <= 0:
|
||||
return "abitrate must be a positive integer"
|
||||
|
||||
if "vbitrate_range" in data and data["vbitrate_range"] is not None:
|
||||
if not isinstance(data["vbitrate_range"], str) or "-" not in data["vbitrate_range"]:
|
||||
return "vbitrate_range must be a string in 'MIN-MAX' format (e.g., '6000-7000')"
|
||||
|
||||
if "abitrate_range" in data and data["abitrate_range"] is not None:
|
||||
if not isinstance(data["abitrate_range"], str) or "-" not in data["abitrate_range"]:
|
||||
return "abitrate_range must be a string in 'MIN-MAX' format (e.g., '128-256')"
|
||||
|
||||
if "channels" in data and data["channels"] is not None:
|
||||
if not isinstance(data["channels"], (int, float)) or data["channels"] <= 0:
|
||||
return "channels must be a positive number"
|
||||
|
||||
26
uv.lock
generated
26
uv.lock
generated
@@ -150,6 +150,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bandit"
|
||||
version = "1.9.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
{ name = "stevedore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.14.3"
|
||||
@@ -1520,6 +1535,15 @@ version = "3.5.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/b7/4a1bc231e0681ebf339337b0cd05b91dc6a0d701fa852bb812e244b7a030/srt-3.5.3.tar.gz", hash = "sha256:4884315043a4f0740fd1f878ed6caa376ac06d70e135f306a6dc44632eed0cc0", size = 28296, upload-time = "2023-03-28T02:35:44.007Z" }
|
||||
|
||||
[[package]]
|
||||
name = "stevedore"
|
||||
version = "5.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/6d/90764092216fa560f6587f83bb70113a8ba510ba436c6476a2b47359057c/stevedore-5.7.0.tar.gz", hash = "sha256:31dd6fe6b3cbe921e21dcefabc9a5f1cf848cf538a1f27543721b8ca09948aa3", size = 516200, upload-time = "2026-02-20T13:27:06.765Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/06/36d260a695f383345ab5bbc3fd447249594ae2fa8dfd19c533d5ae23f46b/stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed", size = 54483, upload-time = "2026-02-20T13:27:05.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subby"
|
||||
version = "0.3.27"
|
||||
@@ -1634,6 +1658,7 @@ dependencies = [
|
||||
{ name = "aiohttp-swagger3" },
|
||||
{ name = "animeapi-py" },
|
||||
{ name = "appdirs" },
|
||||
{ name = "bandit" },
|
||||
{ name = "brotli" },
|
||||
{ name = "chardet" },
|
||||
{ name = "click" },
|
||||
@@ -1691,6 +1716,7 @@ requires-dist = [
|
||||
{ name = "aiohttp-swagger3", specifier = ">=0.9.0,<1" },
|
||||
{ name = "animeapi-py", specifier = ">=0.6.0" },
|
||||
{ name = "appdirs", specifier = ">=1.4.4,<2" },
|
||||
{ name = "bandit", specifier = ">=1.9.4" },
|
||||
{ name = "brotli", specifier = ">=1.1.0,<2" },
|
||||
{ name = "chardet", specifier = ">=5.2.0,<6" },
|
||||
{ name = "click", specifier = ">=8.1.8,<9" },
|
||||
|
||||
Reference in New Issue
Block a user