Make it possible to cancel download and retain the temp data, ref #332

This commit is contained in:
arabcoders 2025-07-11 18:18:29 +03:00
parent f782cad213
commit 9d24fc01b4
4 changed files with 38 additions and 14 deletions

View file

@ -4,18 +4,20 @@ import logging
import multiprocessing import multiprocessing
import os import os
import re import re
import signal
import time import time
from datetime import UTC, datetime from datetime import UTC, datetime
from email.utils import formatdate from email.utils import formatdate
from pathlib import Path from pathlib import Path
import yt_dlp import yt_dlp.utils
from .config import Config from .config import Config
from .Events import EventBus, Events from .Events import EventBus, Events
from .ffprobe import ffprobe from .ffprobe import ffprobe
from .ItemDTO import ItemDTO from .ItemDTO import ItemDTO
from .Utils import delete_dir, extract_info, extract_ytdlp_logs, load_cookies from .Utils import delete_dir, extract_info, extract_ytdlp_logs, load_cookies
from .ytdlp import YTDLP
from .YTDLPOpts import YTDLPOpts from .YTDLPOpts import YTDLPOpts
@ -264,10 +266,17 @@ class Download:
params["logger"] = NestedLogger(self.logger) params["logger"] = NestedLogger(self.logger)
cls = yt_dlp.YoutubeDL(params=params) cls = YTDLP(params=params)
self.started_time = int(time.time()) self.started_time = int(time.time())
def mark_cancelled(*_):
cls._interrupted = True
cls.to_screen("[info] Interrupt received, exiting cleanly...")
raise SystemExit(130) # noqa: TRY301
signal.signal(signal.SIGUSR1, mark_cancelled)
if isinstance(self.info_dict, dict) and len(self.info_dict) > 1: if isinstance(self.info_dict, dict) and len(self.info_dict) > 1:
self.logger.debug(f"Downloading '{self.info.url}' using pre-info.") self.logger.debug(f"Downloading '{self.info.url}' using pre-info.")
cls.process_ie_result( cls.process_ie_result(
@ -275,10 +284,10 @@ class Download:
download=True, download=True,
extra_info={k: v for k, v in self.info.extras.items() if k not in self.info_dict}, extra_info={k: v for k, v in self.info.extras.items() if k not in self.info_dict},
) )
ret = cls._download_retcode ret: int = cls._download_retcode
else: else:
self.logger.debug(f"Downloading using url: {self.info.url}") self.logger.debug(f"Downloading using url: {self.info.url}")
ret = cls.download(url_list=[self.info.url]) ret: int = cls.download(url_list=[self.info.url])
self.status_queue.put({"id": self.id, "status": "finished" if ret == 0 else "error"}) self.status_queue.put({"id": self.id, "status": "finished" if ret == 0 else "error"})
except yt_dlp.utils.ExistingVideoReached as exc: except yt_dlp.utils.ExistingVideoReached as exc:
@ -333,7 +342,7 @@ class Download:
return False return False
self.cancel_in_progress = True self.cancel_in_progress = True
procId = self.proc.ident procId: int | None = self.proc.ident
self.logger.info(f"Closing PID='{procId}' download process.") self.logger.info(f"Closing PID='{procId}' download process.")
@ -387,7 +396,10 @@ class Download:
try: try:
self.logger.info(f"Killing download process: '{self.proc.ident}'.") self.logger.info(f"Killing download process: '{self.proc.ident}'.")
self.proc.kill() if self.proc.pid and "posix" == os.name:
os.kill(self.proc.pid, signal.SIGUSR1)
else:
self.proc.kill()
return True return True
except Exception as e: except Exception as e:
self.logger.error(f"Failed to kill process: '{self.proc.ident}'. {e}") self.logger.error(f"Failed to kill process: '{self.proc.ident}'. {e}")
@ -398,9 +410,9 @@ class Download:
if self.temp_keep is True or not self.temp_path: if self.temp_keep is True or not self.temp_path:
return return
if "finished" != self.info.status and self.is_live: if "finished" != self.info.status and self.info.downloaded_bytes > 0:
self.logger.warning( self.logger.warning(
f"Keeping live temp folder '{self.temp_path}', as the reported status is not finished '{self.info.status}'." f"Keeping temp folder '{self.temp_path}', as the reported status is not finished '{self.info.status}'."
) )
return return

View file

@ -10,7 +10,7 @@ from pathlib import Path
from sqlite3 import Connection from sqlite3 import Connection
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import yt_dlp import yt_dlp.utils
from aiohttp import web from aiohttp import web
from .ag_utils import ag from .ag_utils import ag

View file

@ -15,11 +15,11 @@ from http.cookiejar import MozillaCookieJar
from pathlib import Path from pathlib import Path
from typing import TypeVar from typing import TypeVar
import yt_dlp
from Crypto.Cipher import AES from Crypto.Cipher import AES
from yt_dlp.utils import age_restricted, match_str from yt_dlp.utils import age_restricted, match_str
from .LogWrapper import LogWrapper from .LogWrapper import LogWrapper
from .ytdlp import YTDLP
LOG: logging.Logger = logging.getLogger("Utils") LOG: logging.Logger = logging.getLogger("Utils")
@ -47,7 +47,7 @@ REMOVE_KEYS: list = [
}, },
] ]
YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None YTDLP_INFO_CLS: YTDLP = None
ALLOWED_SUBS_EXTENSIONS: tuple[str] = (".srt", ".vtt", ".ass") ALLOWED_SUBS_EXTENSIONS: tuple[str] = (".srt", ".vtt", ".ass")
@ -189,7 +189,7 @@ def extract_info(
if no_archive and "download_archive" in params: if no_archive and "download_archive" in params:
del params["download_archive"] del params["download_archive"]
data = yt_dlp.YoutubeDL(params=params).extract_info(url, download=False) data = YTDLP(params=params).extract_info(url, download=False)
if data and follow_redirect and "_type" in data and "url" == data["_type"]: if data and follow_redirect and "_type" in data and "url" == data["_type"]:
return extract_info( return extract_info(
@ -208,7 +208,7 @@ def extract_info(
if not data["is_premiere"]: if not data["is_premiere"]:
data["is_premiere"] = "video" == data.get("media_type") and "is_upcoming" == data.get("live_status") data["is_premiere"] = "video" == data.get("media_type") and "is_upcoming" == data.get("live_status")
return yt_dlp.YoutubeDL.sanitize_info(data) if sanitize_info else data return YTDLP.sanitize_info(data) if sanitize_info else data
def merge_dict(source: dict, destination: dict) -> dict: def merge_dict(source: dict, destination: dict) -> dict:
@ -1103,7 +1103,7 @@ def get_archive_id(url: str) -> tuple[bool, dict[str | None, str | None, str | N
} }
if YTDLP_INFO_CLS is None: if YTDLP_INFO_CLS is None:
YTDLP_INFO_CLS = yt_dlp.YoutubeDL( YTDLP_INFO_CLS = YTDLP(
params={ params={
"color": "no_color", "color": "no_color",
"extract_flat": True, "extract_flat": True,

12
app/library/ytdlp.py Normal file
View file

@ -0,0 +1,12 @@
import yt_dlp
class YTDLP(yt_dlp.YoutubeDL):
_interrupted = False
def _delete_downloaded_files(self, *args, **kwargs):
if self._interrupted:
self.to_screen("[info] Cancelled — skipping temp cleanup.")
return None
return super()._delete_downloaded_files(*args, **kwargs)