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",
|
"libjavascriptcoregtk",
|
||||||
"libwebkit",
|
"libwebkit",
|
||||||
"libx",
|
"libx",
|
||||||
|
"matchtitle",
|
||||||
"matroska",
|
"matroska",
|
||||||
"mbed",
|
"mbed",
|
||||||
"mccabe",
|
"mccabe",
|
||||||
|
|
@ -71,6 +72,7 @@
|
||||||
"pywebview",
|
"pywebview",
|
||||||
"qtpy",
|
"qtpy",
|
||||||
"quicktime",
|
"quicktime",
|
||||||
|
"rejecttitle",
|
||||||
"rtime",
|
"rtime",
|
||||||
"smhd",
|
"smhd",
|
||||||
"socketio",
|
"socketio",
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ from .Utils import (
|
||||||
is_downloaded,
|
is_downloaded,
|
||||||
load_cookies,
|
load_cookies,
|
||||||
str_to_dt,
|
str_to_dt,
|
||||||
|
ytdlp_reject,
|
||||||
)
|
)
|
||||||
from .YTDLPOpts import YTDLPOpts
|
from .YTDLPOpts import YTDLPOpts
|
||||||
|
|
||||||
|
|
@ -203,7 +204,10 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
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, yt_params: dict | None = None):
|
||||||
|
if not yt_params:
|
||||||
|
yt_params = {}
|
||||||
|
|
||||||
entries = entry.get("entries", [])
|
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')}' Playlist.")
|
||||||
|
|
@ -216,6 +220,11 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
try:
|
try:
|
||||||
LOG.debug(f"Processing entry {i}/{playlistCount} - ID: {etr.get('id')} - Title: {etr.get('title')}")
|
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 = {
|
extras = {
|
||||||
"playlist": entry.get("title") or entry.get("id"),
|
"playlist": entry.get("title") or entry.get("id"),
|
||||||
"playlist_count": playlistCount,
|
"playlist_count": playlistCount,
|
||||||
|
|
@ -424,7 +433,9 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
LOG.error(f"Failed to download item. '{e!s}'")
|
LOG.error(f"Failed to download item. '{e!s}'")
|
||||||
return {"status": "error", "msg": str(e)}
|
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.
|
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.
|
item (Item): The item to add to the download queue.
|
||||||
already (set): The set of already downloaded items.
|
already (set): The set of already downloaded items.
|
||||||
logs (list): The list of logs generated during information extraction.
|
logs (list): The list of logs generated during information extraction.
|
||||||
|
yt_params (dict): The parameters for yt-dlp.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The status of the operation.
|
dict: The status of the operation.
|
||||||
|
|
@ -444,7 +456,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
event_type = entry.get("_type", "video")
|
event_type = entry.get("_type", "video")
|
||||||
|
|
||||||
if event_type.startswith("playlist"):
|
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"):
|
if event_type.startswith("url"):
|
||||||
return await self.add(item=item.new_with(url=entry.get("url")), already=already)
|
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}'.")
|
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)
|
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
|
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'.")
|
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:
|
except yt_dlp.utils.ExistingVideoReached as exc:
|
||||||
|
|
@ -578,7 +595,7 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Failed to remove cookie file '{yt_conf['cookiefile']}'. {e!s}")
|
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]:
|
async def cancel(self, ids: list[str]) -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
from Crypto.Cipher import AES
|
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
|
from .LogWrapper import LogWrapper
|
||||||
|
|
||||||
|
|
@ -1325,3 +1325,50 @@ def str_to_dt(time_str: str, now=None) -> datetime:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
return dt
|
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