Implements ytdlp filter logic to our own playlist processing.

This commit is contained in:
arabcoders 2025-06-27 20:12:32 +03:00
parent 25b7aad606
commit ebb7299053
3 changed files with 71 additions and 5 deletions

View file

@ -45,6 +45,7 @@
"libjavascriptcoregtk",
"libwebkit",
"libx",
"matchtitle",
"matroska",
"mbed",
"mccabe",
@ -71,6 +72,7 @@
"pywebview",
"qtpy",
"quicktime",
"rejecttitle",
"rtime",
"smhd",
"socketio",

View file

@ -35,6 +35,7 @@ from .Utils import (
is_downloaded,
load_cookies,
str_to_dt,
ytdlp_reject,
)
from .YTDLPOpts import YTDLPOpts
@ -203,7 +204,10 @@ class DownloadQueue(metaclass=Singleton):
except Exception as e:
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, yt_params: dict | None = None):
if not yt_params:
yt_params = {}
entries = entry.get("entries", [])
LOG.info(f"Processing '{entry.get('id')}: {entry.get('title')}' Playlist.")
@ -216,6 +220,11 @@ class DownloadQueue(metaclass=Singleton):
try:
LOG.debug(f"Processing entry {i}/{playlistCount} - ID: {etr.get('id')} - Title: {etr.get('title')}")
_status, _msg = ytdlp_reject(entry=etr, yt_params=yt_params)
if not _status:
LOG.debug(_msg)
return {"status": "ok", "msg": _msg}
extras = {
"playlist": entry.get("title") or entry.get("id"),
"playlist_count": playlistCount,
@ -424,7 +433,9 @@ class DownloadQueue(metaclass=Singleton):
LOG.error(f"Failed to download item. '{e!s}'")
return {"status": "error", "msg": str(e)}
async def _add_item(self, entry: dict, item: Item, already=None, logs: list | None = None):
async def _add_item(
self, entry: dict, item: Item, already=None, logs: list | None = None, yt_params: dict | None = None
):
"""
Add an entry to the download queue.
@ -433,6 +444,7 @@ class DownloadQueue(metaclass=Singleton):
item (Item): The item to add to the download queue.
already (set): The set of already downloaded items.
logs (list): The list of logs generated during information extraction.
yt_params (dict): The parameters for yt-dlp.
Returns:
dict: The status of the operation.
@ -444,7 +456,7 @@ class DownloadQueue(metaclass=Singleton):
event_type = entry.get("_type", "video")
if event_type.startswith("playlist"):
return await self._process_playlist(entry=entry, item=item, already=already)
return await self._process_playlist(entry=entry, item=item, already=already, yt_params=yt_params)
if event_type.startswith("url"):
return await self.add(item=item.new_with(url=entry.get("url")), already=already)
@ -556,6 +568,11 @@ class DownloadQueue(metaclass=Singleton):
LOG.info(f"Condition '{condition.name}' matched for '{item.url}'.")
return await self.add(item=item.new_with(requeued=True, cli=condition.cli), already=already)
_status, _msg = ytdlp_reject(entry=entry, yt_params=yt_conf)
if not _status:
LOG.debug(_msg)
return {"status": "ok", "msg": _msg}
end_time = time.perf_counter() - started
LOG.debug(f"extract_info: for 'URL: {item.url}' is done in '{end_time:.3f}'. Length: '{len(entry)}/keys'.")
except yt_dlp.utils.ExistingVideoReached as exc:
@ -578,7 +595,7 @@ class DownloadQueue(metaclass=Singleton):
except Exception as e:
LOG.error(f"Failed to remove cookie file '{yt_conf['cookiefile']}'. {e!s}")
return await self._add_item(entry=entry, item=item, already=already, logs=logs)
return await self._add_item(entry=entry, item=item, already=already, logs=logs, yt_params=yt_conf)
async def cancel(self, ids: list[str]) -> dict[str, str]:
"""

View file

@ -17,7 +17,7 @@ from typing import TypeVar
import yt_dlp
from Crypto.Cipher import AES
from yt_dlp.utils import match_str
from yt_dlp.utils import age_restricted, match_str
from .LogWrapper import LogWrapper
@ -1325,3 +1325,50 @@ def str_to_dt(time_str: str, now=None) -> datetime:
raise ValueError(msg)
return dt
def ytdlp_reject(entry: dict, yt_params: dict) -> tuple[bool, str]:
"""
Implement yt-dlp reject filter logic.
Args:
entry (dict): The entry to check.
yt_params (dict): The yt-dlp parameters containing filters.
Returns:
tuple[bool, str]: A tuple where the first element is True if the entry passes the filters, or False if it does not,
and the second element is a message explaining the reason for rejection or an empty string if it passes.
"""
if title := entry.get("title"):
if (matchtitle := yt_params.get("matchtitle")) and not re.search(matchtitle, title, re.IGNORECASE):
return (False, f'"{title}" title did not match pattern "{matchtitle}". Skipping download.')
if (rejecttitle := yt_params.get("rejecttitle")) and re.search(rejecttitle, title, re.IGNORECASE):
return (False, f'"{title}" title matched reject pattern "{rejecttitle}". Skipping download.')
date = entry.get("upload_date")
date_range = yt_params.get("daterange")
if (date and date_range) and date not in date_range:
return (False, f"Upload date '{date}' is not in range '{date_range}'.")
view_count = entry.get("view_count")
if view_count is not None:
min_views = yt_params.get("min_views")
if min_views is not None and view_count < min_views:
return (
False,
f"Skipping {entry.get('title', 'video')}, because it has not reached minimum view count ({view_count}/{min_views}).",
)
max_views = yt_params.get("max_views")
if max_views is not None and view_count > max_views:
return (
False,
f"Skipping {entry.get('title', 'video')}, because it has exceeded maximum view count ({view_count}/{max_views}).",
)
if entry.get("age_limit") and age_restricted(entry.get("age_limit"), yt_params.get("age_limit")):
return (False, f'Video "{entry.get("title", "unknown")}" is age restricted.')
return (True, "")