120 lines
4.3 KiB
Python
120 lines
4.3 KiB
Python
"""Hook handlers for download progress and postprocessing events."""
|
|
|
|
import logging
|
|
import re
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from .utils import DEBUG_MESSAGE_PREFIXES, YTDLP_PROGRESS_FIELDS, create_debug_safe_dict
|
|
|
|
if TYPE_CHECKING:
|
|
from multiprocessing import Queue
|
|
|
|
|
|
class HookHandlers:
|
|
"""Manages yt-dlp hook callbacks for progress tracking and postprocessing."""
|
|
|
|
def __init__(self, download_id: str, status_queue: "Queue[Any]", logger: logging.Logger, debug: bool = False):
|
|
"""
|
|
Initialize hook handlers.
|
|
|
|
Args:
|
|
download_id: Unique identifier for the download
|
|
status_queue: Multiprocessing queue for status updates
|
|
logger: Logger instance for this download
|
|
debug: Whether to enable debug logging
|
|
|
|
"""
|
|
self.id = download_id
|
|
self.status_queue = status_queue
|
|
self.logger = logger
|
|
self.debug = debug
|
|
|
|
def progress_hook(self, data: dict[str, Any]) -> None:
|
|
if self.debug:
|
|
try:
|
|
d_safe = create_debug_safe_dict(data)
|
|
self.logger.debug(
|
|
"Received a yt-dlp progress update for download '%s'.",
|
|
self.id,
|
|
extra={"download": {"download_id": self.id, "hook": "progress", "status": d_safe}},
|
|
)
|
|
except Exception as e:
|
|
self.logger.exception(
|
|
"Failed to create progress hook debug info for download '%s'.",
|
|
self.id,
|
|
extra={
|
|
"download": {"download_id": self.id, "hook": "progress", "exception_type": type(e).__name__}
|
|
},
|
|
)
|
|
|
|
self.status_queue.put(
|
|
{
|
|
"id": self.id,
|
|
"action": "progress",
|
|
**{k: v for k, v in data.items() if k in YTDLP_PROGRESS_FIELDS},
|
|
}
|
|
)
|
|
|
|
def postprocessor_hook(self, data: dict[str, Any]) -> None:
|
|
info_dict = data.get("info_dict", {})
|
|
filepath = info_dict.get("filepath")
|
|
|
|
status: dict[str, Any] = {
|
|
"id": self.id,
|
|
"action": "postprocessing",
|
|
**{k: v for k, v in data.items() if k in YTDLP_PROGRESS_FIELDS},
|
|
"status": "postprocessing",
|
|
}
|
|
if filepath:
|
|
status["filepath"] = filepath
|
|
|
|
if self.debug:
|
|
try:
|
|
d_safe = create_debug_safe_dict(data)
|
|
d_safe["postprocessor"] = data.get("postprocessor")
|
|
self.logger.debug(
|
|
"Received a yt-dlp post-processing update for download '%s'.",
|
|
self.id,
|
|
extra={"download": {"download_id": self.id, "hook": "postprocessor", "status": d_safe}},
|
|
)
|
|
except Exception as e:
|
|
self.logger.exception(
|
|
"Failed to create postprocessor hook debug info for download '%s'.",
|
|
self.id,
|
|
extra={
|
|
"download": {
|
|
"download_id": self.id,
|
|
"hook": "postprocessor",
|
|
"exception_type": type(e).__name__,
|
|
}
|
|
},
|
|
)
|
|
|
|
self.status_queue.put(status)
|
|
|
|
def post_hook(self, filename: str) -> None:
|
|
self.status_queue.put({"id": self.id, "status": "finished", "final_name": filename})
|
|
|
|
|
|
class NestedLogger:
|
|
"""
|
|
Logger adapter for yt-dlp that adjusts log levels based on message prefixes.
|
|
|
|
yt-dlp logs everything through a custom logger. This adapter maps certain
|
|
message types to appropriate log levels and strips redundant prefixes.
|
|
"""
|
|
|
|
def __init__(self, logger: logging.Logger) -> None:
|
|
self.logger: logging.Logger = logger
|
|
|
|
def debug(self, msg: str) -> None:
|
|
levelno: int = logging.DEBUG if any(msg.startswith(x) for x in DEBUG_MESSAGE_PREFIXES) else logging.INFO
|
|
self.logger.log(level=levelno, msg=re.sub(r"^\[(debug|info)\] ", "", msg, flags=re.IGNORECASE))
|
|
|
|
def error(self, msg: str) -> None:
|
|
"""Log an error message."""
|
|
self.logger.error(msg)
|
|
|
|
def warning(self, msg: str) -> None:
|
|
"""Log a warning message."""
|
|
self.logger.warning(msg)
|