fix percentage

This commit is contained in:
Lukas 2025-11-01 18:46:53 +01:00
parent 0b5faeffc9
commit 9fce617949
4 changed files with 106 additions and 68 deletions

View File

@ -1,45 +1,5 @@
{
"pending": [
{
"id": "b9598089-3874-4574-9b1f-d31669eab645",
"serie_id": "beheneko-the-elf-girls-cat-is-secretly-an-s-ranked-monster",
"serie_folder": "beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)",
"serie_name": "beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)",
"episode": {
"season": 1,
"episode": 2,
"title": null
},
"status": "pending",
"priority": "NORMAL",
"added_at": "2025-11-01T17:20:49.339563Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "b7626704-068f-4084-80b6-6f213c121412",
"serie_id": "beheneko-the-elf-girls-cat-is-secretly-an-s-ranked-monster",
"serie_folder": "beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)",
"serie_name": "beheneko the elf girls cat is secretly an s ranked monster (2025) (2025)",
"episode": {
"season": 1,
"episode": 3,
"title": null
},
"status": "pending",
"priority": "NORMAL",
"added_at": "2025-11-01T17:20:49.339620Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "67d98e0a-ce51-4768-854d-33c565d9bf17",
"serie_id": "beheneko-the-elf-girls-cat-is-secretly-an-s-ranked-monster",
@ -63,5 +23,5 @@
],
"active": [],
"failed": [],
"timestamp": "2025-11-01T17:22:28.998624+00:00"
"timestamp": "2025-11-01T17:42:05.983160+00:00"
}

View File

@ -245,6 +245,8 @@ class SeriesApp:
# event-driven progress reporting with the legacy callback API that
# the CLI still relies on.
def wrapped_callback(progress_info):
logger.debug(f"wrapped_callback called with: {progress_info}")
if self._is_cancelled():
raise InterruptedError("Download cancelled by user")
@ -267,6 +269,11 @@ class SeriesApp:
speed = progress_info.get('speed', 0) # bytes/sec
eta = progress_info.get('eta') # seconds
logger.debug(
f"Progress calculation: {downloaded}/{total_bytes} = "
f"{progress:.1f}%, speed={speed}, eta={eta}"
)
# Convert to expected format for web API callback
# Web API expects: percent, downloaded_mb, total_mb,
# speed_mbps, eta_seconds
@ -295,6 +302,7 @@ class SeriesApp:
'speed_mbps': None,
'eta_seconds': None,
}
logger.debug(f"Old-style progress: {progress}%")
# Notify progress via new callback system
self._callback_manager.notify_progress(
@ -317,6 +325,7 @@ class SeriesApp:
# Call callback with web API format
# (dict with detailed progress info)
if callback:
logger.debug(f"Calling progress callback: {web_progress_dict}")
callback(web_progress_dict)
# Propagate progress into the legacy callback chain so

View File

@ -261,26 +261,59 @@ class AniworldLoader(Loader):
ydl_opts['http_headers'] = header
logging.debug("Using custom headers for download")
if progress_callback:
ydl_opts['progress_hooks'] = [progress_callback]
# Wrap the callback to add logging
def logged_progress_callback(d):
logging.debug(
f"YT-DLP progress: status={d.get('status')}, "
f"downloaded={d.get('downloaded_bytes')}, "
f"total={d.get('total_bytes')}, "
f"speed={d.get('speed')}"
)
progress_callback(d)
ydl_opts['progress_hooks'] = [logged_progress_callback]
logging.debug("Progress callback registered with YT-DLP")
try:
logging.debug("Starting YoutubeDL download")
logging.debug(f"Download link: {link[:100]}...")
logging.debug(f"YDL options: {ydl_opts}")
with YoutubeDL(ydl_opts) as ydl:
ydl.download([link])
info = ydl.extract_info(link, download=True)
logging.debug(
f"Download info: "
f"title={info.get('title')}, "
f"filesize={info.get('filesize')}"
)
if os.path.exists(temp_path):
logging.debug("Moving file from temp to final destination")
shutil.copy(temp_path, output_path)
os.remove(temp_path)
logging.info(f"Download completed successfully: {output_file}")
logging.info(
f"Download completed successfully: {output_file}"
)
self.clear_cache()
return True
else:
logging.error(f"Download failed: temp file not found at {temp_path}")
logging.error(
f"Download failed: temp file not found at {temp_path}"
)
self.clear_cache()
return False
except BrokenPipeError as e:
logging.error(
f"Broken pipe error with provider {provider}: {e}. "
f"This usually means the stream connection was closed."
)
# Try next provider if available
continue
except Exception as e:
logging.error(f"YoutubeDL download failed: {e}")
logging.error(
f"YoutubeDL download failed with provider {provider}: "
f"{type(e).__name__}: {e}"
)
# Try next provider if available
continue
break

View File

@ -701,6 +701,10 @@ class DownloadService:
def progress_callback(progress_data: dict) -> None:
"""Update progress and broadcast to clients."""
try:
logger.debug(
f"Progress callback received: {progress_data}"
)
# Update item progress
item.progress = DownloadProgress(
percent=progress_data.get("percent", 0.0),
@ -710,41 +714,73 @@ class DownloadService:
eta_seconds=progress_data.get("eta_seconds"),
)
logger.debug(
f"Updated item progress: percent={item.progress.percent:.1f}%, "
f"downloaded={item.progress.downloaded_mb:.1f}MB, "
f"total={item.progress.total_mb}MB, "
f"speed={item.progress.speed_mbps}MB/s"
)
# Track speed for statistics
if item.progress.speed_mbps:
self._download_speeds.append(item.progress.speed_mbps)
# Update progress service
# Schedule coroutines in a thread-safe manner
# (callback may be called from executor thread)
if item.progress.total_mb and item.progress.total_mb > 0:
current_mb = int(item.progress.downloaded_mb)
total_mb = int(item.progress.total_mb)
asyncio.create_task(
self._progress_service.update_progress(
progress_id=f"download_{item.id}",
current=current_mb,
total=total_mb,
metadata={
"speed_mbps": item.progress.speed_mbps,
"eta_seconds": item.progress.eta_seconds,
},
)
logger.debug(
f"Updating progress service: current={current_mb}MB, "
f"total={total_mb}MB"
)
try:
loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(
self._progress_service.update_progress(
progress_id=f"download_{item.id}",
current=current_mb,
total=total_mb,
metadata={
"speed_mbps": item.progress.speed_mbps,
"eta_seconds": item.progress.eta_seconds,
},
),
loop
)
except RuntimeError as e:
logger.warning(
f"Could not schedule progress update: {e}"
)
# Broadcast update (fire and forget)
asyncio.create_task(
self._broadcast_update(
"download_progress",
{
"download_id": item.id,
"item_id": item.id,
"serie_name": item.serie_name,
"season": item.episode.season,
"episode": item.episode.episode,
"progress": item.progress.model_dump(mode="json"),
},
)
logger.debug(
f"Broadcasting download_progress event for item {item.id}"
)
try:
loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(
self._broadcast_update(
"download_progress",
{
"download_id": item.id,
"item_id": item.id,
"serie_name": item.serie_name,
"season": item.episode.season,
"episode": item.episode.episode,
"progress": item.progress.model_dump(mode="json"),
},
),
loop
)
except RuntimeError as e:
logger.warning(
f"Could not schedule broadcast: {e}"
)
except Exception as e:
logger.error("Progress callback error", error=str(e))