Merge pull request #251 from arabcoders/dev

migrate the entire app to use cli options for ytdlp. Closes #250
This commit is contained in:
Abdulmohsen 2025-04-06 02:31:30 +03:00 committed by GitHub
commit ec1f2f448a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 277 additions and 143 deletions

View file

@ -15,7 +15,7 @@ from .config import Config
from .Events import EventBus, Events
from .ffprobe import ffprobe
from .ItemDTO import ItemDTO
from .Utils import extract_info
from .Utils import extract_info, load_cookies
from .YTDLPOpts import YTDLPOpts
LOG = logging.getLogger("Download")
@ -49,7 +49,6 @@ class Download:
template: str = None
template_chapter: str = None
info: ItemDTO = None
default_ytdl_opts: dict = None
debug: bool = False
temp_path: str = None
cancelled: bool = False
@ -93,9 +92,7 @@ class Download:
self.preset = info.preset
self.info = info
self.id = info._id
self.default_ytdl_opts = config.ytdl_options
self.debug = bool(config.debug)
self.archive = bool(config.keep_archive)
self.debug_ytdl = bool(config.ytdl_debug)
self.cancelled = False
self.tmpfilename = None
@ -143,11 +140,11 @@ class Download:
try:
params = (
YTDLPOpts.get_instance()
.preset(self.preset)
.preset(self.preset, with_cookies=not self.info.cookies)
.add({"break_on_existing": True})
.add_cli(self.info.cli, from_user=True)
.add_cli(args=self.info.cli, from_user=True)
.add(
{
config={
"color": "no_color",
"paths": {
"home": self.download_dir,
@ -165,19 +162,6 @@ class Download:
.get_all()
)
if not self.info_dict:
self.logger.info(f"Extracting info for '{self.info.url}'.")
info = extract_info(
config=params,
url=self.info.url,
debug=self.debug,
no_archive=not self.archive,
follow_redirect=True,
)
if info:
self.info_dict = info
params.update(
{
"progress_hooks": [self._progress_hook],
@ -199,8 +183,25 @@ class Download:
with open(cookie_file, "w") as f:
f.write(self.info.cookies)
params["cookiefile"] = f.name
except ValueError as e:
self.logger.error(f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'.")
load_cookies(cookie_file)
except Exception as e:
err_msg = f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'."
self.logger.error(err_msg)
raise ValueError(err_msg) from e
if not self.info_dict:
self.logger.info(f"Extracting info for '{self.info.url}'.")
info = extract_info(
config=params,
url=self.info.url,
debug=self.debug,
no_archive=not params.get("download_archive", False),
follow_redirect=True,
)
if info:
self.info_dict = info
if self.is_live or self.is_manifestless:
hasDeletedOptions = False
@ -217,7 +218,7 @@ class Download:
)
self.logger.info(
f'Task id="{self.info.id}" PID="{os.getpid()}" title="{self.info.title}" preset="{self.preset}" started.'
f'Task id="{self.info.id}" PID="{os.getpid()}" title="{self.info.title}" preset="{self.preset}" cookies="{bool(params.get("cookiefile"))}" started.'
)
self.logger.debug(f"Params before passing to yt-dlp. {params}")

View file

@ -22,7 +22,7 @@ from .Events import EventBus, Events, info
from .ItemDTO import Item, ItemDTO
from .Presets import Presets
from .Singleton import Singleton
from .Utils import arg_converter, calc_download_path, extract_info, is_downloaded
from .Utils import arg_converter, calc_download_path, extract_info, is_downloaded, load_cookies
from .YTDLPOpts import YTDLPOpts
LOG = logging.getLogger("DownloadQueue")
@ -301,33 +301,38 @@ class DownloadQueue(metaclass=Singleton):
extras=item.extras,
)
dlInfo: Download = Download(info=dl, info_dict=entry)
try:
dlInfo: Download = Download(info=dl, info_dict=entry)
if dlInfo.info.live_in or "is_upcoming" == entry.get("live_status"):
dlInfo.info.status = "not_live"
itemDownload = self.done.put(dlInfo)
NotifyEvent = Events.COMPLETED
log_message = f"{dl.title or dl.id or dl._id}: stream is not live yet."
if dlInfo.info.live_in:
log_message += f" Will start in {dlInfo.info.live_in}."
if dlInfo.info.live_in or "is_upcoming" == entry.get("live_status"):
dlInfo.info.status = "not_live"
itemDownload = self.done.put(dlInfo)
NotifyEvent = Events.COMPLETED
log_message = f"{dl.title or dl.id or dl._id}: stream is not live yet."
if dlInfo.info.live_in:
log_message += f" Will start in {dlInfo.info.live_in}."
await self._notify.emit(
Events.LOG_INFO,
data=info(msg=log_message, data=itemDownload.info.serialize()),
)
elif self.config.allow_manifestless is False and is_manifestless is True:
dlInfo.info.status = "error"
dlInfo.info.error = "Video is in post-live manifestless mode."
itemDownload = self.done.put(dlInfo)
NotifyEvent = Events.COMPLETED
else:
NotifyEvent = Events.ADDED
itemDownload = self.queue.put(dlInfo)
self.event.set()
await self._notify.emit(
Events.LOG_INFO,
data=info(msg=log_message, data=itemDownload.info.serialize()),
)
elif self.config.allow_manifestless is False and is_manifestless is True:
dlInfo.info.status = "error"
dlInfo.info.error = "Video is in post-live manifestless mode."
itemDownload = self.done.put(dlInfo)
NotifyEvent = Events.COMPLETED
else:
NotifyEvent = Events.ADDED
itemDownload = self.queue.put(dlInfo)
self.event.set()
await self._notify.emit(NotifyEvent, data=itemDownload.info.serialize())
await self._notify.emit(NotifyEvent, data=itemDownload.info.serialize())
return {"status": "ok"}
return {"status": "ok"}
except Exception as e:
LOG.exception(e)
LOG.error(f"Failed to download item. '{e!s}'")
return {"status": "error", "msg": str(e)}
if eventType.startswith("url"):
return await self.add(item=item.new_with(url=entry.get("url")), already=already)
@ -363,9 +368,6 @@ class DownloadQueue(metaclass=Singleton):
if _preset.template and not item.template:
item.template = _preset.template
if _preset.cookies and not item.cookies:
item.cookies = _preset.cookies
yt_conf = {}
cookie_file = os.path.join(self.config.temp_path, f"c_{uuid.uuid4().hex}.txt")
@ -380,15 +382,6 @@ class DownloadQueue(metaclass=Singleton):
already.add(item.url)
try:
downloaded, id_dict = self._is_downloaded(item.url)
if downloaded is True and id_dict:
message = f"This url with ID '{id_dict.get('id')}' has been downloaded already and recorded in archive."
LOG.info(message)
return {"status": "error", "msg": message}
started = time.perf_counter()
LOG.debug(f"extract_info: checking '{item.url}'.")
logs = []
yt_conf = {
@ -396,19 +389,34 @@ class DownloadQueue(metaclass=Singleton):
"func": lambda _, msg: logs.append(msg),
"level": logging.WARNING,
},
**YTDLPOpts.get_instance().preset(name=item.preset).add_cli(args=item.cli, from_user=True).get_all(),
**YTDLPOpts.get_instance()
.preset(name=item.preset, with_cookies=not item.cookies)
.add_cli(args=item.cli, from_user=True)
.get_all(),
}
downloaded, id_dict = self._is_downloaded(file=yt_conf.get("download_archive", None), url=item.url)
if downloaded is True and id_dict:
message = f"This url with ID '{id_dict.get('id')}' has been downloaded already and recorded in archive."
LOG.info(message)
return {"status": "error", "msg": message}
started = time.perf_counter()
if item.cookies:
try:
async with await anyio.open_file(cookie_file, "w") as f:
await f.write(item.cookies)
yt_conf["cookiefile"] = f.name
except ValueError as e:
msg = f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'."
load_cookies(cookie_file)
except Exception as e:
msg = f"Failed to create cookie file for '{item.url}'. '{e!s}'."
LOG.error(msg)
return {"status": "error", "msg": msg}
LOG.info(f"Checking '{item.url}' with {'cookies' if yt_conf.get('cookiefile') else 'no cookies'}.")
entry = await asyncio.wait_for(
fut=asyncio.get_running_loop().run_in_executor(
None,
@ -691,18 +699,19 @@ class DownloadQueue(metaclass=Singleton):
if self.event:
self.event.set()
def _is_downloaded(self, url: str) -> tuple[bool, dict | None]:
def _is_downloaded(self, url: str, file: str | None = None) -> tuple[bool, dict | None]:
"""
Check if the url has been downloaded already.
Args:
url (str): The url to check.
file (str | None): The archive file to check.
Returns:
tuple: A tuple with the status of the operation and the id of the downloaded item.
"""
if not url or not self.config.keep_archive:
if not url or not file:
return False, None
return is_downloaded(self.config.ytdl_options.get("download_archive", None), url)
return is_downloaded(file, url)

View file

@ -7,7 +7,7 @@ from dataclasses import dataclass, field
from .Singleton import Singleton
LOG = logging.getLogger("EventsSubscriber")
LOG = logging.getLogger("Events")
def error(msg: str, data: dict | None = None) -> dict:
@ -152,6 +152,16 @@ class Events:
Events.CLI_OUTPUT,
]
def only_debug() -> list:
"""
High frequency events that should only be logged in debug mode.
Returns:
list: The list of debug events.
"""
return [Events.UPDATED]
@dataclass(kw_only=True)
class Event:
@ -226,9 +236,16 @@ class EventBus(metaclass=Singleton):
_listeners: dict[str, list[str, EventListener]] = {}
"""The listeners for the events."""
debug: bool = False
"""Whether to log debug messages or not."""
def __init__(self):
EventBus._instance = self
from .config import Config
self.debug = Config.get_instance().debug
@staticmethod
def get_instance() -> "EventBus":
"""
@ -353,7 +370,9 @@ class EventBus(metaclass=Singleton):
return []
ev = Event(event=event, data=data)
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
if self.debug or event not in Events.only_debug():
LOG.debug(f"Emitting event '{ev.id}: {ev.event}'.", extra={"data": data})
tasks = []
for handler in self._listeners[event].values():

View file

@ -46,6 +46,7 @@ from .Utils import (
get_file_sidecar,
get_files,
get_mime_type,
load_cookies,
read_logfile,
validate_url,
validate_uuid,
@ -530,8 +531,8 @@ class HttpAPI(Common):
opts = {}
if self.config.ytdl_options.get("proxy", None):
opts["proxy"] = self.config.ytdl_options.get("proxy", None)
if ytdlp_proxy := self.config.get_ytdlp_args().get("proxy", None):
opts["proxy"] = ytdlp_proxy
ytdlp_opts = YTDLPOpts.get_instance().preset(name=preset, with_cookies=True).add(opts).get_all()
@ -608,7 +609,7 @@ class HttpAPI(Common):
id,
extract_info(
config={
"proxy": self.config.ytdl_options.get("proxy", None),
"proxy": self.config.get_ytdlp_args().get("proxy", None),
"simulate": True,
"dump_single_json": True,
},
@ -1411,16 +1412,16 @@ class HttpAPI(Common):
return web.json_response(data={"error": str(e)}, status=web.HTTPForbidden.status_code)
try:
ytdlp_args = self.config.get_ytdlp_args()
opts = {
"proxy": self.config.ytdl_options.get("proxy", None),
"proxy": ytdlp_args.get("proxy", None),
"headers": {
"User-Agent": self.config.ytdl_options.get(
"User-Agent": ytdlp_args.get(
"user_agent", request.headers.get("User-Agent", f"YTPTube/{self.config.version}")
),
},
}
logging.getLogger("httpx").setLevel(logging.WARNING)
async with httpx.AsyncClient(**opts) as client:
LOG.debug(f"Fetching thumbnail from '{url}'.")
response = await client.request(method="GET", url=url)
@ -1473,14 +1474,14 @@ class HttpAPI(Common):
},
)
ytdlp_args = self.config.get_ytdlp_args()
opts = {
"proxy": self.config.ytdl_options.get("proxy", None),
"proxy": ytdlp_args.get("proxy", None),
"headers": {
"User-Agent": self.config.ytdl_options.get("user_agent", f"YTPTube/{self.config.version}"),
"User-Agent": ytdlp_args.get("user_agent", f"YTPTube/{self.config.version}"),
},
}
logging.getLogger("httpx").setLevel(logging.WARNING)
async with httpx.AsyncClient(**opts) as client:
response = await client.request(method="GET", url=backend, follow_redirects=True)
@ -1616,7 +1617,9 @@ class HttpAPI(Common):
Response: The response object
"""
cookie_file = self.config.ytdl_options.get("cookiefile", None)
ytdlp_args = self.config.get_ytdlp_args()
cookie_file = ytdlp_args.get("cookiefile", None)
if not cookie_file:
return web.json_response(data={"message": "No cookie file provided."}, status=web.HTTPForbidden.status_code)
@ -1627,32 +1630,24 @@ class HttpAPI(Common):
)
try:
import http.cookiejar
cookies = http.cookiejar.MozillaCookieJar(cookie_file, None, None)
cookies.load()
except Exception as e:
LOG.exception(e)
LOG.error(f"failed to load cookies from '{cookie_file}'. '{e!s}'.")
return web.json_response(
data={"message": "Failed to load cookies"},
status=web.HTTPInternalServerError.status_code,
)
_, cookies = load_cookies(cookie_file)
except ValueError as e:
LOG.error(str(e))
return web.json_response(data={"message": str(e)}, status=web.HTTPInternalServerError.status_code)
url = "https://www.youtube.com/account"
try:
opts = {
"proxy": self.config.ytdl_options.get("proxy", None),
"proxy": ytdlp_args.get("proxy", None),
"headers": {
"User-Agent": self.config.ytdl_options.get(
"User-Agent": ytdlp_args.get(
"user_agent", request.headers.get("User-Agent", f"YTPTube/{self.config.version}")
),
},
"cookies": cookies,
}
logging.getLogger("httpx").setLevel(logging.WARNING)
async with httpx.AsyncClient(**opts) as client:
LOG.debug(f"Checking '{url}' redirection.")
response = await client.request(method="GET", url=url, follow_redirects=False)

View file

@ -236,13 +236,22 @@ class HttpSocket(Common):
@ws_event
async def archive_item(self, _: str, data: dict):
if not isinstance(data, dict) or "url" not in data or not self.config.keep_archive:
if not isinstance(data, dict) or "url" not in data:
return
if not isinstance(self.config.ytdl_options, dict):
self.config.ytdl_options = {}
from .YTDLPOpts import YTDLPOpts
file: str = self.config.ytdl_options.get("download_archive", None)
params = YTDLPOpts.get_instance()
if "preset" in data and isinstance(data["preset"], str):
params.preset(name=data["preset"])
if "cli" in data and isinstance(data["cli"], str) and len(data["cli"]) > 1:
params.add_cli(data["cli"], from_user=True)
params = params.get_all()
file: str = params.get("download_archive", None)
if not file:
return
@ -267,8 +276,9 @@ class HttpSocket(Common):
if not previouslyArchived:
async with await anyio.open_file(manual_archive, "a") as f:
await f.write(f"{idDict['archive_id']} - at: {datetime.now(UTC).isoformat()}\n")
LOG.info(f"Archiving url '{data['url']}' with id '{idDict['archive_id']}'.")
LOG.info(f"Archiving url '{data['url']}' with id '{idDict['archive_id']}'.")
else:
LOG.info(f"URL '{data['url']}' with id '{idDict['archive_id']}' already archived.")
@ws_event
async def connect(self, sid: str, _=None):

View file

@ -368,9 +368,6 @@ class Notification(metaclass=Singleton):
if "form" == target.request.type.lower():
reqBody["data"]["data"] = self._encoder.encode(reqBody["data"]["data"])
if not self._debug:
logging.getLogger("httpx").setLevel(logging.WARNING)
response = await self._client.request(**reqBody)
respData = {"url": target.request.url, "status": response.status_code, "text": response.text}

View file

@ -12,6 +12,7 @@ import shlex
import socket
import uuid
from functools import lru_cache
from http.cookiejar import MozillaCookieJar
from typing import Any
import yt_dlp
@ -28,7 +29,6 @@ REMOVE_KEYS: list = [
"progress_hooks": "--progress_hooks",
"postprocessor_hooks": "--postprocessor_hooks",
"post_hooks": "--post_hooks",
"download_archive": "--download_archive",
},
{
"quiet": "-q, --quiet",
@ -38,7 +38,6 @@ REMOVE_KEYS: list = [
"simulate": "--simulate",
"noprogress": "--no-progress",
"wait_for_video": "--wait-for-video",
"mark_watched": "--mark-watched",
"color": "--color",
"verbose": "-v, --verbose",
"debug_printtraffic": "--print-traffic",
@ -52,9 +51,6 @@ REMOVE_KEYS: list = [
"print_to_file": "--print-to-file",
"cookiesfrombrowser": "--cookies-from-browser",
},
{
"cookiefile": "--cookies",
},
]
YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None
@ -1051,3 +1047,26 @@ async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5):
"datetime": dt_match.group(1) if dt_match else None,
}
)
def load_cookies(file: str) -> tuple[bool, MozillaCookieJar]:
"""
Validate and load a cookie file.
Args:
file (str): The cookie file path.
Returns:
bool: True if the cookie file is valid.
"""
try:
from http.cookiejar import MozillaCookieJar
cookies = MozillaCookieJar(file, None, None)
cookies.load()
return (True, cookies)
except Exception as e:
msg = f"Invalid cookie file '{file}'. '{e!s}'"
raise ValueError(msg) from e

View file

@ -4,7 +4,7 @@ from pathlib import Path
from .config import Config
from .Presets import Presets
from .Singleton import Singleton
from .Utils import REMOVE_KEYS, arg_converter, calc_download_path, merge_dict
from .Utils import REMOVE_KEYS, arg_converter, calc_download_path, load_cookies, merge_dict
LOG = logging.getLogger("YTDLPOpts")
@ -16,6 +16,12 @@ class YTDLPOpts(metaclass=Singleton):
_preset_opts: dict = {}
"""The preset options."""
_item_cli: list = []
"""The item cli options."""
_preset_cli: str = ""
"""The preset cli options."""
_instance = None
"""The instance of the class."""
@ -38,7 +44,7 @@ class YTDLPOpts(metaclass=Singleton):
def add_cli(self, args: str, from_user: int | bool = False) -> "YTDLPOpts":
"""
Prase and add yt-dlp cli options to the item options.
Parse and add yt-dlp cli options to the item options.
Args:
args (str): The cli options to add
@ -51,12 +57,13 @@ class YTDLPOpts(metaclass=Singleton):
if not args or len(args) < 2 or not isinstance(args, str):
return self
removed_options = []
try:
arg_converter(args=args, level=from_user)
except Exception as e:
msg = f"Invalid cli options for were given. '{e!s}'."
raise ValueError(msg) from e
self._item_opts.update(arg_converter(args=args, level=from_user, removed_options=removed_options))
if len(removed_options) > 0:
LOG.warning("Removed the following options: '%s'.", ", ".join(removed_options))
self._item_cli.append(args)
return self
@ -108,13 +115,9 @@ class YTDLPOpts(metaclass=Singleton):
if preset.cli:
try:
removed_options = []
self._preset_opts = arg_converter(args=preset.cli, level=True, removed_options=removed_options)
if len(removed_options) > 0:
LOG.warning(
"Removed the following options '%s' from preset '%s'.", ", ".join(removed_options), preset.name
)
arg_converter(args=preset.cli, level=True)
self._preset_opts = {}
self._preset_cli = preset.cli
except Exception as e:
msg = f"Invalid cli options for preset '{preset.name}'. '{e!s}'."
raise ValueError(msg) from e
@ -128,6 +131,8 @@ class YTDLPOpts(metaclass=Singleton):
with open(file, "w") as f:
f.write(preset.cookies)
load_cookies(file)
self._preset_opts["cookiefile"] = str(file)
if preset.format:
@ -155,20 +160,55 @@ class YTDLPOpts(metaclass=Singleton):
dict: The options
"""
default_opts = self._config.ytdl_options
default_opts = {}
default_opts["paths"] = {"home": self._config.download_path, "temp": self._config.temp_path}
default_opts["outtmpl"] = {
"default": self._config.output_template,
"chapter": self._config.output_template_chapter,
}
data = merge_dict(self._item_opts, merge_dict(self._preset_opts, default_opts))
if not isinstance(self._item_cli, list):
self._item_cli = []
merge = []
if self._config._ytdlp_cli_mutable and len(self._config._ytdlp_cli_mutable) > 1:
merge.append(self._config._ytdlp_cli_mutable)
if self._preset_cli and len(self._preset_cli) > 1:
merge.append(self._preset_cli)
if len(merge) > 0:
# prepend the cli options to the list
self._item_cli = merge + self._item_cli
user_cli = {}
if len(self._item_cli) > 0:
try:
removed_options = []
user_cli = arg_converter(args="\n".join(self._item_cli), level=True, removed_options=removed_options)
if len(removed_options) > 0:
LOG.warning("Removed the following options: '%s'.", ", ".join(removed_options))
except Exception as e:
msg = f"Invalid cli options were given. '{e!s}'."
raise ValueError(msg) from e
data = merge_dict(user_cli, merge_dict(self._item_opts, merge_dict(self._preset_opts, default_opts)))
if not keep:
self.presets_opts = {}
self._item_opts = {}
self._item_cli = []
self._preset_cli = ""
if "format" in data and data["format"] in ["not_set", "default"]:
data["format"] = None
if "format" in data:
if data["format"] in ["not_set", "default", "best"]:
data["format"] = None
if data["format"] == "-best":
data["format"] = data["format"][1:]
LOG.debug(f"Parsed yt-dlp options are: '{data!s}'.")
return data

View file

@ -11,7 +11,7 @@ from pathlib import Path
import coloredlogs
from dotenv import load_dotenv
from .Utils import FileLogFormatter, arg_converter
from .Utils import FileLogFormatter, arg_converter, load_cookies
from .version import APP_VERSION
@ -112,9 +112,6 @@ class Config:
__instance = None
"The instance of the class."
ytdl_options: dict = {}
"The options to use for yt-dlp."
new_version_available: bool = False
"A new version of the application is available."
@ -154,6 +151,9 @@ class Config:
ytdlp_cli: str = ""
"""The command line options to use for yt-dlp."""
_ytdlp_cli_mutable: str = ""
"""The command line options to use for yt-dlp."""
pictures_backends: list[str] = [
"https://unsplash.it/1920/1080?random",
"https://picsum.photos/1920/1080",
@ -178,6 +178,7 @@ class Config:
"new_version_available",
"started",
"ytdlp_cli",
"_ytdlp_cli_mutable",
)
"The variables that are immutable."
@ -328,22 +329,20 @@ class Config:
except Exception as e:
LOG.error(f"Error starting debugpy server at '0.0.0.0:{self.debugpy_port}'. {e}")
ytdl_options = {}
opts_file: str = os.path.join(self.config_path, "ytdlp.cli")
if os.path.exists(opts_file) and os.path.getsize(opts_file) > 2:
LOG.info(f"Loading yt-dlp custom options from '{opts_file}'.")
with open(opts_file) as f:
self.ytdlp_cli = f.read().strip()
if self.ytdlp_cli:
self._ytdlp_cli_mutable = self.ytdlp_cli
try:
removed_options = []
self.ytdl_options = arg_converter(
args=self.ytdlp_cli,
level=1,
removed_options=removed_options,
)
ytdl_options = arg_converter(args=self.ytdlp_cli, level=True, removed_options=removed_options)
try:
LOG.debug("Parsed yt-dlp cli options '%s'.", self.ytdl_options)
LOG.debug("Parsed yt-dlp cli options '%s'.", ytdl_options)
except Exception:
pass
@ -359,17 +358,28 @@ class Config:
else:
LOG.info(f"No yt-dlp custom options found at '{opts_file}'.")
self.ytdl_options["socket_timeout"] = self.socket_timeout
self._ytdlp_cli_mutable += f"\n--socket-timeout {self.socket_timeout}"
if self.keep_archive:
LOG.info("keep archive option is enabled.")
self.ytdl_options["download_archive"] = os.path.join(self.config_path, "archive.log")
archive_file: str = os.path.join(self.config_path, "archive.log")
self._ytdlp_cli_mutable += f"\n--download-archive {archive_file}"
cookiesFile: str = os.path.join(self.config_path, "cookies.txt")
if cookies_file := ytdl_options.get("cookiefile", None):
if os.path.exists(cookies_file) and os.path.getsize(cookies_file) > 2:
LOG.info(f"Using cookies from '{cookies_file}'.")
load_cookies(cookies_file)
self._ytdlp_cli_mutable += f"\n--cookies {cookies_file}"
else:
LOG.warning(f"Invalid cookie file '{cookies_file}' specified.")
ytdl_options.pop("cookiefile", None)
if os.path.exists(cookiesFile) and self.ytdl_options.get("cookiefile", None) is None:
LOG.info(f"Using cookies from '{cookiesFile}' as default.")
self.ytdl_options["cookiefile"] = cookiesFile
if not ytdl_options.get("cookiefile", None):
cookies_file: str = os.path.join(self.config_path, "cookies.txt")
if os.path.exists(cookies_file) and os.path.getsize(cookies_file) > 2:
LOG.info(f"Using cookies from '{cookies_file}'.")
load_cookies(cookies_file)
self._ytdlp_cli_mutable += f"\n--cookies {cookies_file}"
if self.temp_keep:
LOG.info("Keep temp files option is enabled.")
@ -412,6 +422,9 @@ class Config:
self.started = time.time()
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.INFO)
def _get_attributes(self) -> dict:
attrs: dict = {}
vClass: str = self.__class__
@ -426,6 +439,13 @@ class Config:
return attrs
def get_ytdlp_args(self) -> dict:
try:
return arg_converter(args=self._ytdlp_cli_mutable, level=True)
except Exception as e:
msg = f"Invalid ytdlp.cli options for yt-dlp. '{e!s}'."
raise ValueError(msg) from e
def frontend(self) -> dict:
"""
Returns configuration variables relevant to the frontend.
@ -435,10 +455,16 @@ class Config:
"""
data = {k: getattr(self, k) for k in self._frontend_vars}
hasCookies = self.ytdl_options.get("cookiefile", None)
data["has_cookies"] = hasCookies is not None and os.path.exists(hasCookies)
data["ytdlp_version"] = Config.ytdlp_version()
ytdlp_args = self.get_ytdlp_args()
hasCookies = ytdlp_args.get("cookiefile", None)
data["has_cookies"] = hasCookies is not None and os.path.exists(hasCookies)
if not data.get("keep_archive", False) and ytdlp_args.get("download_archive", None):
data["keep_archive"] = True
data["ytdlp_version"] = Config.ytdlp_version()
return data
@staticmethod

View file

@ -58,6 +58,7 @@
import "@xterm/xterm/css/xterm.css"
import { Terminal } from "@xterm/xterm"
import { FitAddon } from "@xterm/addon-fit"
import { useStorage } from '@vueuse/core'
const terminal = ref()
const terminalFit = ref()
@ -67,6 +68,8 @@ const command_input = ref()
const isLoading = ref(false)
const config = useConfigStore()
const socket = useSocketStore()
const bg_enable = useStorage('random_bg', true)
const bg_opacity = useStorage('random_bg_opacity', 0.85)
watch(() => isLoading.value, async value => {
if (value) {
@ -185,11 +188,17 @@ onMounted(async () => {
socket.off('cli_output', writer)
socket.on('cli_close', loader)
socket.on('cli_output', writer)
if (bg_enable.value) {
document.querySelector('body').setAttribute("style", `opacity: 1.0`)
}
})
onUnmounted(() => {
socket.off('cli_close', loader)
socket.off('cli_output', writer)
window.removeEventListener('resize', reSizeTerminal)
if (bg_enable.value) {
document.querySelector('body').setAttribute("style", `opacity: ${bg_opacity.value}`)
}
});
</script>

View file

@ -97,6 +97,7 @@ div.logbox pre {
import moment from 'moment'
import { request } from '~/utils/index'
import { ref, onMounted, nextTick } from 'vue'
import { useStorage } from '@vueuse/core'
let scrollTimeout = null
@ -113,6 +114,8 @@ const logContainer = ref(null)
const bottomMarker = ref(null)
const autoScroll = ref(true)
const textWrap = ref(true)
const bg_enable = useStorage('random_bg', true)
const bg_opacity = useStorage('random_bg_opacity', 0.85)
const query = ref(useRoute().query.filter ?? '')
const toggleFilter = ref(false)
@ -254,19 +257,25 @@ onMounted(async () => {
bottomMarker.value.scrollIntoView({ behavior: 'smooth' })
}
})
})
if (bg_enable.value) {
document.querySelector('body').setAttribute("style", `opacity: 1.0`)
}
})
onUnmounted(() => {
socket.emit('unsubscribe', 'log_lines')
socket.off('log_lines')
if (bg_enable.value) {
document.querySelector('body').setAttribute("style", `opacity: ${bg_opacity.value}`)
}
})
onBeforeUnmount(() => {
socket.emit('unsubscribe', 'log_lines')
socket.off('log_lines')
if (scrollTimeout) clearTimeout(scrollTimeout)
})
useHead({ title: 'Logs' })