added playlist items concurrency support.

This commit is contained in:
arabcoders 2025-06-18 19:58:58 +03:00
parent 872d85c458
commit 8a3673bc07
3 changed files with 94 additions and 39 deletions

View file

@ -275,7 +275,7 @@ If you feel like donating and appreciate my work, you can do so by donating to c
Certain configuration values can be set via environment variables, using the `-e` parameter on the docker command line, or the `environment:` section in `compose.yaml` file. Certain configuration values can be set via environment variables, using the `-e` parameter on the docker command line, or the `environment:` section in `compose.yaml` file.
| Environment Variable | Description | Default | | Environment Variable | Description | Default |
| ------------------------- | ------------------------------------------------------------------ | ---------------------------------- | | ------------------------------ | ------------------------------------------------------------------ | ---------------------------------- |
| YTP_OUTPUT_TEMPLATE | The template for the filenames of the downloaded videos | `%(title)s.%(ext)s` | | YTP_OUTPUT_TEMPLATE | The template for the filenames of the downloaded videos | `%(title)s.%(ext)s` |
| YTP_DEFAULT_PRESET | The default preset to use for the download | `default` | | YTP_DEFAULT_PRESET | The default preset to use for the download | `default` |
| YTP_INSTANCE_TITLE | The title of the instance | `empty string` | | YTP_INSTANCE_TITLE | The title of the instance | `empty string` |
@ -313,4 +313,5 @@ Certain configuration values can be set via environment variables, using the `-e
| YTP_BASE_PATH | Set this if you are serving YTPTube from sub-folder | `/` | | YTP_BASE_PATH | Set this if you are serving YTPTube from sub-folder | `/` |
| YTP_PREVENT_LIVE_PREMIERE | Prevents the initial youtube premiere stream from being downloaded | `false` | | YTP_PREVENT_LIVE_PREMIERE | Prevents the initial youtube premiere stream from being downloaded | `false` |
| YTP_TASKS_HANDLER_TIMER | The cron expression for the tasks handler timer. | `*15 */1 * * *` | | YTP_TASKS_HANDLER_TIMER | The cron expression for the tasks handler timer. | `*15 */1 * * *` |
| YTP_PLAYLIST_ITEMS_CONCURRENCY | The number of playlist items be to processed at same time. | `1` |

View file

@ -202,6 +202,54 @@ class DownloadQueue(metaclass=Singleton):
LOG.error(f"Failed to cancel downloads. {e!s}") LOG.error(f"Failed to cancel downloads. {e!s}")
async def _process_playlist(self, entry: dict, item: Item, already=None): async def _process_playlist(self, entry: dict, item: Item, already=None):
if 1 == self.config.playlist_items_concurrency:
return await self._process_playlist_old(entry=entry, item=item, already=already)
LOG.info(f"Playlist '{entry.get('id')}: {entry.get('title')}' processing.")
entries = entry.get("entries", [])
playlistCount = int(entry.get("playlist_count", len(entries)))
results = []
semaphore = asyncio.Semaphore(self.config.playlist_items_concurrency)
async def process_entry(i, etr):
extras = {
"playlist": entry.get("id"),
"playlist_index": f"{{0:0{len(str(playlistCount))}d}}".format(i),
"playlist_autonumber": i,
}
for property in ("id", "title", "uploader", "uploader_id"):
if property in entry:
extras[f"playlist_{property}"] = entry.get(property)
LOG.debug(f"Processing entry {i}/{playlistCount} - ID: {etr.get('id')} - Title: {etr.get('title')}")
if "thumbnail" not in etr and "youtube:" in entry.get("extractor", ""):
extras["thumbnail"] = f"https://img.youtube.com/vi/{etr['id']}/maxresdefault.jpg"
async with semaphore:
return await self.add(
item=item.new_with(url=etr.get("url") or etr.get("webpage_url"), extras=extras),
already=already,
)
tasks = [process_entry(i, etr) for i, etr in enumerate(entries, start=1)]
results = await asyncio.gather(*tasks)
LOG.info(
f"Playlist '{entry.get('id')}: {entry.get('title')}' processing completed with '{len(results)}' entries."
)
if any("error" == res["status"] for res in results):
return {
"status": "error",
"msg": ", ".join(res["msg"] for res in results if res["status"] == "error" and "msg" in res),
}
return {"status": "ok"}
async def _process_playlist_old(self, entry: dict, item: Item, already=None):
LOG.info(f"Playlist '{entry.get('id')}: {entry.get('title')}' processing.") LOG.info(f"Playlist '{entry.get('id')}: {entry.get('title')}' processing.")
entries = entry.get("entries", []) entries = entry.get("entries", [])
playlistCount = int(entry.get("playlist_count", len(entries))) playlistCount = int(entry.get("playlist_count", len(entries)))
@ -221,6 +269,8 @@ 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"
LOG.debug(f"Processing entry {i}/{playlistCount} - ID: {etr.get('id')} - Title: {etr.get('title')}")
results.append( results.append(
await self.add( await self.add(
item=item.new_with(url=etr.get("url") or etr.get("webpage_url"), extras=extras), item=item.new_with(url=etr.get("url") or etr.get("webpage_url"), extras=extras),

View file

@ -167,6 +167,9 @@ class Config:
prevent_live_premiere: bool = False prevent_live_premiere: bool = False
"""Prevent downloading of the initial premiere live broadcast.""" """Prevent downloading of the initial premiere live broadcast."""
playlist_items_concurrency: int = 1
"""The number of concurrent playlist items to be processed at same time."""
pictures_backends: list[str] = [ pictures_backends: list[str] = [
"https://unsplash.it/1920/1080?random", "https://unsplash.it/1920/1080?random",
"https://picsum.photos/1920/1080", "https://picsum.photos/1920/1080",
@ -199,6 +202,7 @@ class Config:
"socket_timeout", "socket_timeout",
"extract_info_timeout", "extract_info_timeout",
"debugpy_port", "debugpy_port",
"playlist_items_concurrency",
) )
"The variables that are integers." "The variables that are integers."