channel banner + avatar working
This commit is contained in:
parent
4ef6898e05
commit
83350c6f10
5 changed files with 99 additions and 6 deletions
|
|
@ -47,6 +47,9 @@ presets:
|
||||||
|
|
||||||
yt_channel:
|
yt_channel:
|
||||||
youtube:
|
youtube:
|
||||||
|
download_strategy: "channel"
|
||||||
|
channel_avatar_path: "poster.jpg"
|
||||||
|
channel_banner_path: "fanart.jpg"
|
||||||
ytdl_options:
|
ytdl_options:
|
||||||
format: 'best'
|
format: 'best'
|
||||||
ignoreerrors: True
|
ignoreerrors: True
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ recent_channel_test:
|
||||||
ytdl_options:
|
ytdl_options:
|
||||||
break_on_reject: True
|
break_on_reject: True
|
||||||
youtube:
|
youtube:
|
||||||
download_strategy: "channel"
|
|
||||||
channel_id: "UCsvn_Po0SmunchJYOWpOxMg"
|
channel_id: "UCsvn_Po0SmunchJYOWpOxMg"
|
||||||
after: today-13days
|
after: today-13days
|
||||||
output_options:
|
output_options:
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from typing import TypeVar
|
||||||
|
|
||||||
import yt_dlp as ytdl
|
import yt_dlp as ytdl
|
||||||
|
|
||||||
|
from ytdl_subscribe.config.preset_options import Overrides
|
||||||
from ytdl_subscribe.entries.entry import Entry
|
from ytdl_subscribe.entries.entry import Entry
|
||||||
from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
from ytdl_subscribe.validators.strict_dict_validator import StrictDictValidator
|
||||||
|
|
||||||
|
|
@ -106,4 +107,19 @@ class Downloader(Generic[DownloaderOptionsT, DownloaderEntryT], ABC):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def download(self) -> List[DownloaderEntryT]:
|
def download(self) -> List[DownloaderEntryT]:
|
||||||
"""The function to perform the download"""
|
"""The function to perform the download of all media entries"""
|
||||||
|
|
||||||
|
def post_download(self, overrides: Overrides, output_directory: str):
|
||||||
|
"""
|
||||||
|
After all media entries have been downloaded, post processed, and moved to the output
|
||||||
|
directory, run this function. This lets the downloader add any extra files directly to the
|
||||||
|
output directory, for things like YT channel image, banner.
|
||||||
|
|
||||||
|
This ideally should not perform any extra downloads, but rather, use the content already
|
||||||
|
downloaded in the working directory and use it in the output directory.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
output_directory
|
||||||
|
Output directory to potentially store extra files downloaded
|
||||||
|
"""
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,18 @@ from typing import Generic
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
from PIL.Image import Image
|
||||||
|
from PIL.Image import open as pil_open
|
||||||
from yt_dlp.utils import RejectedVideoReached
|
from yt_dlp.utils import RejectedVideoReached
|
||||||
|
|
||||||
|
from ytdl_subscribe.config.preset_options import Overrides
|
||||||
from ytdl_subscribe.downloaders.downloader import Downloader
|
from ytdl_subscribe.downloaders.downloader import Downloader
|
||||||
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
||||||
from ytdl_subscribe.entries.youtube import YoutubeVideo
|
from ytdl_subscribe.entries.youtube import YoutubeVideo
|
||||||
from ytdl_subscribe.validators.date_range_validator import DateRangeValidator
|
from ytdl_subscribe.validators.date_range_validator import DateRangeValidator
|
||||||
|
from ytdl_subscribe.validators.string_formatter_validators import OverridesStringFormatterValidator
|
||||||
from ytdl_subscribe.validators.validators import StringValidator
|
from ytdl_subscribe.validators.validators import StringValidator
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
@ -143,12 +148,18 @@ class YoutubePlaylistDownloader(YoutubeDownloader[YoutubePlaylistDownloaderOptio
|
||||||
|
|
||||||
class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DateRangeValidator):
|
class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DateRangeValidator):
|
||||||
_required_keys = {"channel_id"}
|
_required_keys = {"channel_id"}
|
||||||
_optional_keys = {"before", "after"}
|
_optional_keys = {"before", "after", "channel_avatar_path", "channel_banner_path"}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
YoutubeDownloaderOptions.__init__(self, name, value)
|
YoutubeDownloaderOptions.__init__(self, name, value)
|
||||||
DateRangeValidator.__init__(self, name, value)
|
DateRangeValidator.__init__(self, name, value)
|
||||||
self.channel_id = self._validate_key("channel_id", StringValidator)
|
self.channel_id = self._validate_key("channel_id", StringValidator)
|
||||||
|
self.channel_avatar_path = self._validate_key_if_present(
|
||||||
|
"channel_avatar_path", OverridesStringFormatterValidator
|
||||||
|
)
|
||||||
|
self.channel_banner_path = self._validate_key_if_present(
|
||||||
|
"channel_banner_path", OverridesStringFormatterValidator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions]):
|
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions]):
|
||||||
|
|
@ -159,12 +170,20 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
|
||||||
"""Returns full channel url"""
|
"""Returns full channel url"""
|
||||||
return f"https://youtube.com/channel/{channel_id}"
|
return f"https://youtube.com/channel/{channel_id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def channel_id(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Channel ID
|
||||||
|
"""
|
||||||
|
return self.download_options.channel_id.value
|
||||||
|
|
||||||
def download(self) -> List[YoutubeVideo]:
|
def download(self) -> List[YoutubeVideo]:
|
||||||
"""
|
"""
|
||||||
Downloads all videos from a channel
|
Downloads all videos from a channel
|
||||||
"""
|
"""
|
||||||
channel_id = self.download_options.channel_id.value
|
channel_url = self.channel_url(channel_id=self.channel_id)
|
||||||
channel_url = self.channel_url(channel_id=channel_id)
|
|
||||||
|
|
||||||
# If a date range is specified when download a YT channel, add it into the ytdl options
|
# If a date range is specified when download a YT channel, add it into the ytdl options
|
||||||
ytdl_options_overrides = {}
|
ytdl_options_overrides = {}
|
||||||
|
|
@ -174,6 +193,58 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
|
||||||
|
|
||||||
return self._download_using_metadata(
|
return self._download_using_metadata(
|
||||||
url=channel_url,
|
url=channel_url,
|
||||||
ignore_prefix=channel_id,
|
ignore_prefix=self.channel_id,
|
||||||
ytdl_options_overrides=ytdl_options_overrides,
|
ytdl_options_overrides=ytdl_options_overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __download_thumbnail(
|
||||||
|
self,
|
||||||
|
channel_dict: dict,
|
||||||
|
thumbnail_id: str,
|
||||||
|
output_thumbnail_path: str,
|
||||||
|
):
|
||||||
|
thumbnail_url = None
|
||||||
|
for thumbnail in channel_dict.get("thumbnails", []):
|
||||||
|
if thumbnail["id"] == thumbnail_id:
|
||||||
|
thumbnail_url = thumbnail["url"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if not thumbnail_url:
|
||||||
|
# TODO: add logger with warn here
|
||||||
|
return
|
||||||
|
|
||||||
|
with urlopen(thumbnail_url) as file:
|
||||||
|
image: Image = pil_open(file).convert("RGB")
|
||||||
|
|
||||||
|
image.save(fp=output_thumbnail_path, format="jpeg")
|
||||||
|
|
||||||
|
def post_download(self, overrides: Overrides, output_directory: str):
|
||||||
|
"""
|
||||||
|
Downloads and moves channel avatar and banner images to the output directory.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
overrides
|
||||||
|
Overrides that can contain variables in the avatar or banner file path
|
||||||
|
output_directory
|
||||||
|
Output directory path
|
||||||
|
"""
|
||||||
|
channel_json_file_path = Path(self.working_directory) / f"{self.channel_id}.info.json"
|
||||||
|
with open(channel_json_file_path) as channel_json:
|
||||||
|
channel_entry = json.load(channel_json)
|
||||||
|
|
||||||
|
if self.download_options.channel_avatar_path:
|
||||||
|
thumbnail_name = overrides.apply_formatter(self.download_options.channel_avatar_path)
|
||||||
|
self.__download_thumbnail(
|
||||||
|
channel_dict=channel_entry,
|
||||||
|
thumbnail_id="avatar_uncropped",
|
||||||
|
output_thumbnail_path=str(Path(output_directory) / thumbnail_name),
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.download_options.channel_banner_path:
|
||||||
|
thumbnail_name = overrides.apply_formatter(self.download_options.channel_banner_path)
|
||||||
|
self.__download_thumbnail(
|
||||||
|
channel_dict=channel_entry,
|
||||||
|
thumbnail_id="banner_uncropped",
|
||||||
|
output_thumbnail_path=str(Path(output_directory) / thumbnail_name),
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -185,3 +185,7 @@ class Subscription:
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
self._copy_entry_files_to_output_directory(entry=entry)
|
self._copy_entry_files_to_output_directory(entry=entry)
|
||||||
|
|
||||||
|
downloader.post_download(
|
||||||
|
overrides=self.overrides, output_directory=self.output_directory
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue