Merge pull request #480 from arabcoders/dev

[FIX] implement --max-downloads arg. Ref #479
This commit is contained in:
Abdulmohsen 2025-11-10 00:35:48 +03:00 committed by GitHub
commit f0b5e20af8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 761 additions and 839 deletions

View file

@ -203,10 +203,7 @@
}, },
"eslint.format.enable": true, "eslint.format.enable": true,
"eslint.ignoreUntitled": true, "eslint.ignoreUntitled": true,
"python.testing.pytestArgs": [ "python.testing.unittestEnabled": false,
"app/tests"
],
"python.testing.unittestEnabled": true,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"json.schemas": [ "json.schemas": [
{ {
@ -245,5 +242,6 @@
], ],
"url": "./app/schema/tasks.json" "url": "./app/schema/tasks.json"
} }
] ],
"python.testing.cwd": "app/tests"
} }

View file

@ -10,7 +10,7 @@ from datetime import UTC, datetime, timedelta
from email.utils import formatdate from email.utils import formatdate
from pathlib import Path from pathlib import Path
from sqlite3 import Connection from sqlite3 import Connection
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any
import yt_dlp.utils import yt_dlp.utils
from aiohttp import web from aiohttp import web
@ -305,14 +305,16 @@ class DownloadQueue(metaclass=Singleton):
entries = entry.get("entries", []) entries = entry.get("entries", [])
LOG.info(f"Processing '{entry.get('id')}: {entry.get('title')} ({len(entries)})' Playlist.") playlist_name: str = f"{entry.get('id')}: {entry.get('title')}"
LOG.info(f"Processing '{playlist_name} ({len(entries)})' Playlist.")
playlistCount = entry.get("playlist_count") playlistCount = entry.get("playlist_count")
playlistCount = int(playlistCount) if playlistCount else len(entries) playlistCount: int = int(playlistCount) if playlistCount else len(entries)
results = [] results = []
playlist_keys = { playlist_keys: dict[str, Any] = {
"playlist_count": playlistCount, "playlist_count": playlistCount,
"playlist": entry.get("title") or entry.get("id"), "playlist": entry.get("title") or entry.get("id"),
"playlist_id": entry.get("id"), "playlist_id": entry.get("id"),
@ -328,7 +330,7 @@ class DownloadQueue(metaclass=Singleton):
async def playlist_processor(i: int, etr: dict): async def playlist_processor(i: int, etr: dict):
try: try:
item_name = ( item_name: str = (
f"'{entry.get('title')}: {i}/{playlist_keys['n_entries']}' - '{etr.get('id')}: {etr.get('title')}'" 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}") LOG.debug(f"Waiting to acquire lock for {item_name}")
@ -345,7 +347,7 @@ class DownloadQueue(metaclass=Singleton):
if not _status: if not _status:
return {"status": "error", "msg": _msg} return {"status": "error", "msg": _msg}
extras = { extras: dict[str, Any] = {
**playlist_keys, **playlist_keys,
"playlist_index": i, "playlist_index": i,
"playlist_index_number": i, "playlist_index_number": i,
@ -359,7 +361,7 @@ class DownloadQueue(metaclass=Singleton):
if "thumbnail" not in etr and "youtube:" in entry.get("extractor", ""): if "thumbnail" not in etr and "youtube:" in entry.get("extractor", ""):
extras["thumbnail"] = f"https://img.youtube.com/vi/{etr['id']}/maxresdefault.jpg" extras["thumbnail"] = f"https://img.youtube.com/vi/{etr['id']}/maxresdefault.jpg"
newItem = item.new_with(url=etr.get("url") or etr.get("webpage_url"), extras=extras) newItem: Item = item.new_with(url=etr.get("url") or etr.get("webpage_url"), extras=extras)
if "formats" in etr and isinstance(etr["formats"], list) and len(etr["formats"]) > 0: if "formats" in etr and isinstance(etr["formats"], list) and len(etr["formats"]) > 0:
LOG.warning(f"Unexpected formats entries in --flat-playlist for {item_name}, treating as video.") LOG.warning(f"Unexpected formats entries in --flat-playlist for {item_name}, treating as video.")
@ -371,8 +373,16 @@ class DownloadQueue(metaclass=Singleton):
finally: finally:
self.processors.release() self.processors.release()
max_downloads: int = -1
ytdlp_opts: dict[str, Any] = item.get_ytdlp_opts().get_all()
if ytdlp_opts.get("max_downloads") and isinstance(ytdlp_opts.get("max_downloads"), int):
max_downloads: int = ytdlp_opts.get("max_downloads")
tasks: list[asyncio.Task] = [] tasks: list[asyncio.Task] = []
for i, etr in enumerate(entries, start=1): for i, etr in enumerate(entries, start=1):
if max_downloads > 0 and i > max_downloads:
break
task = asyncio.create_task( task = asyncio.create_task(
playlist_processor(i, etr), playlist_processor(i, etr),
name=f"playlist_processor_{etr.get('id')}_{i}", name=f"playlist_processor_{etr.get('id')}_{i}",
@ -382,9 +392,12 @@ class DownloadQueue(metaclass=Singleton):
results: list[dict] = await asyncio.gather(*tasks) results: list[dict] = await asyncio.gather(*tasks)
LOG.info( log_msg: str = f"Playlist '{playlist_name}' processing completed with '{len(results)}' entries."
f"Playlist '{entry.get('id')}: {entry.get('title')}' processing completed with '{len(results)}' entries." if max_downloads > 0 and len(entries) > max_downloads:
) skipped: int = len(entries) - max_downloads
log_msg += f" Limited to '{max_downloads}' items, skipped '{skipped}' remaining items."
LOG.info(log_msg)
if any("error" == res["status"] for res in results): if any("error" == res["status"] for res in results):
return { return {

View file

@ -18,7 +18,7 @@
}, },
"web-types": "./web-types.json", "web-types": "./web-types.json",
"dependencies": { "dependencies": {
"@pinia/nuxt": "^0.11.2", "@pinia/nuxt": "^0.11.3",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"@vueuse/nuxt": "^14.0.0", "@vueuse/nuxt": "^14.0.0",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
@ -26,16 +26,16 @@
"cron-parser": "^5.4.0", "cron-parser": "^5.4.0",
"cronstrue": "^3.9.0", "cronstrue": "^3.9.0",
"floating-vue": "^5.2.2", "floating-vue": "^5.2.2",
"hls.js": "^1.6.13", "hls.js": "^1.6.14",
"marked": "^16.4.1", "marked": "^16.4.1",
"marked-alert": "^2.1.2", "marked-alert": "^2.1.2",
"marked-base-url": "^1.1.7", "marked-base-url": "^1.1.7",
"marked-gfm-heading-id": "^4.1.2", "marked-gfm-heading-id": "^4.1.2",
"moment": "^2.30.1", "moment": "^2.30.1",
"nuxt": "^4.2.0", "nuxt": "^4.2.0",
"pinia": "^3.0.3", "pinia": "^3.0.4",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"vue": "^3.5.22", "vue": "^3.5.23",
"vue-router": "^4.6.3", "vue-router": "^4.6.3",
"vue-toastification": "2.0.0-rc.5" "vue-toastification": "2.0.0-rc.5"
}, },
@ -54,11 +54,11 @@
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.10.0", "@nuxt/eslint": "^1.10.0",
"@nuxt/eslint-config": "^1.10.0", "@nuxt/eslint-config": "^1.10.0",
"@typescript-eslint/parser": "^8.46.2", "@typescript-eslint/parser": "^8.46.3",
"eslint": "^9.38.0", "eslint": "^9.39.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vitest": "^4.0.5", "vitest": "^4.0.7",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.2" "vue-tsc": "^3.1.3"
} }
} }

File diff suppressed because it is too large Load diff