Skip to content

Commit 40085fe

Browse files
committed
[FEATURE] Filter Duration prebuilt preset
1 parent 6c180c9 commit 40085fe

5 files changed

Lines changed: 245 additions & 0 deletions

File tree

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 Keywords:
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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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("filter_eval, filter_value, filters_all",
47+
[
48+
("min", 10, False),
49+
("max", 100, False),
50+
("min", 100, True),
51+
("max", 10, True),
52+
]
53+
)
54+
def test_filter_duration(
55+
self,
56+
config,
57+
filter_subscription_dict,
58+
output_directory,
59+
subscription_name,
60+
mock_download_collection_entries,
61+
filter_eval: str,
62+
filter_value: int,
63+
filters_all: bool,
64+
):
65+
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = filter_value
66+
subscription = Subscription.from_dict(
67+
config=config,
68+
preset_name=subscription_name,
69+
preset_dict=filter_subscription_dict,
70+
)
71+
72+
with mock_download_collection_entries(
73+
is_youtube_channel=False, num_urls=1, is_dry_run=True
74+
):
75+
transaction_log = subscription.download(dry_run=True)
76+
77+
if filters_all:
78+
assert transaction_log.is_empty
79+
else:
80+
assert_transaction_log_matches(
81+
output_directory=output_directory,
82+
transaction_log=transaction_log,
83+
transaction_log_summary_file_name=f"integration/prebuilt_presets/filter_duration.txt",
84+
)
85+
86+
87+
@pytest.mark.parametrize(
88+
"filter_eval",
89+
[
90+
"min",
91+
"max"
92+
],
93+
)
94+
def test_error_not_numeric(
95+
self,
96+
config,
97+
filter_subscription_dict,
98+
output_directory,
99+
subscription_name,
100+
mock_download_collection_entries,
101+
filter_eval,
102+
):
103+
filter_subscription_dict["overrides"][f"filter_duration_{filter_eval}_s"] = "egg"
104+
subscription = Subscription.from_dict(
105+
config=config,
106+
preset_name=subscription_name,
107+
preset_dict=filter_subscription_dict,
108+
)
109+
110+
with (
111+
mock_download_collection_entries(is_youtube_channel=False, num_urls=1, is_dry_run=True),
112+
pytest.raises(UserThrownRuntimeError, match=f"filter_duration args must be numeric"),
113+
):
114+
_ = subscription.download(dry_run=True)
115+
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)