116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
from typing import Dict
|
|
from typing import List
|
|
from typing import Optional
|
|
from typing import Tuple
|
|
|
|
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloader
|
|
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloaderOptions
|
|
from ytdl_sub.entries.youtube import YoutubeVideo
|
|
from ytdl_sub.utils.chapters import Chapters
|
|
from ytdl_sub.utils.ffmpeg import add_ffmpeg_metadata
|
|
from ytdl_sub.utils.file_handler import FileMetadata
|
|
from ytdl_sub.validators.url_validator import YoutubeVideoUrlValidator
|
|
from ytdl_sub.validators.validators import StringValidator
|
|
|
|
|
|
class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
|
"""
|
|
Downloads a single youtube video. This download strategy is intended for CLI usage performing
|
|
a one-time download of a video, not a subscription.
|
|
|
|
Usage:
|
|
|
|
.. code-block:: yaml
|
|
|
|
presets:
|
|
example_preset:
|
|
youtube:
|
|
# required
|
|
download_strategy: "video"
|
|
video_url: "youtube.com/watch?v=VMAPTo7RVDo"
|
|
# optional
|
|
chapter_timestamps: path/to/timestamps.txt
|
|
|
|
CLI usage:
|
|
|
|
.. code-block:: bash
|
|
|
|
ytdl-sub dl --preset "example_preset" --youtube.video_url "youtube.com/watch?v=VMAPTo7RVDo"
|
|
"""
|
|
|
|
_required_keys = {"video_url"}
|
|
_optional_keys = {"chapter_timestamps"}
|
|
|
|
def __init__(self, name, value):
|
|
super().__init__(name, value)
|
|
self._video_url = self._validate_key("video_url", YoutubeVideoUrlValidator).video_url
|
|
self._chapter_timestamps = self._validate_key_if_present(
|
|
"chapter_timestamps", StringValidator
|
|
)
|
|
|
|
@property
|
|
def video_url(self) -> str:
|
|
"""
|
|
Required. The url of the video, i.e. ``youtube.com/watch?v=VMAPTo7RVDo``.
|
|
"""
|
|
return self._video_url
|
|
|
|
@property
|
|
def chapter_timestamps(self) -> Optional[str]:
|
|
"""
|
|
Optional. The path to the file containing the timestamps to embed into the video as
|
|
chapters. Should be formatted as:
|
|
|
|
.. code-block:: markdown
|
|
|
|
0:00 Intro
|
|
0:24 Blackwater Park
|
|
10:23 Bleak
|
|
16:39 Jokes
|
|
1:02:23 Ending
|
|
"""
|
|
if self._chapter_timestamps:
|
|
return self._chapter_timestamps.value
|
|
return None
|
|
|
|
|
|
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, YoutubeVideo]):
|
|
downloader_options_type = YoutubeVideoDownloaderOptions
|
|
downloader_entry_type = YoutubeVideo
|
|
|
|
@classmethod
|
|
def ytdl_option_defaults(cls) -> Dict:
|
|
"""
|
|
Default `ytdl_options`_ for ``video``
|
|
|
|
.. code-block:: yaml
|
|
|
|
ytdl_options:
|
|
ignoreerrors: True # ignore errors like hidden videos, age restriction, etc
|
|
"""
|
|
return dict(
|
|
super().ytdl_option_defaults(),
|
|
**{"break_on_existing": True},
|
|
)
|
|
|
|
def download(self) -> List[YoutubeVideo] | List[Tuple[YoutubeVideo, FileMetadata]]:
|
|
"""Download a single Youtube video"""
|
|
entry_dict = self.extract_info(url=self.download_options.video_url)
|
|
video = YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory)
|
|
|
|
# If no chapters, just return the video
|
|
if not self.download_options.chapter_timestamps:
|
|
return [video]
|
|
|
|
# Otherwise, add the chapters and return the video + chapter metadata
|
|
chapters = Chapters.from_file(chapters_file_path=self.download_options.chapter_timestamps)
|
|
if not self.is_dry_run:
|
|
add_ffmpeg_metadata(
|
|
file_path=video.get_download_file_path(),
|
|
chapters=chapters,
|
|
file_duration_sec=video.kwargs("duration"),
|
|
)
|
|
|
|
file_metadata = chapters.to_file_metadata(title="Chapters embedded into the video:")
|
|
|
|
return [(video, file_metadata)]
|