Skip to content

Commit 00e9cea

Browse files
authored
[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 ```
1 parent 6c180c9 commit 00e9cea

6 files changed

Lines changed: 249 additions & 3 deletions

File tree

docs/source/config_reference/prebuilt_presets/common.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ Common
44

55
.. highlight:: yaml
66

7-
Filtering
8-
-------------
7+
Filter Keywords
8+
---------------
9+
10+
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml
11+
12+
Filter Duration
13+
---------------
914

10-
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filtering.yaml
15+
.. literalinclude:: /../../src/ytdl_sub/prebuilt_presets/helpers/filter_duration.yaml
1116

1217
Media Quality
1318
-------------

docs/source/prebuilt_presets/helpers.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,35 @@ Supports the following override variables:
7171
- "maple leafs"
7272
- "highlights"
7373
74+
Filter Duration
75+
---------------
76+
77+
``Filter Duration`` can include or exclude media based on its duration.
78+
79+
Supports the following override variables:
80+
81+
* ``filter_duration_min_s``
82+
* ``filter_duration_max_s``
83+
84+
.. tip::
85+
86+
Use the `~` tilda subscription mode to set a subscription's list override variables.
87+
Tilda mode allows override variables to be set directly underneath it.
88+
89+
.. code-block:: yaml
90+
91+
Plex TV Show by Date | Filter Duration:
92+
93+
= Documentaries:
94+
"~NOVA PBS":
95+
url: "https://www.youtube.com/@novapbs"
96+
filter_duration_min_s: 120 # Only download videos at least 2m long
97+
98+
= Sports:
99+
"~Maple Leafs Highlights":
100+
url: "https://www.youtube.com/@NHL"
101+
filter_duration_max_s: 180 # Only get highlight videos less than 3m long
102+
74103
Chunk Downloads
75104
---------------
76105

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
presets:
2+
3+
#############################################################################
4+
# Include Keywords
5+
# Include or exclude media with any of the listed keywords in their titles
6+
# Keywords will check a lower-cased title or description
7+
Filter Duration:
8+
overrides:
9+
filter_duration_min_s: 0
10+
filter_duration_max_s: 4294967296
11+
12+
"%filter_duration_ensure_numeric": >-
13+
{
14+
%assert_then(
15+
%is_numeric($0),
16+
$0,
17+
"filter_duration args must be numeric"
18+
)
19+
}
20+
21+
filter_duration_zero_msg: "Duration metadata for {title} is missing, cannot perform filter."
22+
23+
"%filter_duration_eval": >-
24+
{
25+
%if(
26+
%eq(duration, 0),
27+
%print(filter_duration_zero_msg, False)
28+
$0
29+
)
30+
}
31+
32+
33+
filter_exclude:
34+
- "{ %filter_duration_eval( %lt(duration, %filter_duration_ensure_numeric(filter_duration_min_s)) ) }"
35+
- "{ %filter_duration_eval( %gt(duration, %filter_duration_ensure_numeric(filter_duration_max_s)) ) }"

src/ytdl_sub/prebuilt_presets/helpers/filtering.yaml renamed to src/ytdl_sub/prebuilt_presets/helpers/filter_keywords.yaml

File renamed without changes.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import pytest
2+
from expected_transaction_log import assert_transaction_log_matches
3+
4+
from ytdl_sub.script.utils.exceptions import UserThrownRuntimeError
5+
from ytdl_sub.subscriptions.subscription import Subscription
6+
7+
8+
@pytest.fixture
9+
def filter_subscription_dict(output_directory):
10+
return {
11+
"preset": [
12+
"Plex TV Show by Date",
13+
"Filter Duration",
14+
],
15+
"overrides": {"url": "https://your.name.here", "tv_show_directory": output_directory},
16+
}
17+
18+
19+
class TestFilterKeywords:
20+
21+
def test_no_overrides(
22+
self,
23+
config,
24+
filter_subscription_dict,
25+
output_directory,
26+
subscription_name,
27+
mock_download_collection_entries,
28+
):
29+
subscription = Subscription.from_dict(
30+
config=config,
31+
preset_name=subscription_name,
32+
preset_dict=filter_subscription_dict,
33+
)
34+
35+
with mock_download_collection_entries(
36+
is_youtube_channel=False, num_urls=1, is_dry_run=True
37+
):
38+
transaction_log = subscription.download(dry_run=True)
39+
40+
assert_transaction_log_matches(
41+
output_directory=output_directory,
42+
transaction_log=transaction_log,
43+
transaction_log_summary_file_name=f"integration/prebuilt_presets/filter_keywords_empty.txt",
44+
)
45+
46+
@pytest.mark.parametrize(
47+
"filter_eval, filter_value, filters_all",
48+
[
49+
("min", 10, False),
50+
("max", 100, False),
51+
("min", 100, True),
52+
("max", 10, True),
53+
],
54+
)
55+
def test_filter_duration(
56+
self,
57+
config,
58+
filter_subscription_dict,
59+
output_directory,
60+
subscription_name,
61+
mock_download_collection_entries,
62+
filter_eval: str,
63+
filter_value: int,
64+
filters_all: bool,
65+
):
66+
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = filter_value
67+
subscription = Subscription.from_dict(
68+
config=config,
69+
preset_name=subscription_name,
70+
preset_dict=filter_subscription_dict,
71+
)
72+
73+
with mock_download_collection_entries(
74+
is_youtube_channel=False, num_urls=1, is_dry_run=True
75+
):
76+
transaction_log = subscription.download(dry_run=True)
77+
78+
if filters_all:
79+
assert transaction_log.is_empty
80+
else:
81+
assert_transaction_log_matches(
82+
output_directory=output_directory,
83+
transaction_log=transaction_log,
84+
transaction_log_summary_file_name=f"integration/prebuilt_presets/filter_duration.txt",
85+
)
86+
87+
@pytest.mark.parametrize(
88+
"filter_eval",
89+
["min", "max"],
90+
)
91+
def test_error_not_numeric(
92+
self,
93+
config,
94+
filter_subscription_dict,
95+
output_directory,
96+
subscription_name,
97+
mock_download_collection_entries,
98+
filter_eval,
99+
):
100+
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = "egg"
101+
subscription = Subscription.from_dict(
102+
config=config,
103+
preset_name=subscription_name,
104+
preset_dict=filter_subscription_dict,
105+
)
106+
107+
with (
108+
mock_download_collection_entries(is_youtube_channel=False, num_urls=1, is_dry_run=True),
109+
pytest.raises(UserThrownRuntimeError, match=f"filter_duration args must be numeric"),
110+
):
111+
_ = subscription.download(dry_run=True)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
Files created:
2+
----------------------------------------
3+
{output_directory}
4+
.ytdl-sub-subscription_test-download-archive.json
5+
{output_directory}/Season 2020
6+
s2020.e080701 - Mock Entry 20-3-thumb.jpg
7+
s2020.e080701 - Mock Entry 20-3.info.json
8+
s2020.e080701 - Mock Entry 20-3.mp4
9+
Video Tags:
10+
contentRating: TV-14
11+
date: 2020-08-07
12+
episode_id: 80701
13+
genre: ytdl-sub
14+
show: subscription_test
15+
synopsis:
16+
https://20-3.com
17+
18+
The Description
19+
title: 2020-08-07 - Mock Entry 20-3
20+
year: 2020
21+
s2020.e080801 - Mock Entry 20-2-thumb.jpg
22+
s2020.e080801 - Mock Entry 20-2.info.json
23+
s2020.e080801 - Mock Entry 20-2.mp4
24+
Video Tags:
25+
contentRating: TV-14
26+
date: 2020-08-08
27+
episode_id: 80801
28+
genre: ytdl-sub
29+
show: subscription_test
30+
synopsis:
31+
https://20-2.com
32+
33+
The Description
34+
title: 2020-08-08 - Mock Entry 20-2
35+
year: 2020
36+
s2020.e080802 - Mock Entry 20-1-thumb.jpg
37+
s2020.e080802 - Mock Entry 20-1.info.json
38+
s2020.e080802 - Mock Entry 20-1.mp4
39+
Video Tags:
40+
contentRating: TV-14
41+
date: 2020-08-08
42+
episode_id: 80802
43+
genre: ytdl-sub
44+
show: subscription_test
45+
synopsis:
46+
https://20-1.com
47+
48+
The Description
49+
title: 2020-08-08 - Mock Entry 20-1
50+
year: 2020
51+
{output_directory}/Season 2021
52+
s2021.e080801 - Mock Entry 21-1-thumb.jpg
53+
s2021.e080801 - Mock Entry 21-1.info.json
54+
s2021.e080801 - Mock Entry 21-1.mp4
55+
Video Tags:
56+
contentRating: TV-14
57+
date: 2021-08-08
58+
episode_id: 80801
59+
genre: ytdl-sub
60+
show: subscription_test
61+
synopsis:
62+
https://21-1.com
63+
64+
The Description
65+
title: 2021-08-08 - Mock Entry 21-1
66+
year: 2021

0 commit comments

Comments
 (0)