[FEATURE] Filter Duration prebuilt preset (#1259)
Adds the prebuilt preset ``Filter Duration``, which can include/exclude media based on its duration.
Supports the following override variables:
* ``filter_duration_min_s``
* ``filter_duration_max_s``
Usage:
```
Plex TV Show by Date | Filter Duration:
= Documentaries:
"~NOVA PBS":
url: "https://www.youtube.com/@novapbs"
filter_duration_min_s: 120 # Only download videos at least 2m long
= Sports:
"~Maple Leafs Highlights":
url: "https://www.youtube.com/@NHL"
filter_duration_max_s: 180 # Only get highlight videos less than 3m long
```
This commit is contained in:
parent
6c180c9478
commit
00e9ceaf3d
6 changed files with 249 additions and 3 deletions
|
|
@ -4,10 +4,15 @@ Common
|
||||||
|
|
||||||
.. highlight:: yaml
|
.. highlight:: yaml
|
||||||
|
|
||||||
Filtering
|
Filter Keywords
|
||||||
-------------
|
---------------
|
||||||
|
|
||||||
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filtering.yaml
|
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml
|
||||||
|
|
||||||
|
Filter Duration
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml
|
||||||
|
|
||||||
Media Quality
|
Media Quality
|
||||||
-------------
|
-------------
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,35 @@ Supports the following override variables:
|
||||||
- "maple leafs"
|
- "maple leafs"
|
||||||
- "highlights"
|
- "highlights"
|
||||||
|
|
||||||
|
Filter Duration
|
||||||
|
---------------
|
||||||
|
|
||||||
|
``Filter Duration`` can include or exclude media based on its duration.
|
||||||
|
|
||||||
|
Supports the following override variables:
|
||||||
|
|
||||||
|
* ``filter_duration_min_s``
|
||||||
|
* ``filter_duration_max_s``
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
Use the `~` tilda subscription mode to set a subscription's list override variables.
|
||||||
|
Tilda mode allows override variables to be set directly underneath it.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
Plex TV Show by Date | Filter Duration:
|
||||||
|
|
||||||
|
= Documentaries:
|
||||||
|
"~NOVA PBS":
|
||||||
|
url: "https://www.youtube.com/@novapbs"
|
||||||
|
filter_duration_min_s: 120 # Only download videos at least 2m long
|
||||||
|
|
||||||
|
= Sports:
|
||||||
|
"~Maple Leafs Highlights":
|
||||||
|
url: "https://www.youtube.com/@NHL"
|
||||||
|
filter_duration_max_s: 180 # Only get highlight videos less than 3m long
|
||||||
|
|
||||||
Chunk Downloads
|
Chunk Downloads
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
||||||
35
src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml
Normal file
35
src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
presets:
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Include Keywords
|
||||||
|
# Include or exclude media with any of the listed keywords in their titles
|
||||||
|
# Keywords will check a lower-cased title or description
|
||||||
|
Filter Duration:
|
||||||
|
overrides:
|
||||||
|
filter_duration_min_s: 0
|
||||||
|
filter_duration_max_s: 4294967296
|
||||||
|
|
||||||
|
"%filter_duration_ensure_numeric": >-
|
||||||
|
{
|
||||||
|
%assert_then(
|
||||||
|
%is_numeric($0),
|
||||||
|
$0,
|
||||||
|
"filter_duration args must be numeric"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_duration_zero_msg: "Duration metadata for {title} is missing, cannot perform filter."
|
||||||
|
|
||||||
|
"%filter_duration_eval": >-
|
||||||
|
{
|
||||||
|
%if(
|
||||||
|
%eq(duration, 0),
|
||||||
|
%print(filter_duration_zero_msg, False)
|
||||||
|
$0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
filter_exclude:
|
||||||
|
- "{ %filter_duration_eval( %lt(duration, %filter_duration_ensure_numeric(filter_duration_min_s)) ) }"
|
||||||
|
- "{ %filter_duration_eval( %gt(duration, %filter_duration_ensure_numeric(filter_duration_max_s)) ) }"
|
||||||
111
tests/integration/prebuilt_presets/test_filter_duration.py
Normal file
111
tests/integration/prebuilt_presets/test_filter_duration.py
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import pytest
|
||||||
|
from expected_transaction_log import assert_transaction_log_matches
|
||||||
|
|
||||||
|
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
|
||||||
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def filter_subscription_dict(output_directory):
|
||||||
|
return {
|
||||||
|
"preset": [
|
||||||
|
"Plex TV Show by Date",
|
||||||
|
"Filter Duration",
|
||||||
|
],
|
||||||
|
"overrides": {"url": "https://your.name.here", "tv_show_directory": output_directory},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestFilterKeywords:
|
||||||
|
|
||||||
|
def test_no_overrides(
|
||||||
|
self,
|
||||||
|
config,
|
||||||
|
filter_subscription_dict,
|
||||||
|
output_directory,
|
||||||
|
subscription_name,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
):
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=filter_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False, num_urls=1, is_dry_run=True
|
||||||
|
):
|
||||||
|
transaction_log = subscription.download(dry_run=True)
|
||||||
|
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name=f"integration/prebuilt_presets/filter_keywords_empty.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"filter_eval, filter_value, filters_all",
|
||||||
|
[
|
||||||
|
("min", 10, False),
|
||||||
|
("max", 100, False),
|
||||||
|
("min", 100, True),
|
||||||
|
("max", 10, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_filter_duration(
|
||||||
|
self,
|
||||||
|
config,
|
||||||
|
filter_subscription_dict,
|
||||||
|
output_directory,
|
||||||
|
subscription_name,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
filter_eval: str,
|
||||||
|
filter_value: int,
|
||||||
|
filters_all: bool,
|
||||||
|
):
|
||||||
|
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = filter_value
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=filter_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock_download_collection_entries(
|
||||||
|
is_youtube_channel=False, num_urls=1, is_dry_run=True
|
||||||
|
):
|
||||||
|
transaction_log = subscription.download(dry_run=True)
|
||||||
|
|
||||||
|
if filters_all:
|
||||||
|
assert transaction_log.is_empty
|
||||||
|
else:
|
||||||
|
assert_transaction_log_matches(
|
||||||
|
output_directory=output_directory,
|
||||||
|
transaction_log=transaction_log,
|
||||||
|
transaction_log_summary_file_name=f"integration/prebuilt_presets/filter_duration.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"filter_eval",
|
||||||
|
["min", "max"],
|
||||||
|
)
|
||||||
|
def test_error_not_numeric(
|
||||||
|
self,
|
||||||
|
config,
|
||||||
|
filter_subscription_dict,
|
||||||
|
output_directory,
|
||||||
|
subscription_name,
|
||||||
|
mock_download_collection_entries,
|
||||||
|
filter_eval,
|
||||||
|
):
|
||||||
|
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = "egg"
|
||||||
|
subscription = Subscription.from_dict(
|
||||||
|
config=config,
|
||||||
|
preset_name=subscription_name,
|
||||||
|
preset_dict=filter_subscription_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock_download_collection_entries(is_youtube_channel=False, num_urls=1, is_dry_run=True),
|
||||||
|
pytest.raises(UserThrownRuntimeError, match=f"filter_duration args must be numeric"),
|
||||||
|
):
|
||||||
|
_ = subscription.download(dry_run=True)
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
Files created:
|
||||||
|
----------------------------------------
|
||||||
|
{output_directory}
|
||||||
|
.ytdl-sub-subscription_test-download-archive.json
|
||||||
|
{output_directory}/Season 2020
|
||||||
|
s2020.e080701 - Mock Entry 20-3-thumb.jpg
|
||||||
|
s2020.e080701 - Mock Entry 20-3.info.json
|
||||||
|
s2020.e080701 - Mock Entry 20-3.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-07
|
||||||
|
episode_id: 80701
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: subscription_test
|
||||||
|
synopsis:
|
||||||
|
https://20-3.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-07 - Mock Entry 20-3
|
||||||
|
year: 2020
|
||||||
|
s2020.e080801 - Mock Entry 20-2-thumb.jpg
|
||||||
|
s2020.e080801 - Mock Entry 20-2.info.json
|
||||||
|
s2020.e080801 - Mock Entry 20-2.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 80801
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: subscription_test
|
||||||
|
synopsis:
|
||||||
|
https://20-2.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-2
|
||||||
|
year: 2020
|
||||||
|
s2020.e080802 - Mock Entry 20-1-thumb.jpg
|
||||||
|
s2020.e080802 - Mock Entry 20-1.info.json
|
||||||
|
s2020.e080802 - Mock Entry 20-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2020-08-08
|
||||||
|
episode_id: 80802
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: subscription_test
|
||||||
|
synopsis:
|
||||||
|
https://20-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2020-08-08 - Mock Entry 20-1
|
||||||
|
year: 2020
|
||||||
|
{output_directory}/Season 2021
|
||||||
|
s2021.e080801 - Mock Entry 21-1-thumb.jpg
|
||||||
|
s2021.e080801 - Mock Entry 21-1.info.json
|
||||||
|
s2021.e080801 - Mock Entry 21-1.mp4
|
||||||
|
Video Tags:
|
||||||
|
contentRating: TV-14
|
||||||
|
date: 2021-08-08
|
||||||
|
episode_id: 80801
|
||||||
|
genre: ytdl-sub
|
||||||
|
show: subscription_test
|
||||||
|
synopsis:
|
||||||
|
https://21-1.com
|
||||||
|
|
||||||
|
The Description
|
||||||
|
title: 2021-08-08 - Mock Entry 21-1
|
||||||
|
year: 2021
|
||||||
Loading…
Reference in a new issue