Implements ytdlp filter logic to our own playlist processing.
This commit is contained in:
parent
25b7aad606
commit
ebb7299053
3 changed files with 71 additions and 5 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -45,6 +45,7 @@
|
|||
"libjavascriptcoregtk",
|
||||
"libwebkit",
|
||||
"libx",
|
||||
"matchtitle",
|
||||
"matroska",
|
||||
"mbed",
|
||||
"mccabe",
|
||||
|
|
@ -71,6 +72,7 @@
|
|||
"pywebview",
|
||||
"qtpy",
|
||||
"quicktime",
|
||||
"rejecttitle",
|
||||
"rtime",
|
||||
"smhd",
|
||||
"socketio",
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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, "")
|
||||
|
|
|
|||
Loading…
Reference in a new issue