From 764678367f72fac82b2ee9e5a4d77a3c62d55985 Mon Sep 17 00:00:00 2001 From: jbannon Date: Sat, 9 Apr 2022 06:31:13 +0000 Subject: [PATCH] add support for single youtube video --- config.yaml | 1 - subscriptions.yaml | 56 +++++++++++-------- .../downloaders/youtube_downloader.py | 9 +++ ytdl_subscribe/subscriptions/youtube.py | 12 ++++ .../download_strategy_validators.py | 8 ++- .../source_options/youtube_validators.py | 8 +++ .../config/subscription_validator.py | 12 +++- 7 files changed, 81 insertions(+), 25 deletions(-) diff --git a/config.yaml b/config.yaml index d5a09d45..22845859 100644 --- a/config.yaml +++ b/config.yaml @@ -27,7 +27,6 @@ presets: music_videos: youtube: - download_strategy: "playlist" ytdl_options: format: 'best' ignoreerrors: True diff --git a/subscriptions.yaml b/subscriptions.yaml index 0e2c9c7d..57db03dc 100644 --- a/subscriptions.yaml +++ b/subscriptions.yaml @@ -18,30 +18,42 @@ # artist: "DeLorra" # genre: "Synthwave / Electronic" # -bl00dwave: - preset: "soundcloud_with_id3_tags" - soundcloud: - username: bl00dwave - output_options: - output_directory: "/tmp/bl00dwave" - overrides: - artist: "bl00dwave" - genre: "Synthwave / Electronic" +#bl00dwave: +# preset: "soundcloud_with_id3_tags" +# soundcloud: +# username: bl00dwave +# output_options: +# output_directory: "/tmp/bl00dwave" +# overrides: +# artist: "bl00dwave" +# genre: "Synthwave / Electronic" +# +#tom_petty: +# preset: "music_videos" +# youtube: +# download_strategy: "playlist" +# playlist_id: PLoopXDarluPBnuxs4PTC55Sc_2ShAXC0i +# output_options: +# output_directory: "/tmp/Tom Petty" +# overrides: +# artist: "Tom Petty and the Heartbreakers" +# +#rammstein: +# preset: "music_videos" +# youtube: +# download_strategy: "playlist" +# playlist_id: "PLVTLbc6i-h_iuhdwUfuPDLFLXG2QQnz-x" +# output_options: +# output_directory: "/tmp/Rammstein" +# overrides: +# artist: "Rammstein" -tom_petty: +gogo_video_test: preset: "music_videos" youtube: - playlist_id: PLoopXDarluPBnuxs4PTC55Sc_2ShAXC0i + download_strategy: "video" + video_id: "2RHTiXvELNg" output_options: - output_directory: "/tmp/Tom Petty" + output_directory: "/tmp/gogos" overrides: - artist: "Tom Petty and the Heartbreakers" - -rammstein: - preset: "music_videos" - youtube: - playlist_id: "PLVTLbc6i-h_iuhdwUfuPDLFLXG2QQnz-x" - output_options: - output_directory: "/tmp/Rammstein" - overrides: - artist: "Rammstein" + artist: "The Gogos" \ No newline at end of file diff --git a/ytdl_subscribe/downloaders/youtube_downloader.py b/ytdl_subscribe/downloaders/youtube_downloader.py index 32444c59..758b8596 100644 --- a/ytdl_subscribe/downloaders/youtube_downloader.py +++ b/ytdl_subscribe/downloaders/youtube_downloader.py @@ -18,6 +18,11 @@ class YoutubeDownloader(Downloader): """Returns full playlist url""" return f"https://youtube.com/playlist?list={playlist_id}" + @classmethod + def video_url(cls, video_id: str) -> str: + """Returns full video url""" + return f"https://youtube.com/watch?v={video_id}" + def _download_with_metadata(self, url: str) -> None: """ Do not get entries from the extract info, let it write to the info.json file and load @@ -30,6 +35,10 @@ class YoutubeDownloader(Downloader): } _ = self.extract_info(ytdl_options_overrides=ytdl_metadata_override, url=url) + def download_video(self, video_id: str) -> YoutubeVideo: + entry = self.extract_info(url=self.video_url(video_id)) + return YoutubeVideo(**entry) + def download_playlist(self, playlist_id: str) -> List[YoutubeVideo]: """ Downloads all videos in a playlist diff --git a/ytdl_subscribe/subscriptions/youtube.py b/ytdl_subscribe/subscriptions/youtube.py index 9e5cea20..80521c98 100644 --- a/ytdl_subscribe/subscriptions/youtube.py +++ b/ytdl_subscribe/subscriptions/youtube.py @@ -3,6 +3,9 @@ from ytdl_subscribe.subscriptions.subscription import Subscription from ytdl_subscribe.validators.config.source_options.youtube_validators import ( YoutubePlaylistSourceValidator, ) +from ytdl_subscribe.validators.config.source_options.youtube_validators import ( + YoutubeVideoSourceValidator, +) class YoutubePlaylistSubscription(Subscription[YoutubePlaylistSourceValidator]): @@ -13,3 +16,12 @@ class YoutubePlaylistSubscription(Subscription[YoutubePlaylistSourceValidator]): for entry in entries: self.post_process_entry(entry) + + +class YoutubeVideoSubscription(Subscription[YoutubeVideoSourceValidator]): + def extract_info(self): + entry = self.get_downloader(YoutubeDownloader).download_video( + video_id=self.source_options.video_id.value + ) + + self.post_process_entry(entry) diff --git a/ytdl_subscribe/validators/config/source_options/download_strategy_validators.py b/ytdl_subscribe/validators/config/source_options/download_strategy_validators.py index b7b69b74..a1d0289b 100644 --- a/ytdl_subscribe/validators/config/source_options/download_strategy_validators.py +++ b/ytdl_subscribe/validators/config/source_options/download_strategy_validators.py @@ -13,6 +13,9 @@ from ytdl_subscribe.validators.config.source_options.source_validators import So from ytdl_subscribe.validators.config.source_options.youtube_validators import ( YoutubePlaylistSourceValidator, ) +from ytdl_subscribe.validators.config.source_options.youtube_validators import ( + YoutubeVideoSourceValidator, +) class DownloadStrategyValidator(StrictDictValidator, ABC): @@ -61,4 +64,7 @@ class SoundcloudDownloadStrategyValidator(DownloadStrategyValidator): class YoutubeDownloadStrategyValidator(DownloadStrategyValidator): - _download_strategy_to_source_mapping = {"playlist": YoutubePlaylistSourceValidator} + _download_strategy_to_source_mapping = { + "playlist": YoutubePlaylistSourceValidator, + "video": YoutubeVideoSourceValidator, + } diff --git a/ytdl_subscribe/validators/config/source_options/youtube_validators.py b/ytdl_subscribe/validators/config/source_options/youtube_validators.py index 75d163bf..9eda02ab 100644 --- a/ytdl_subscribe/validators/config/source_options/youtube_validators.py +++ b/ytdl_subscribe/validators/config/source_options/youtube_validators.py @@ -8,3 +8,11 @@ class YoutubePlaylistSourceValidator(YoutubeSourceValidator): def __init__(self, name, value): super().__init__(name, value) self.playlist_id = self._validate_key("playlist_id", StringValidator) + + +class YoutubeVideoSourceValidator(YoutubeSourceValidator): + _required_keys = {"video_id"} + + def __init__(self, name, value): + super().__init__(name, value) + self.video_id = self._validate_key("video_id", StringValidator) diff --git a/ytdl_subscribe/validators/config/subscription_validator.py b/ytdl_subscribe/validators/config/subscription_validator.py index cec19449..862f8106 100644 --- a/ytdl_subscribe/validators/config/subscription_validator.py +++ b/ytdl_subscribe/validators/config/subscription_validator.py @@ -1,6 +1,8 @@ import copy from typing import Any from typing import List +from typing import Optional +from typing import Type import yaml from mergedeep import mergedeep @@ -8,6 +10,7 @@ from mergedeep import mergedeep from ytdl_subscribe.subscriptions.soundcloud import SoundcloudAlbumsAndSinglesSubscription from ytdl_subscribe.subscriptions.subscription import Subscription from ytdl_subscribe.subscriptions.youtube import YoutubePlaylistSubscription +from ytdl_subscribe.subscriptions.youtube import YoutubeVideoSubscription from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator from ytdl_subscribe.validators.base.validators import StringValidator from ytdl_subscribe.validators.config.config_file_validator import ConfigFileValidator @@ -21,6 +24,9 @@ from ytdl_subscribe.validators.config.source_options.soundcloud_validators impor from ytdl_subscribe.validators.config.source_options.youtube_validators import ( YoutubePlaylistSourceValidator, ) +from ytdl_subscribe.validators.config.source_options.youtube_validators import ( + YoutubeVideoSourceValidator, +) class SubscriptionValidator(StrictDictValidator): @@ -65,11 +71,15 @@ class SubscriptionValidator(StrictDictValidator): ) def to_subscription(self) -> Subscription: + subscription_class: Optional[Type[Subscription]] = None if isinstance(self.preset.subscription_source, SoundcloudAlbumsAndSinglesSourceValidator): subscription_class = SoundcloudAlbumsAndSinglesSubscription elif isinstance(self.preset.subscription_source, YoutubePlaylistSourceValidator): subscription_class = YoutubePlaylistSubscription - else: + elif isinstance(self.preset.subscription_source, YoutubeVideoSourceValidator): + subscription_class = YoutubeVideoSubscription + + if subscription_class is None: raise ValueError("subscription source class not found") return subscription_class(