diff --git a/app/library/Download.py b/app/library/Download.py index c2b041a5..6747ead6 100644 --- a/app/library/Download.py +++ b/app/library/Download.py @@ -1,6 +1,5 @@ import asyncio import hashlib -import json import logging import multiprocessing import os @@ -15,7 +14,7 @@ from .config import Config from .Emitter import Emitter from .ffprobe import ffprobe from .ItemDTO import ItemDTO -from .Utils import get_opts, json_cookie, merge_config +from .Utils import get_opts, merge_config LOG = logging.getLogger("download") @@ -138,17 +137,11 @@ class Download: if self.info.cookies: try: - data = json_cookie(json.loads(self.info.cookies)) - if not data: - LOG.warning( - f"The cookie string that was provided for {self.info.title} is empty or not in expected spec." - ) with open(os.path.join(self.temp_path, f"cookie_{self.info._id}.txt"), "w") as f: - f.write(data) - - params["cookiefile"] = f.name + f.write(self.info.cookies) + params["cookiefile"] = f.name except ValueError as e: - LOG.error(f"Invalid cookies: was provided for '{self.info.title}'. '{e!s}'.") + LOG.error(f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'.") if self.is_live or self.is_manifestless: hasDeletedOptions = False diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py index a14fd8f9..10079d4e 100644 --- a/app/library/DownloadQueue.py +++ b/app/library/DownloadQueue.py @@ -3,10 +3,12 @@ import json import logging import os import time +import uuid from datetime import UTC, datetime from email.utils import formatdate from sqlite3 import Connection +import anyio import yt_dlp from aiohttp import web @@ -354,9 +356,11 @@ class DownloadQueue(metaclass=Singleton): folder = str(folder) if folder else "" filePath = calc_download_path(base_path=self.config.download_path, folder=folder) + yt_conf = {} + cookie_file = os.path.join(self.config.temp_path, f"c_{uuid.uuid4().hex}.txt") LOG.info( - f"Adding 'URL: {url}' to 'Folder: {filePath}' with 'Preset: {preset}' 'Naming: {template}', 'Cookies: {cookies}' 'YTConfig: {config}'." + f"Adding 'URL: {url}' to 'Folder: {filePath}' with 'Preset: {preset}' 'Naming: {template}', 'Cookies: {len(cookies)}/chars' 'YTConfig: {config}'." ) if isinstance(config, str): @@ -384,19 +388,33 @@ class DownloadQueue(metaclass=Singleton): started = time.perf_counter() LOG.debug(f"extract_info: checking {url=}") + logs = [] + + yt_conf = { + "callback": { + "func": lambda _, msg: logs.append(msg), + "level": logging.WARNING, + }, + **merge_config(self.config.ytdl_options, config), + } + + if cookies: + try: + async with await anyio.open_file(cookie_file, "w") as f: + await f.write(cookies) + yt_conf["cookiefile"] = f.name + except ValueError as e: + LOG.error(f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'.") + entry = await asyncio.wait_for( fut=asyncio.get_running_loop().run_in_executor( - None, - extract_info, - get_opts(preset, merge_config(self.config.ytdl_options, config)), - url, - bool(self.config.ytdl_debug), + None, extract_info, get_opts(preset, yt_conf), url, bool(self.config.ytdl_debug) ), timeout=self.config.extract_info_timeout, ) if not entry: - return {"status": "error", "msg": "Unable to extract info check logs."} + return {"status": "error", "msg": "Unable to extract info." + "\n".join(logs)} LOG.debug( f"extract_info: for 'URL: {url}' is done in '{time.perf_counter() - started}'. Length: '{len(entry)}'." @@ -413,6 +431,13 @@ class DownloadQueue(metaclass=Singleton): "status": "error", "msg": f"TimeoutError: {self.config.extract_info_timeout}s reached Unable to extract info.", } + finally: + if cookie_file and os.path.exists(cookie_file): + try: + os.remove(yt_conf["cookiefile"]) + del yt_conf["cookiefile"] + except Exception as e: + LOG.error(f"Failed to remove cookie file '{yt_conf['cookiefile']}'. {e!s}") return await self.__add_entry( entry=entry, diff --git a/app/library/Tasks.py b/app/library/Tasks.py index 35b52ddc..e8c56555 100644 --- a/app/library/Tasks.py +++ b/app/library/Tasks.py @@ -17,7 +17,7 @@ from .encoder import Encoder from .EventsSubscriber import Event, Events, EventsSubscriber from .Singleton import Singleton -LOG = logging.getLogger("notifications") +LOG = logging.getLogger("tasks") @dataclass(kw_only=True) diff --git a/ui/components/NewDownload.vue b/ui/components/NewDownload.vue index eb99257b..885989c5 100644 --- a/ui/components/NewDownload.vue +++ b/ui/components/NewDownload.vue @@ -100,7 +100,7 @@
-
diff --git a/ui/components/TaskForm.vue b/ui/components/TaskForm.vue index dc70f794..e625494f 100644 --- a/ui/components/TaskForm.vue +++ b/ui/components/TaskForm.vue @@ -127,7 +127,8 @@
@@ -145,17 +146,16 @@
- +
- Use flagCookies to - extract cookies as JSON string. - + Use the + Recommended addon by yt-dlp to export cookies. The cookies MUST be in Netscape HTTP + Cookie format.
@@ -217,7 +217,6 @@ const props = defineProps({ const form = reactive(props.task); onMounted(() => { - if (props.task?.config && (typeof props.task.config === 'object')) { form.config = JSON.stringify(props.task.config, null, 4); } @@ -252,7 +251,11 @@ const checkInfo = async () => { return; } - if (form.config && !form.config.trim().startsWith('{')) { + if (typeof form.config === 'object') { + form.config = JSON.stringify(form.config, null, 4); + } + + if (form.config && form.config && !form.config.trim().startsWith('{')) { await convertOptions(); } @@ -265,15 +268,6 @@ const checkInfo = async () => { } } - if (form.cookies) { - try { - JSON.parse(form.cookies); - } catch (e) { - toast.error(`Invalid JSON yt-dlp cookies. ${e.message}`) - return; - } - } - emitter('submit', { reference: toRaw(props.reference), task: toRaw(form) }); } diff --git a/ui/pages/tasks.vue b/ui/pages/tasks.vue index 86dacbdc..9fa32c7e 100644 --- a/ui/pages/tasks.vue +++ b/ui/pages/tasks.vue @@ -269,10 +269,10 @@ const editItem = item => { } const calcPath = path => { - let loc = config.app.download_path + const loc = config.app.download_path || '/downloads' if (path) { - let loc = loc + '/' + sTrim(path, '/') + return loc + '/' + sTrim(path, '/') } return loc