diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3fcb2218..09c5c3df 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -80,6 +80,9 @@
"onefile",
"pathex",
"platformdirs",
+ "playlistend",
+ "playlistrandom",
+ "playlistreverse",
"plexmatch",
"postprocessor",
"preferredcodec",
diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py
index f2d0a8c0..f3cf740f 100644
--- a/app/library/DownloadQueue.py
+++ b/app/library/DownloadQueue.py
@@ -305,7 +305,7 @@ class DownloadQueue(metaclass=Singleton):
entries = entry.get("entries", [])
- LOG.info(f"Processing '{entry.get('id')}: {entry.get('title')}' Playlist.")
+ LOG.info(f"Processing '{entry.get('id')}: {entry.get('title')} ({len(entries)})' Playlist.")
playlistCount = int(entry.get("playlist_count", len(entries)))
results = []
@@ -325,11 +325,19 @@ class DownloadQueue(metaclass=Singleton):
}
async def playlist_processor(i: int, etr: dict):
- await self.processors.acquire()
try:
- LOG.info(
- f"Processing '{entry.get('title')}: {i}/{playlistCount}' - ID: {etr.get('id')} - Title: {etr.get('title')}"
+ item_name = (
+ f"'{entry.get('title')}: {i}/{playlist_keys['n_entries']}' - '{etr.get('id')}: {etr.get('title')}'"
)
+ LOG.debug(f"Waiting to acquire lock for {item_name}")
+ await self.processors.acquire()
+ LOG.debug(f"Acquired lock for {item_name}")
+
+ if self.is_paused():
+ LOG.warning(f"Download is paused. Skipping processing of '{item_name}'.")
+ return {"status": "ok"}
+
+ LOG.info(f"Processing '{item_name}'.")
_status, _msg = ytdlp_reject(entry=etr, yt_params=yt_params)
if not _status:
diff --git a/app/library/Utils.py b/app/library/Utils.py
index 26257ad4..b1f74ad8 100644
--- a/app/library/Utils.py
+++ b/app/library/Utils.py
@@ -57,7 +57,7 @@ REMOVE_KEYS: list = [
"opt_list_format": "-F, --list-formats",
"opt_dump_agent": "--dump-user-agent",
"opt_extractor_descriptions": "--extractor-descriptions",
- "opt_list_impersonate_targets": "--list-impersonate-targets"
+ "opt_list_impersonate_targets": "--list-impersonate-targets",
},
]
@@ -459,6 +459,7 @@ def arg_converter(
level: int | bool | None = None,
dumps: bool = False,
removed_options: list | None = None,
+ keep_defaults: bool = False,
) -> dict:
"""
Convert yt-dlp options to a dictionary.
@@ -468,6 +469,7 @@ def arg_converter(
level (int|bool|None): Level of options to remove, True for all.
dumps (bool): Dump options as JSON.
removed_options (list|None): List of removed options.
+ keep_defaults (bool): Keep default options.
Returns:
dict: yt-dlp options dictionary.
@@ -488,8 +490,7 @@ def arg_converter(
default_opts = _default_opts([]).ydl_opts
opts = yt_dlp.parse_options(shlex.split(args)).ydl_opts
-
- diff = {k: v for k, v in opts.items() if default_opts[k] != v}
+ diff = {k: v for k, v in opts.items() if default_opts[k] != v} if not keep_defaults else opts.items()
if "postprocessors" in diff:
diff["postprocessors"] = [pp for pp in diff["postprocessors"] if pp not in default_opts["postprocessors"]]
diff --git a/app/library/ytdlp.py b/app/library/ytdlp.py
index ecbc61e9..b2f8d224 100644
--- a/app/library/ytdlp.py
+++ b/app/library/ytdlp.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
from typing import Any
import yt_dlp
@@ -47,13 +45,16 @@ def ytdlp_options() -> list[dict[str, Any]]:
),
}
for opt in opts
- if (
- (getattr(opt, "_short_opts", []) or getattr(opt, "_long_opts", []))
- and getattr(opt, "help", None)
- and "SUPPRESSHELP" not in getattr(opt, "help", "")
- )
+ if (getattr(opt, "_long_opts", []) and getattr(opt, "help", None))
]
- return collect(parser.option_list, "root") + [
+ opts = collect(parser.option_list, "root") + [
entry for grp in parser.option_groups for entry in collect(grp.option_list, grp.title or "other")
]
+
+ for opt in opts:
+ if "SUPPRESSHELP" in opt.get("description", ""):
+ opt["description"] = "No description available from yt-dlp."
+
+ return opts
+
diff --git a/ui/app/components/Queue.vue b/ui/app/components/Queue.vue
index 53ad7830..e22ef611 100644
--- a/ui/app/components/Queue.vue
+++ b/ui/app/components/Queue.vue
@@ -523,6 +523,7 @@ const cancelSelected = () => {
}
cancelItems(selectedElms.value)
selectedElms.value = []
+ masterSelectAll.value = false
return true
}
diff --git a/ui/app/pages/index.vue b/ui/app/pages/index.vue
index e135eb0d..771625c3 100644
--- a/ui/app/pages/index.vue
+++ b/ui/app/pages/index.vue
@@ -141,10 +141,17 @@ watch(() => stateStore.queue, () => {
const pauseDownload = () => {
dialog_confirm.value.visible = true
- dialog_confirm.value.html_message = `Pause All non-active downloads?
-
+ dialog_confirm.value.html_message = `
+
- This will not stop downloads that are currently in progress.
+ Pause All non-active downloads?
+
+
+
+
+ - This will not stop downloads that are currently in progress.
+ - If you are in middle of adding a playlist/channel, it will break and stop adding more items.
+
`
dialog_confirm.value.confirm = () => {
socket.emit('pause', {})