[FEATURE] TV Show prebuilt presets that require no config (#788)
Introduces 3 new presets + 1 helper preset to download TV show-based subscriptions without the need of a config ``` Plex TV Show by Date Jellyfin TV Show by Date Kodi TV Show by Date ``` and ``` Only Recent ``` Example usage can be found in https://github.com/jmbannon/ytdl-sub/blob/master/examples/tv_show_subscriptions.yaml
This commit is contained in:
parent
9f408d2196
commit
5fb5a18abc
22 changed files with 394 additions and 416 deletions
|
|
@ -1,7 +1,13 @@
|
||||||
# Example Configurations
|
# Example Configurations
|
||||||
This directory shows how you can use ytdl-sub for various use cases. These
|
This directory shows how use ytdl-sub's built-in presets to start downloading immediately with no
|
||||||
are the configs I personally use and have incorporated as part of the e2e tests.
|
configuration required. Simply run:
|
||||||
|
|
||||||
Each example has a `config.yaml` and `subscription.yaml`. The config defines
|
### Unix
|
||||||
_how_ you format your media, whereas the subscription defines _what_ you
|
```commandline
|
||||||
download plus some additional configuring if needed.
|
ytdl-sub sub tv_show_subscriptions.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
```commandline
|
||||||
|
./ytdl-sub.exe sub tv_show_subscriptions.yaml
|
||||||
|
```
|
||||||
100
examples/advanced/tv_show_config.yaml
Normal file
100
examples/advanced/tv_show_config.yaml
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
###############################################################################
|
||||||
|
# Top-level configurations to apply umask and persist error logs
|
||||||
|
configuration:
|
||||||
|
umask: "002"
|
||||||
|
persist_logs:
|
||||||
|
logs_directory: './logs'
|
||||||
|
keep_successful_logs: False
|
||||||
|
|
||||||
|
presets:
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set tv_show_directory here instead of in the subscriptions file
|
||||||
|
tv_show_paths:
|
||||||
|
overrides:
|
||||||
|
tv_show_directory: "/tv_shows"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Filter out any YouTube shorts
|
||||||
|
no_shorts:
|
||||||
|
match_filters:
|
||||||
|
filters:
|
||||||
|
- "original_url!*=/shorts/"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Remove all the following sponsorblock sections
|
||||||
|
sponsorblock:
|
||||||
|
chapters:
|
||||||
|
sponsorblock_categories:
|
||||||
|
- "outro"
|
||||||
|
- "selfpromo"
|
||||||
|
- "preview"
|
||||||
|
- "interaction"
|
||||||
|
- "sponsor"
|
||||||
|
- "music_offtopic"
|
||||||
|
- "intro"
|
||||||
|
remove_sponsorblock_categories: "all"
|
||||||
|
force_key_frames: False
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Wait 2 days before downloading in hopes to get more accurate sponsorblock
|
||||||
|
sponsorblock_wait:
|
||||||
|
# Import the sponsorblock preset defined above
|
||||||
|
preset:
|
||||||
|
- "sponsorblock"
|
||||||
|
|
||||||
|
date_range:
|
||||||
|
before: "today-2days"
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# base preset to use on all TV Show-based subscriptions
|
||||||
|
base:
|
||||||
|
preset:
|
||||||
|
- "Kodi TV Show by Date" # Set intended player
|
||||||
|
- "best_video_quality" # prebuilt preset to get best quality
|
||||||
|
- "tv_show_paths"
|
||||||
|
|
||||||
|
# Embed chapters into video files
|
||||||
|
chapters:
|
||||||
|
embed_chapters: True
|
||||||
|
|
||||||
|
# Embed English subtitles into video files (supports more)
|
||||||
|
subtitles:
|
||||||
|
embed_subtitles: True
|
||||||
|
languages:
|
||||||
|
- "en"
|
||||||
|
allow_auto_generated_subtitles: True
|
||||||
|
|
||||||
|
# ytdl_options lets you pass any arg into yt-dlp's Python API
|
||||||
|
ytdl_options:
|
||||||
|
# Set the cookie file
|
||||||
|
# cookiefile: "/config/youtube_cookies.txt"
|
||||||
|
|
||||||
|
# For YouTube, get English metadata if multiple languages are present
|
||||||
|
extractor_args:
|
||||||
|
youtube:
|
||||||
|
lang:
|
||||||
|
- "en"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Custom preset to archive an entire channel
|
||||||
|
TV Show Full Archive:
|
||||||
|
preset:
|
||||||
|
- "base"
|
||||||
|
- "sponsorblock_wait" # wait for sponsorblock when full-archiving
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Custom preset to only fetch and keep recent videos.
|
||||||
|
# Format the videos in reverse order, so the first video is the most recent
|
||||||
|
# Also include the prebuilt "Only Recent" preset
|
||||||
|
TV Show Only Recent:
|
||||||
|
preset:
|
||||||
|
- "base"
|
||||||
|
- "sponsorblock"
|
||||||
|
- "no_shorts"
|
||||||
|
- "season_by_year__episode_by_month_day_reversed"
|
||||||
|
- "Only Recent"
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
date_range: "2months"
|
||||||
29
examples/advanced/tv_show_subscriptions.yaml
Normal file
29
examples/advanced/tv_show_subscriptions.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Subscriptions using custom presets made in `tv_show_config.yaml`
|
||||||
|
|
||||||
|
TV Show Full Archive:
|
||||||
|
= Documentaries | = TV-PG:
|
||||||
|
"NOVA PBS": "https://www.youtube.com/@novapbs"
|
||||||
|
"National Geographic": "https://www.youtube.com/@NatGeo"
|
||||||
|
"Cosmos - What If": "https://www.youtube.com/playlist?list=PLZdXRHYAVxTJno6oFF9nLGuwXNGYHmE8U"
|
||||||
|
|
||||||
|
= Kids | = TV-Y:
|
||||||
|
"Jake Trains": "https://www.youtube.com/@JakeTrains"
|
||||||
|
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
|
||||||
|
|
||||||
|
= Gardening | = TV-Y:
|
||||||
|
# TV Show presets support multiple URLs as a list
|
||||||
|
"Gardening with Ciscoe":
|
||||||
|
- "https://www.youtube.com/@gardeningwithciscoe4430"
|
||||||
|
- "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_"
|
||||||
|
- "https://www.youtube.com/playlist?list=PLsJlQSR-KjmaQqqJ9jq18cF6XXXAR4kyn"
|
||||||
|
- "https://www.youtube.com/watch?v=2vq-vPubS5I"
|
||||||
|
|
||||||
|
TV Show Only Recent:
|
||||||
|
= News | = TV-14:
|
||||||
|
# Subscriptions can prefix a tilda to specify override variables
|
||||||
|
# to set only for that subscriptions
|
||||||
|
"~BBC News":
|
||||||
|
url: "https://www.youtube.com/@BBCNews" # use url2, url3, ... for multi-url in this form
|
||||||
|
date_range: "2weeks"
|
||||||
|
"Frontline PBS": "https://www.youtube.com/@frontline"
|
||||||
|
"Whitehouse": "https://www.bitchute.com/channel/zWsYVmCOu4JA/" # Supports non-YT sites
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
# This config uses prebuilt presets included with ytdl-sub to download and format
|
|
||||||
# channels from YouTube or other sites supported by yt-dlp into a TV show for
|
|
||||||
# your favorite player. The directory format will look something like
|
|
||||||
#
|
|
||||||
# /tv_shows
|
|
||||||
# /Season 2021
|
|
||||||
# s2021.e0317 - Pattys Day Video-thumb.jpg
|
|
||||||
# s2021.e0317 - Pattys Day Video.mp4
|
|
||||||
# s2021.e0317 - Pattys Day Video.nfo
|
|
||||||
# /Season 2022
|
|
||||||
# s2022.e1225 - Merry Christmas-thumb.jpg
|
|
||||||
# s2022.e1225 - Merry Christmas.mp4
|
|
||||||
# s2022.e1225 - Merry Christmas.nfo
|
|
||||||
# poster.jpg
|
|
||||||
# fanart.jpg
|
|
||||||
# tvshow.nfo
|
|
||||||
#
|
|
||||||
# The idea is to use dates as numerics to represent season and episode numbers.
|
|
||||||
configuration:
|
|
||||||
working_directory: '.ytdl-sub-downloads'
|
|
||||||
|
|
||||||
presets:
|
|
||||||
|
|
||||||
# Your main TV show preset - all your tv show subscriptions will use this.
|
|
||||||
"TV Show Full Archive":
|
|
||||||
preset:
|
|
||||||
# Choose one of the following player types:
|
|
||||||
# - "kodi_tv_show_by_date"
|
|
||||||
# - "jellyfin_tv_show_by_date"
|
|
||||||
# - "plex_tv_show_by_date"
|
|
||||||
|
|
||||||
- "kodi_tv_show_by_date" # replace with desired player type
|
|
||||||
|
|
||||||
# Choose one of the following season/episode formats:
|
|
||||||
# - "season_by_year__episode_by_month_day"
|
|
||||||
# - "season_by_year_month__episode_by_day"
|
|
||||||
# - "season_by_year__episode_by_month_day_reversed"
|
|
||||||
# - "season_by_year__episode_by_download_index"
|
|
||||||
|
|
||||||
- "season_by_year__episode_by_month_day" # replace with desired season/episode format
|
|
||||||
|
|
||||||
# Include any of the presets listed below in your 'main preset' if you want
|
|
||||||
# it applied to every TV show. Or, use them on the individual subscriptions.
|
|
||||||
# - "add_subtitles"
|
|
||||||
# - "sponsorblock"
|
|
||||||
|
|
||||||
# To download age-restricted videos, you will need to uncomment and set your cookie
|
|
||||||
# file here as a ytdl parameter. For more info, see
|
|
||||||
# https://ytdl-sub.readthedocs.io/en/latest/faq.html#download-age-restricted-youtube-videos
|
|
||||||
#
|
|
||||||
# ytdl_options:
|
|
||||||
# cookiefile: "/config/cookie_file.txt" # replace with actual cookie file path
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
tv_show_directory: "/tv_shows" # replace with path to tv show directory
|
|
||||||
# Fields in the prebuilt preset that can be changed:
|
|
||||||
#
|
|
||||||
# episode_title: "{upload_date_standardized} - {title}"
|
|
||||||
# episode_plot: "{webpage_url}" # source variable for the video description is {description}
|
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
|
|
||||||
# A secondary TV Show preset that only keeps recent videos
|
|
||||||
"TV Show Only Recent":
|
|
||||||
|
|
||||||
# Inherit the `TV Show Full Archive` preset
|
|
||||||
preset:
|
|
||||||
- "TV Show Full Archive"
|
|
||||||
|
|
||||||
# Only download videos within the download_range
|
|
||||||
date_range:
|
|
||||||
after: "today-{download_range}"
|
|
||||||
|
|
||||||
# Deletes any videos older than download_range. WARNING: do not use
|
|
||||||
# "season_by_year__episode_by_download_index" if you plan to delete older videos
|
|
||||||
output_options:
|
|
||||||
keep_files_after: "today-{download_range}"
|
|
||||||
|
|
||||||
# Set the duration of download_range, defaults to 2 months
|
|
||||||
overrides:
|
|
||||||
download_range: "2months"
|
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
|
|
||||||
# Preset to download subtitles (either by file or embedded)
|
|
||||||
add_subtitles:
|
|
||||||
subtitles:
|
|
||||||
# Embed subtitles into the video
|
|
||||||
embed_subtitles: True
|
|
||||||
# And/or download them as a file. Uncomment to download as file:
|
|
||||||
# subtitles_name: "{episode_file_path}.{lang}.{subtitles_ext}"
|
|
||||||
# subtitles_type: "srt"
|
|
||||||
|
|
||||||
languages:
|
|
||||||
- "en"
|
|
||||||
allow_auto_generated_subtitles: True # allow auto subtitles
|
|
||||||
|
|
||||||
####################################################################################################
|
|
||||||
|
|
||||||
# Preset to cut sponsor segments from videos
|
|
||||||
sponsorblock:
|
|
||||||
# If you download using cron, it is wise to add a delay before downloading ad-filled content to
|
|
||||||
# give folks time to submit sponsor segments. Uncomment to wait 2 days before download a video.
|
|
||||||
# date_range:
|
|
||||||
# before: "today-2days"
|
|
||||||
|
|
||||||
chapters:
|
|
||||||
# Remove all of these sponsorblock categories
|
|
||||||
sponsorblock_categories:
|
|
||||||
- "intro"
|
|
||||||
- "outro"
|
|
||||||
- "selfpromo"
|
|
||||||
- "preview"
|
|
||||||
- "interaction"
|
|
||||||
- "sponsor"
|
|
||||||
- "music_offtopic"
|
|
||||||
remove_sponsorblock_categories: "all"
|
|
||||||
force_key_frames: False
|
|
||||||
|
|
@ -1,24 +1,55 @@
|
||||||
# Global overrides for all subscriptions. Can either be set here or in your config.yaml
|
# This example downloads the entirety of a channel (not limited to YouTube).
|
||||||
|
# Files will be stored in the form of:
|
||||||
|
#
|
||||||
|
# /tv_shows
|
||||||
|
# /Season 2021
|
||||||
|
# s2021.e031701 - Pattys Day Video-thumb.jpg
|
||||||
|
# s2021.e031701 - Pattys Day Video.mp4
|
||||||
|
# s2021.e031701 - Pattys Day Video.nfo
|
||||||
|
# s2021.e031702 - Second Pattys Day Video-thumb.jpg
|
||||||
|
# s2021.e031702 - Second Pattys Day Video.mp4
|
||||||
|
# s2021.e031702 - Second Pattys Day Video.nfo
|
||||||
|
# /Season 2022
|
||||||
|
# s2022.e122501 - Merry Christmas-thumb.jpg
|
||||||
|
# s2022.e122501 - Merry Christmas.mp4
|
||||||
|
# s2022.e122501 - Merry Christmas.nfo
|
||||||
|
# poster.jpg
|
||||||
|
# fanart.jpg
|
||||||
|
# tvshow.nfo
|
||||||
|
#
|
||||||
|
# The idea is to use dates as numerics to represent season and episode numbers.
|
||||||
|
|
||||||
|
# Overrides to the prebuilt presets
|
||||||
__preset__:
|
__preset__:
|
||||||
overrides:
|
overrides:
|
||||||
tv_show_directory: "/tv_shows" # Root folder of ytdl-sub TV Shows
|
tv_show_directory: "/tv_shows" # Root folder of all ytdl-sub TV Shows
|
||||||
download_range: "2months" # Range to keep for only recent
|
date_range: "2months" # For 'Only Recent' preset, only keep vids uploaded in this range
|
||||||
|
|
||||||
|
# Choose the player you intend to use by setting the top-level key to be either:
|
||||||
|
# - Plex TV Show by Date:
|
||||||
|
# - Jellyfin TV Show by Date:
|
||||||
|
# - Kodi TV Show by Date:
|
||||||
|
Plex TV Show by Date:
|
||||||
|
|
||||||
# All subscriptions under this will use the `TV Show Full Archive` preset
|
# Sets genre tag to "Documentaries"
|
||||||
TV Show Full Archive:
|
= Documentaries:
|
||||||
# Sets "Music" for genre
|
"NOVA PBS": "https://www.youtube.com/@novapbs"
|
||||||
= Music:
|
"National Geographic": "https://www.youtube.com/@NatGeo"
|
||||||
"Rick A": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw"
|
"Cosmos - What If": "https://www.youtube.com/playlist?list=PLZdXRHYAVxTJno6oFF9nLGuwXNGYHmE8U"
|
||||||
"Opeth": "https://www.youtube.com/channel/UCmQSJTFZaXN85gYk6W3XbdQ"
|
|
||||||
|
|
||||||
# Sets "Kids" for genre, "TV-Y" for content rating
|
# Sets genre tag to "Kids", "TV-Y" for content rating
|
||||||
= Kids | = TV-Y:
|
= Kids | = TV-Y:
|
||||||
"Jake Trains": "https://www.youtube.com/@JakeTrains"
|
"Jake Trains": "https://www.youtube.com/@JakeTrains"
|
||||||
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
|
"Kids Toys Play": "https://www.youtube.com/@KidsToysPlayChannel"
|
||||||
|
|
||||||
|
# Sets genre tag to "Music"
|
||||||
|
= Music:
|
||||||
|
# Subscriptions can support multiple urls and store in the same
|
||||||
|
# TV Show
|
||||||
|
"Rick Beato":
|
||||||
|
- "https://www.youtube.com/@RickBeato"
|
||||||
|
- "https://www.youtube.com/@rickbeato240"
|
||||||
|
|
||||||
# All subscriptions under this will use the `TV Show Only Recent` preset
|
# Set "News" for genre, use `Only Recent` preset to only store videos uploaded recently
|
||||||
TV Show Only Recent:
|
= News | Only Recent:
|
||||||
= News | = TV-14:
|
"BBC News": "https://www.youtube.com/@BBCNews"
|
||||||
"BBC": "https://www.youtube.com/@BBCNews"
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
from ytdl_sub.entries.variables.entry_variables import pad
|
|
||||||
|
|
||||||
Preset = Dict[str, Any]
|
|
||||||
|
|
||||||
####################################################
|
|
||||||
# TV SHOW TYPES
|
|
||||||
|
|
||||||
KODI_TV_SHOW = "_kodi_tv_show"
|
|
||||||
JELLYFIN_TV_SHOW = "_jellyfin_tv_show"
|
|
||||||
PLEX_TV_SHOW = "_plex_tv_show"
|
|
||||||
|
|
||||||
#####################################################
|
|
||||||
# TV SHOW URL PRESETS
|
|
||||||
|
|
||||||
TV_SHOW_BY_DATE = "_tv_show_by_date"
|
|
||||||
|
|
||||||
SEASON_YEAR__EPISODE_MONTH_DAY = "season_by_year__episode_by_month_day"
|
|
||||||
SEASON_YEAR__EPISODE_MONTH_DAY_REVERSED = "season_by_year__episode_by_month_day_reversed"
|
|
||||||
SEASON_YEAR_MONTH__EPISODE_DAY = "season_by_year_month__episode_by_day"
|
|
||||||
|
|
||||||
#####################################################
|
|
||||||
# TV SHOW COLLECTION PRESETS
|
|
||||||
|
|
||||||
TV_SHOW_COLLECTION = "_tv_show_collection"
|
|
||||||
|
|
||||||
SEASON_COLLECTION__EPISODE_Y_M_D = "season_by_collection__episode_by_year_month_day"
|
|
||||||
SEASON_COLLECTION__EPISODE_Y_M_D_REV = "season_by_collection__episode_by_year_month_day_reversed"
|
|
||||||
|
|
||||||
|
|
||||||
class PrebuiltPresets:
|
|
||||||
@classmethod
|
|
||||||
def _build_preset(cls, name: str, parent_presets: List[str]) -> Preset:
|
|
||||||
return {"presets": {name: {"preset": parent_presets}}}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _document_preset(cls) -> Preset:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_preset_names(cls) -> Set[str]:
|
|
||||||
"""
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Preset names in the set
|
|
||||||
"""
|
|
||||||
preset_names = [prop for prop in dir(cls) if isinstance(getattr(cls, prop), property)]
|
|
||||||
return set(preset_names)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_presets(cls) -> List[Preset]:
|
|
||||||
"""
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
Preset dicts in the set if they are not added in other yamls
|
|
||||||
"""
|
|
||||||
return [getattr(cls(), preset_name) for preset_name in cls.get_preset_names()]
|
|
||||||
|
|
||||||
|
|
||||||
class TvShowByDatePresets(PrebuiltPresets):
|
|
||||||
"""
|
|
||||||
TV Show by Date presets create a TV show from a single URL using upload dates as season/episode
|
|
||||||
numbers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def kodi_tv_show_by_date(self):
|
|
||||||
"""
|
|
||||||
Formats a TV show organized by date for Kodi
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="kodi_tv_show_by_date", parent_presets=[KODI_TV_SHOW, TV_SHOW_BY_DATE]
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def jellyfin_tv_show_by_date(self):
|
|
||||||
"""
|
|
||||||
Formats a TV show organized by date for Jellyfin
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="jellyfin_tv_show_by_date", parent_presets=[JELLYFIN_TV_SHOW, TV_SHOW_BY_DATE]
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def plex_tv_show_by_date(self):
|
|
||||||
"""
|
|
||||||
Formats a TV show organized by date for Plex
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="plex_tv_show_by_date", parent_presets=[PLEX_TV_SHOW, TV_SHOW_BY_DATE]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TvShowByDateEpisodeFormattingPresets(PrebuiltPresets):
|
|
||||||
@property
|
|
||||||
def season_by_year__episode_by_month_day(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def season_by_year__episode_by_month_day_reversed(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def season_by_year_month__episode_by_day(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
|
|
||||||
class TvShowCollectionPresets(PrebuiltPresets):
|
|
||||||
"""
|
|
||||||
Docstring for all TV SHOW URL presets
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def kodi_tv_show_collection(self) -> Preset:
|
|
||||||
"""
|
|
||||||
Kodi TV Show with seasons as years, episodes ordered by upload date
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="kodi_tv_show_collection", parent_presets=[KODI_TV_SHOW, TV_SHOW_COLLECTION]
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def jellyfin_tv_show_collection(self) -> Preset:
|
|
||||||
"""
|
|
||||||
Kodi TV Show with seasons as years, episodes ordered by upload date
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="jellyfin_tv_show_collection",
|
|
||||||
parent_presets=[JELLYFIN_TV_SHOW, TV_SHOW_COLLECTION],
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def plex_tv_show_collection(self) -> Preset:
|
|
||||||
"""
|
|
||||||
Kodi TV Show with seasons as years, episodes ordered by upload date
|
|
||||||
"""
|
|
||||||
return self._build_preset(
|
|
||||||
name="plex_tv_show_collection", parent_presets=[PLEX_TV_SHOW, TV_SHOW_COLLECTION]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TvShowCollectionEpisodeFormattingPresets(PrebuiltPresets):
|
|
||||||
@property
|
|
||||||
def season_by_collection__episode_by_year_month_day(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def season_by_collection__episode_by_year_month_day_reversed(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def season_by_collection__episode_by_playlist_index(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def season_by_collection__episode_by_playlist_index_reversed(self) -> Preset:
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
|
|
||||||
class TvShowCollectionSeasonPresets(PrebuiltPresets):
|
|
||||||
@property
|
|
||||||
def collection_season_1(self):
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def collection_season_2(self):
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def collection_season_3(self):
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def collection_season_4(self):
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def collection_season_5(self):
|
|
||||||
"""
|
|
||||||
DOC
|
|
||||||
"""
|
|
||||||
return self._document_preset()
|
|
||||||
|
|
@ -17,4 +17,17 @@ presets:
|
||||||
max_downloads: 20
|
max_downloads: 20
|
||||||
playlistreverse: True
|
playlistreverse: True
|
||||||
break_on_existing: False
|
break_on_existing: False
|
||||||
break_on_reject: True
|
break_on_reject: True
|
||||||
|
|
||||||
|
"Only Recent":
|
||||||
|
# Only fetch videos after today minus date_range
|
||||||
|
date_range:
|
||||||
|
after: "today-{date_range}"
|
||||||
|
|
||||||
|
# Only keep files uploaded after date_range
|
||||||
|
output_options:
|
||||||
|
keep_files_after: "today-{date_range}"
|
||||||
|
|
||||||
|
# Set the default date_range to 2 months
|
||||||
|
overrides:
|
||||||
|
date_range: "2months"
|
||||||
|
|
@ -2,6 +2,14 @@ from ytdl_sub.prebuilt_presets import PrebuiltPresets
|
||||||
|
|
||||||
|
|
||||||
class TvShowByDatePresets(PrebuiltPresets):
|
class TvShowByDatePresets(PrebuiltPresets):
|
||||||
|
preset_names = {
|
||||||
|
"Kodi TV Show by Date",
|
||||||
|
"Jellyfin TV Show by Date",
|
||||||
|
"Plex TV Show by Date",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TvShowByDateOldPresets(PrebuiltPresets):
|
||||||
"""
|
"""
|
||||||
TV Show by Date presets create a TV show from a single URL using upload dates as season/episode
|
TV Show by Date presets create a TV show from a single URL using upload dates as season/episode
|
||||||
numbers.
|
numbers.
|
||||||
|
|
|
||||||
|
|
@ -61,26 +61,45 @@ presets:
|
||||||
- url: "{url20}"
|
- url: "{url20}"
|
||||||
overrides:
|
overrides:
|
||||||
subscription_value: ""
|
subscription_value: ""
|
||||||
|
subscription_value_2: ""
|
||||||
|
subscription_value_3: ""
|
||||||
|
subscription_value_4: ""
|
||||||
|
subscription_value_5: ""
|
||||||
|
subscription_value_6: ""
|
||||||
|
subscription_value_7: ""
|
||||||
|
subscription_value_8: ""
|
||||||
|
subscription_value_9: ""
|
||||||
|
subscription_value_10: ""
|
||||||
|
subscription_value_11: ""
|
||||||
|
subscription_value_12: ""
|
||||||
|
subscription_value_13: ""
|
||||||
|
subscription_value_14: ""
|
||||||
|
subscription_value_15: ""
|
||||||
|
subscription_value_16: ""
|
||||||
|
subscription_value_17: ""
|
||||||
|
subscription_value_18: ""
|
||||||
|
subscription_value_19: ""
|
||||||
|
subscription_value_20: ""
|
||||||
url: "{subscription_value}"
|
url: "{subscription_value}"
|
||||||
url2: ""
|
url2: "{subscription_value_2}"
|
||||||
url3: ""
|
url3: "{subscription_value_3}"
|
||||||
url4: ""
|
url4: "{subscription_value_4}"
|
||||||
url5: ""
|
url5: "{subscription_value_5}"
|
||||||
url6: ""
|
url6: "{subscription_value_6}"
|
||||||
url7: ""
|
url7: "{subscription_value_7}"
|
||||||
url8: ""
|
url8: "{subscription_value_8}"
|
||||||
url9: ""
|
url9: "{subscription_value_9}"
|
||||||
url10: ""
|
url10: "{subscription_value_10}"
|
||||||
url11: ""
|
url11: "{subscription_value_11}"
|
||||||
url12: ""
|
url12: "{subscription_value_12}"
|
||||||
url13: ""
|
url13: "{subscription_value_13}"
|
||||||
url14: ""
|
url14: "{subscription_value_14}"
|
||||||
url15: ""
|
url15: "{subscription_value_15}"
|
||||||
url16: ""
|
url16: "{subscription_value_16}"
|
||||||
url17: ""
|
url17: "{subscription_value_17}"
|
||||||
url18: ""
|
url18: "{subscription_value_18}"
|
||||||
url19: ""
|
url19: "{subscription_value_19}"
|
||||||
url20: ""
|
url20: "{subscription_value_20}"
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,20 @@ presets:
|
||||||
plex_tv_show_by_date:
|
plex_tv_show_by_date:
|
||||||
preset:
|
preset:
|
||||||
- "_plex_tv_show"
|
- "_plex_tv_show"
|
||||||
- "_tv_show_by_date"
|
- "_tv_show_by_date"
|
||||||
|
|
||||||
|
# All-in-one presets that require no config
|
||||||
|
"Kodi TV Show by Date":
|
||||||
|
preset:
|
||||||
|
- "kodi_tv_show_by_date"
|
||||||
|
- "season_by_year__episode_by_month_day"
|
||||||
|
|
||||||
|
"Jellyfin TV Show by Date":
|
||||||
|
preset:
|
||||||
|
- "jellyfin_tv_show_by_date"
|
||||||
|
- "season_by_year__episode_by_month_day"
|
||||||
|
|
||||||
|
"Plex TV Show by Date":
|
||||||
|
preset:
|
||||||
|
- "plex_tv_show_by_date"
|
||||||
|
- "season_by_year__episode_by_month_day"
|
||||||
|
|
@ -169,10 +169,13 @@ def music_video_subscription_path() -> Path:
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def channel_as_tv_show_config(working_directory) -> ConfigFile:
|
def tv_show_config_path() -> str:
|
||||||
return _load_config(
|
return "examples/advanced/tv_show_config.yaml"
|
||||||
config_path=Path("examples/tv_show_config.yaml"), working_directory=working_directory
|
|
||||||
)
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def tv_show_config(working_directory, tv_show_config_path) -> ConfigFile:
|
||||||
|
return _load_config(config_path=Path(tv_show_config_path), working_directory=working_directory)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -180,6 +183,11 @@ def tv_show_subscriptions_path() -> Path:
|
||||||
return Path("examples/tv_show_subscriptions.yaml")
|
return Path("examples/tv_show_subscriptions.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def advanced_tv_show_subscriptions_path() -> Path:
|
||||||
|
return Path("examples/advanced/tv_show_subscriptions.yaml")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def default_config(working_directory) -> ConfigFile:
|
def default_config(working_directory) -> ConfigFile:
|
||||||
return ConfigFile.from_dict({"configuration": {"working_directory": working_directory}})
|
return ConfigFile.from_dict({"configuration": {"working_directory": working_directory}})
|
||||||
|
|
|
||||||
|
|
@ -43,12 +43,12 @@ class TestDateRange:
|
||||||
def test_recent_channel_download(
|
def test_recent_channel_download(
|
||||||
self,
|
self,
|
||||||
recent_preset_dict,
|
recent_preset_dict,
|
||||||
channel_as_tv_show_config,
|
tv_show_config,
|
||||||
output_directory,
|
output_directory,
|
||||||
dry_run,
|
dry_run,
|
||||||
):
|
):
|
||||||
recent_channel_subscription = Subscription.from_dict(
|
recent_channel_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="recent",
|
preset_name="recent",
|
||||||
preset_dict=recent_preset_dict,
|
preset_dict=recent_preset_dict,
|
||||||
)
|
)
|
||||||
|
|
@ -86,7 +86,7 @@ class TestDateRange:
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
def test_recent_channel_download__no_vids_in_range(
|
def test_recent_channel_download__no_vids_in_range(
|
||||||
self,
|
self,
|
||||||
channel_as_tv_show_config,
|
tv_show_config,
|
||||||
recent_preset_dict,
|
recent_preset_dict,
|
||||||
output_directory,
|
output_directory,
|
||||||
dry_run,
|
dry_run,
|
||||||
|
|
@ -94,7 +94,7 @@ class TestDateRange:
|
||||||
recent_preset_dict["date_range"]["after"] = "21000101"
|
recent_preset_dict["date_range"]["after"] = "21000101"
|
||||||
|
|
||||||
recent_channel_no_vids_in_range_subscription = Subscription.from_dict(
|
recent_channel_no_vids_in_range_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="recent",
|
preset_name="recent",
|
||||||
preset_dict=recent_preset_dict,
|
preset_dict=recent_preset_dict,
|
||||||
)
|
)
|
||||||
|
|
@ -118,19 +118,19 @@ class TestDateRange:
|
||||||
@pytest.mark.parametrize("dry_run", [True, False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
def test_rolling_recent_channel_download(
|
def test_rolling_recent_channel_download(
|
||||||
self,
|
self,
|
||||||
channel_as_tv_show_config,
|
tv_show_config,
|
||||||
recent_preset_dict,
|
recent_preset_dict,
|
||||||
rolling_recent_channel_preset_dict,
|
rolling_recent_channel_preset_dict,
|
||||||
output_directory,
|
output_directory,
|
||||||
dry_run,
|
dry_run,
|
||||||
):
|
):
|
||||||
recent_channel_subscription = Subscription.from_dict(
|
recent_channel_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="recent",
|
preset_name="recent",
|
||||||
preset_dict=recent_preset_dict,
|
preset_dict=recent_preset_dict,
|
||||||
)
|
)
|
||||||
rolling_recent_channel_subscription = Subscription.from_dict(
|
rolling_recent_channel_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="recent",
|
preset_name="recent",
|
||||||
preset_dict=rolling_recent_channel_preset_dict,
|
preset_dict=rolling_recent_channel_preset_dict,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,16 @@ class TestChannel:
|
||||||
expected md5 file hashes.
|
expected md5 file hashes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@pytest.mark.parametrize("dry_run", [False])
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
def test_full_channel_download(
|
def test_full_channel_download(
|
||||||
self,
|
self,
|
||||||
channel_as_tv_show_config,
|
tv_show_config,
|
||||||
channel_preset_dict,
|
channel_preset_dict,
|
||||||
output_directory,
|
output_directory,
|
||||||
dry_run,
|
dry_run,
|
||||||
):
|
):
|
||||||
full_channel_subscription = Subscription.from_dict(
|
full_channel_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config, preset_name="pz", preset_dict=channel_preset_dict
|
config=tv_show_config, preset_name="pz", preset_dict=channel_preset_dict
|
||||||
)
|
)
|
||||||
transaction_log = full_channel_subscription.download(dry_run=dry_run)
|
transaction_log = full_channel_subscription.download(dry_run=dry_run)
|
||||||
assert_transaction_log_matches(
|
assert_transaction_log_matches(
|
||||||
|
|
|
||||||
|
|
@ -208,12 +208,12 @@ class TestYoutubeVideo:
|
||||||
|
|
||||||
def test_single_video_nulled_values(
|
def test_single_video_nulled_values(
|
||||||
self,
|
self,
|
||||||
channel_as_tv_show_config,
|
tv_show_config,
|
||||||
single_tv_show_video_nulled_values_preset_dict,
|
single_tv_show_video_nulled_values_preset_dict,
|
||||||
output_directory,
|
output_directory,
|
||||||
):
|
):
|
||||||
single_video_subscription = Subscription.from_dict(
|
single_video_subscription = Subscription.from_dict(
|
||||||
config=channel_as_tv_show_config,
|
config=tv_show_config,
|
||||||
preset_name="tv_video_nulled_values",
|
preset_name="tv_video_nulled_values",
|
||||||
preset_dict=single_tv_show_video_nulled_values_preset_dict,
|
preset_dict=single_tv_show_video_nulled_values_preset_dict,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e102901 - Jesse's Minecraft Server | Teaser Trailer.nfo": "ee5bee94667ed4f1d47af08384418d3c",
|
"Project ⧸ Zombie/Season 2018/s2018.e102901 - Jesse's Minecraft Server | Teaser Trailer.nfo": "ee5bee94667ed4f1d47af08384418d3c",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "77aca1c8f8ef32c282a8a871d80bec10",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "3725eaeaae4a400599070a56a1dccf9d",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "c2f06f9eb5b42c777808a35587e5941e",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "c2f06f9eb5b42c777808a35587e5941e",
|
||||||
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"Project ⧸ Zombie/.ytdl-sub-recent-download-archive.json": "46170edaf7cc51e5f59ef5d4c0476200",
|
"Project ⧸ Zombie/.ytdl-sub-recent-download-archive.json": "46170edaf7cc51e5f59ef5d4c0476200",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "77aca1c8f8ef32c282a8a871d80bec10",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "3725eaeaae4a400599070a56a1dccf9d",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "c2f06f9eb5b42c777808a35587e5941e",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "c2f06f9eb5b42c777808a35587e5941e",
|
||||||
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg": "28d852ede73b879b9ebf9a061cfc7d46",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt": "3d2c4e7f65d2ca5e96da38ce7eecfc4e",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json": "INFO_JSON",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "77aca1c8f8ef32c282a8a871d80bec10",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4": "3725eaeaae4a400599070a56a1dccf9d",
|
||||||
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "a9b7ac3cacbda7fed9d12b0519945292",
|
"Project ⧸ Zombie/Season 2018/s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.nfo": "a9b7ac3cacbda7fed9d12b0519945292",
|
||||||
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
"Project ⧸ Zombie/fanart.jpg": "129c6639b47299bc48062f0365e670ee",
|
||||||
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
"Project ⧸ Zombie/poster.jpg": "5de28eea5a921a041452ab3ce1041f73",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ Files created:
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id-thumb.jpg
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4
|
||||||
|
Embedded subtitles with lang(s) en
|
||||||
Video Tags:
|
Video Tags:
|
||||||
contentRating: TV-14
|
contentRating: TV-14
|
||||||
date: 2018-11-02
|
date: 2018-11-02
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,7 @@ Files created:
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.en.srt
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.info.json
|
||||||
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4
|
s2018.e110201 - Jesse's Minecraft Server | IP mc.jesse.id.mp4
|
||||||
|
Embedded subtitles with lang(s) en
|
||||||
Video Tags:
|
Video Tags:
|
||||||
contentRating: TV-14
|
contentRating: TV-14
|
||||||
date: 2018-11-02
|
date: 2018-11-02
|
||||||
|
|
|
||||||
|
|
@ -489,16 +489,14 @@ def test_subscription_file_invalid_form(config_file: ConfigFile):
|
||||||
_ = Subscription.from_file_path(config=config_file, subscription_path="mocked")
|
_ = Subscription.from_file_path(config=config_file, subscription_path="mocked")
|
||||||
|
|
||||||
|
|
||||||
def test_tv_show_subscriptions(
|
def test_tv_show_subscriptions(config_file: ConfigFile, tv_show_subscriptions_path: Path):
|
||||||
channel_as_tv_show_config: ConfigFile, tv_show_subscriptions_path: Path
|
|
||||||
):
|
|
||||||
subs = Subscription.from_file_path(
|
subs = Subscription.from_file_path(
|
||||||
config=channel_as_tv_show_config, subscription_path=tv_show_subscriptions_path
|
config=config_file, subscription_path=tv_show_subscriptions_path
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(subs) == 5
|
assert len(subs) == 7
|
||||||
assert subs[2].name == "Jake Trains"
|
assert subs[3].name == "Jake Trains"
|
||||||
jake_train_overrides = subs[2].overrides.dict_with_format_strings
|
jake_train_overrides = subs[3].overrides.dict_with_format_strings
|
||||||
|
|
||||||
assert jake_train_overrides["subscription_name"] == "Jake Trains"
|
assert jake_train_overrides["subscription_name"] == "Jake Trains"
|
||||||
assert jake_train_overrides["subscription_name_sanitized"] == "Jake Trains"
|
assert jake_train_overrides["subscription_name_sanitized"] == "Jake Trains"
|
||||||
|
|
@ -507,6 +505,41 @@ def test_tv_show_subscriptions(
|
||||||
assert jake_train_overrides["subscription_indent_2"] == "TV-Y"
|
assert jake_train_overrides["subscription_indent_2"] == "TV-Y"
|
||||||
|
|
||||||
|
|
||||||
|
def test_advanced_tv_show_subscriptions(
|
||||||
|
tv_show_config: ConfigFile, advanced_tv_show_subscriptions_path: Path
|
||||||
|
):
|
||||||
|
subs = Subscription.from_file_path(
|
||||||
|
config=tv_show_config, subscription_path=advanced_tv_show_subscriptions_path
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(subs) == 9
|
||||||
|
assert subs[3].name == "Jake Trains"
|
||||||
|
jake_train_overrides = subs[3].overrides.dict_with_format_strings
|
||||||
|
|
||||||
|
assert jake_train_overrides["subscription_name"] == "Jake Trains"
|
||||||
|
assert jake_train_overrides["subscription_name_sanitized"] == "Jake Trains"
|
||||||
|
assert jake_train_overrides["subscription_value"] == "https://www.youtube.com/@JakeTrains"
|
||||||
|
assert jake_train_overrides["subscription_indent_1"] == "Kids"
|
||||||
|
assert jake_train_overrides["subscription_indent_2"] == "TV-Y"
|
||||||
|
|
||||||
|
assert subs[5].name == "Gardening with Ciscoe"
|
||||||
|
overrides = subs[5].overrides
|
||||||
|
|
||||||
|
assert overrides.apply_formatter(overrides.dict["subscription_name"]) == "Gardening with Ciscoe"
|
||||||
|
assert (
|
||||||
|
overrides.apply_formatter(overrides.dict["subscription_name_sanitized"])
|
||||||
|
== "Gardening with Ciscoe"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
overrides.apply_formatter(overrides.dict["url"])
|
||||||
|
== "https://www.youtube.com/@gardeningwithciscoe4430"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
overrides.apply_formatter(overrides.dict["url2"])
|
||||||
|
== "https://www.youtube.com/playlist?list=PLi8V8UemxeG6lo5if5H5g5EbsteELcb0_"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path):
|
def test_music_subscriptions(default_config: ConfigFile, music_subscriptions_path: Path):
|
||||||
subs = Subscription.from_file_path(
|
subs = Subscription.from_file_path(
|
||||||
config=default_config, subscription_path=music_subscriptions_path
|
config=default_config, subscription_path=music_subscriptions_path
|
||||||
|
|
|
||||||
|
|
@ -96,17 +96,17 @@ def test_main_permission_error(capsys, mock_sys_exit, expected_uncaught_error_me
|
||||||
assert mock_error.call_args.args[1] == "test"
|
assert mock_error.call_args.args[1] == "test"
|
||||||
|
|
||||||
|
|
||||||
def test_args_after_sub_work(mock_sys_exit):
|
def test_args_after_sub_work(mock_sys_exit, tv_show_config_path):
|
||||||
with mock_sys_exit(expected_exit_code=0), patch.object(
|
with mock_sys_exit(expected_exit_code=0), patch.object(
|
||||||
sys,
|
sys,
|
||||||
"argv",
|
"argv",
|
||||||
["ytdl-sub", "-c", "examples/tv_show_config.yaml", "sub", "--log-level", "verbose"],
|
["ytdl-sub", "-c", tv_show_config_path, "sub", "--log-level", "verbose"],
|
||||||
), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub:
|
), patch("ytdl_sub.cli.entrypoint._download_subscriptions_from_yaml_files") as mock_sub:
|
||||||
main()
|
main()
|
||||||
|
|
||||||
assert mock_sub.call_count == 1
|
assert mock_sub.call_count == 1
|
||||||
assert mock_sub.call_args.kwargs["subscription_paths"] == ["subscriptions.yaml"]
|
assert mock_sub.call_args.kwargs["subscription_paths"] == ["subscriptions.yaml"]
|
||||||
assert mock_sub.call_args.kwargs["config"]._name == "examples/tv_show_config.yaml"
|
assert mock_sub.call_args.kwargs["config"]._name == tv_show_config_path
|
||||||
assert Logger._LOGGER_LEVEL == LoggerLevels.VERBOSE
|
assert Logger._LOGGER_LEVEL == LoggerLevels.VERBOSE
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -151,11 +151,11 @@ def test_uses_default_config_if_present(mock_sys_exit):
|
||||||
FileHandler.delete(DEFAULT_CONFIG_FILE_NAME)
|
FileHandler.delete(DEFAULT_CONFIG_FILE_NAME)
|
||||||
|
|
||||||
|
|
||||||
def test_no_positional_arg_command(mock_sys_exit):
|
def test_no_positional_arg_command(mock_sys_exit, tv_show_config_path):
|
||||||
with mock_sys_exit(expected_exit_code=1), patch.object(
|
with mock_sys_exit(expected_exit_code=1), patch.object(
|
||||||
sys,
|
sys,
|
||||||
"argv",
|
"argv",
|
||||||
["ytdl-sub", "-c", "examples/tv_show_config.yaml", "--log-level", "verbose"],
|
["ytdl-sub", "-c", tv_show_config_path, "--log-level", "verbose"],
|
||||||
), patch.object(logging.Logger, "error") as mock_error:
|
), patch.object(logging.Logger, "error") as mock_error:
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import copy
|
import copy
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from expected_download import assert_expected_downloads
|
from expected_download import assert_expected_downloads
|
||||||
|
|
@ -9,7 +11,7 @@ from expected_transaction_log import assert_transaction_log_matches
|
||||||
from ytdl_sub.prebuilt_presets.music import MusicPresets
|
from ytdl_sub.prebuilt_presets.music import MusicPresets
|
||||||
from ytdl_sub.prebuilt_presets.music_videos import MusicVideoPresets
|
from ytdl_sub.prebuilt_presets.music_videos import MusicVideoPresets
|
||||||
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDateEpisodeFormattingPresets
|
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDateEpisodeFormattingPresets
|
||||||
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDatePresets
|
from ytdl_sub.prebuilt_presets.tv_show import TvShowByDateOldPresets
|
||||||
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionEpisodeFormattingPresets
|
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionEpisodeFormattingPresets
|
||||||
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets
|
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets
|
||||||
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionSeasonPresets
|
from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionSeasonPresets
|
||||||
|
|
@ -17,18 +19,56 @@ from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("media_player_preset", TvShowByDatePresets.preset_names)
|
def _tv_show_by_date_combos() -> List[Tuple[None, str, str]]:
|
||||||
|
combos: List[Tuple[None, str, str]] = []
|
||||||
|
|
||||||
|
for media_player_preset in TvShowByDateOldPresets.preset_names:
|
||||||
|
for tv_show_structure_preset in TvShowByDateEpisodeFormattingPresets.preset_names:
|
||||||
|
combos.append((None, media_player_preset, tv_show_structure_preset))
|
||||||
|
|
||||||
|
return combos
|
||||||
|
|
||||||
|
|
||||||
|
def _tv_show_by_date_parent_presets(
|
||||||
|
all_in_one_preset: Optional[str],
|
||||||
|
media_player_preset: str,
|
||||||
|
tv_show_structured_preset: str,
|
||||||
|
) -> List[str]:
|
||||||
|
"""Hack to include the all_in_one presets in this test suite"""
|
||||||
|
if all_in_one_preset is not None:
|
||||||
|
return [all_in_one_preset]
|
||||||
|
return [
|
||||||
|
media_player_preset,
|
||||||
|
tv_show_structured_preset,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"tv_show_structure_preset", TvShowByDateEpisodeFormattingPresets.preset_names
|
"all_in_one_preset, media_player_preset, tv_show_structure_preset",
|
||||||
|
[
|
||||||
|
# Test that the all-in-ones are equivalent to using the two underlying ones
|
||||||
|
("Kodi TV Show by Date", "kodi_tv_show_by_date", "season_by_year__episode_by_month_day"),
|
||||||
|
(
|
||||||
|
"Jellyfin TV Show by Date",
|
||||||
|
"jellyfin_tv_show_by_date",
|
||||||
|
"season_by_year__episode_by_month_day",
|
||||||
|
),
|
||||||
|
("Plex TV Show by Date", "plex_tv_show_by_date", "season_by_year__episode_by_month_day"),
|
||||||
|
*_tv_show_by_date_combos(),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
class TestPrebuiltTVShowPresets:
|
class TestPrebuiltTVShowPresets:
|
||||||
def test_compilation(
|
def test_compilation(
|
||||||
self,
|
self,
|
||||||
config,
|
config,
|
||||||
|
all_in_one_preset: Optional[str],
|
||||||
media_player_preset: str,
|
media_player_preset: str,
|
||||||
tv_show_structure_preset: str,
|
tv_show_structure_preset: str,
|
||||||
):
|
):
|
||||||
parent_presets: List[str] = [media_player_preset, tv_show_structure_preset]
|
parent_presets = _tv_show_by_date_parent_presets(
|
||||||
|
all_in_one_preset, media_player_preset, tv_show_structure_preset
|
||||||
|
)
|
||||||
|
|
||||||
_ = Subscription.from_dict(
|
_ = Subscription.from_dict(
|
||||||
config=config,
|
config=config,
|
||||||
preset_name="preset_test",
|
preset_name="preset_test",
|
||||||
|
|
@ -45,10 +85,14 @@ class TestPrebuiltTVShowPresets:
|
||||||
def test_compilation_many_urls(
|
def test_compilation_many_urls(
|
||||||
self,
|
self,
|
||||||
config,
|
config,
|
||||||
|
all_in_one_preset: Optional[str],
|
||||||
media_player_preset: str,
|
media_player_preset: str,
|
||||||
tv_show_structure_preset: str,
|
tv_show_structure_preset: str,
|
||||||
):
|
):
|
||||||
parent_presets: List[str] = [media_player_preset, tv_show_structure_preset]
|
parent_presets = _tv_show_by_date_parent_presets(
|
||||||
|
all_in_one_preset, media_player_preset, tv_show_structure_preset
|
||||||
|
)
|
||||||
|
|
||||||
_ = Subscription.from_dict(
|
_ = Subscription.from_dict(
|
||||||
config=config,
|
config=config,
|
||||||
preset_name="preset_test",
|
preset_name="preset_test",
|
||||||
|
|
@ -66,10 +110,14 @@ class TestPrebuiltTVShowPresets:
|
||||||
def test_compilation_errors_missing_one(
|
def test_compilation_errors_missing_one(
|
||||||
self,
|
self,
|
||||||
config,
|
config,
|
||||||
|
all_in_one_preset: Optional[str],
|
||||||
media_player_preset: str,
|
media_player_preset: str,
|
||||||
tv_show_structure_preset: str,
|
tv_show_structure_preset: str,
|
||||||
):
|
):
|
||||||
parent_presets: List[str] = [media_player_preset, tv_show_structure_preset]
|
parent_presets = _tv_show_by_date_parent_presets(
|
||||||
|
all_in_one_preset, media_player_preset, tv_show_structure_preset
|
||||||
|
)
|
||||||
|
|
||||||
for parent_preset in parent_presets:
|
for parent_preset in parent_presets:
|
||||||
parent_presets_missing_one = copy.deepcopy(parent_presets).remove(parent_preset)
|
parent_presets_missing_one = copy.deepcopy(parent_presets).remove(parent_preset)
|
||||||
|
|
||||||
|
|
@ -95,6 +143,7 @@ class TestPrebuiltTVShowPresets:
|
||||||
subscription_name,
|
subscription_name,
|
||||||
output_directory,
|
output_directory,
|
||||||
mock_download_collection_entries,
|
mock_download_collection_entries,
|
||||||
|
all_in_one_preset: Optional[str],
|
||||||
media_player_preset: str,
|
media_player_preset: str,
|
||||||
tv_show_structure_preset: str,
|
tv_show_structure_preset: str,
|
||||||
is_youtube_channel: bool,
|
is_youtube_channel: bool,
|
||||||
|
|
@ -106,7 +155,9 @@ class TestPrebuiltTVShowPresets:
|
||||||
int(is_youtube_channel),
|
int(is_youtube_channel),
|
||||||
"_many_urls" if is_many_urls else "",
|
"_many_urls" if is_many_urls else "",
|
||||||
)
|
)
|
||||||
parent_presets = [media_player_preset, tv_show_structure_preset]
|
parent_presets = _tv_show_by_date_parent_presets(
|
||||||
|
all_in_one_preset, media_player_preset, tv_show_structure_preset
|
||||||
|
)
|
||||||
|
|
||||||
preset_dict = {
|
preset_dict = {
|
||||||
"preset": parent_presets,
|
"preset": parent_presets,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue