[BACKEND] Only have single entry type

This commit is contained in:
Jesse Bannon 2022-09-27 19:04:57 -07:00
parent 3a37138ce6
commit db99eda41f
14 changed files with 29 additions and 221 deletions

View file

@ -50,8 +50,6 @@ _______
:member-order: bysource :member-order: bysource
:exclude-members: get_date_range :exclude-members: get_date_range
.. autofunction:: ytdl_sub.downloaders.youtube.channel.YoutubeChannelDownloader.added_override_variables()
.. autofunction:: ytdl_sub.downloaders.youtube.channel.YoutubeChannelDownloader.ytdl_option_defaults() .. autofunction:: ytdl_sub.downloaders.youtube.channel.YoutubeChannelDownloader.ytdl_option_defaults()
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -64,8 +62,6 @@ ________
:members: playlist_url :members: playlist_url
:member-order: bysource :member-order: bysource
.. autofunction:: ytdl_sub.downloaders.youtube.playlist.YoutubePlaylistDownloader.added_override_variables()
.. autofunction:: ytdl_sub.downloaders.youtube.playlist.YoutubePlaylistDownloader.ytdl_option_defaults() .. autofunction:: ytdl_sub.downloaders.youtube.playlist.YoutubePlaylistDownloader.ytdl_option_defaults()
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -300,26 +296,9 @@ Source Variables
---------------- ----------------
.. autoclass:: ytdl_sub.entries.variables.entry_variables.EntryVariables .. autoclass:: ytdl_sub.entries.variables.entry_variables.EntryVariables
.. _youtube-variables:
Youtube Variables
^^^^^^^^^^^^^^^^^
.. automodule:: ytdl_sub.entries.variables.youtube_variables
:members: :members:
:inherited-members: :inherited-members:
:undoc-members: :undoc-members:
:exclude-members: source_variables
.. _soundcloud-variables:
Soundcloud Variables
^^^^^^^^^^^^^^^^^^^^
.. automodule:: ytdl_sub.entries.variables.soundcloud_variables
:members:
:inherited-members:
:undoc-members:
:exclude-members: source_variables
------------------------------------------------------------------------------- -------------------------------------------------------------------------------

View file

@ -19,6 +19,7 @@ from ytdl_sub.config.preset_options import Overrides
from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.preset_options import YTDLOptions
from ytdl_sub.downloaders.downloader import Downloader from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.entries.entry import Entry
from ytdl_sub.plugins.plugin import Plugin from ytdl_sub.plugins.plugin import Plugin
from ytdl_sub.plugins.plugin import PluginOptions from ytdl_sub.plugins.plugin import PluginOptions
from ytdl_sub.utils.yaml import load_yaml from ytdl_sub.utils.yaml import load_yaml
@ -129,7 +130,7 @@ class Preset(StrictDictValidator):
@property @property
def _source_variables(self) -> List[str]: def _source_variables(self) -> List[str]:
return self.downloader.downloader_entry_type.source_variables() return Entry.source_variables()
def __validate_and_get_downloader(self, downloader_source: str) -> Type[Downloader]: def __validate_and_get_downloader(self, downloader_source: str) -> Type[Downloader]:
return self._validate_key(key=downloader_source, validator=DownloadStrategyValidator).get( return self._validate_key(key=downloader_source, validator=DownloadStrategyValidator).get(

View file

@ -86,17 +86,15 @@ class DownloaderValidator(StrictDictValidator, AddsVariablesMixin, ABC):
DownloaderOptionsT = TypeVar("DownloaderOptionsT", bound=DownloaderValidator) DownloaderOptionsT = TypeVar("DownloaderOptionsT", bound=DownloaderValidator)
DownloaderEntryT = TypeVar("DownloaderEntryT", bound=Entry)
class Downloader(DownloadArchiver, Generic[DownloaderOptionsT, DownloaderEntryT], ABC): class Downloader(DownloadArchiver, Generic[DownloaderOptionsT], ABC):
""" """
Class that interacts with ytdl to perform the download of metadata and content, Class that interacts with ytdl to perform the download of metadata and content,
and should translate that to list of Entry objects. and should translate that to list of Entry objects.
""" """
downloader_options_type: Type[DownloaderValidator] = DownloaderValidator downloader_options_type: Type[DownloaderValidator] = DownloaderValidator
downloader_entry_type: Type[Entry] = Entry
supports_download_archive: bool = True supports_download_archive: bool = True
supports_subtitles: bool = True supports_subtitles: bool = True
@ -473,7 +471,7 @@ class Downloader(DownloadArchiver, Generic[DownloaderOptionsT, DownloaderEntryT]
def download( def download(
self, self,
) -> Iterable[DownloaderEntryT] | Iterable[Tuple[DownloaderEntryT, FileMetadata]]: ) -> Iterable[Entry] | Iterable[Tuple[Entry, FileMetadata]]:
"""The function to perform the download of all media entries""" """The function to perform the download of all media entries"""
# download the bottom-most urls first since they are top-priority # download the bottom-most urls first since they are top-priority
for collection_url in reversed(self.collection.collection_urls.list): for collection_url in reversed(self.collection.collection_urls.list):

View file

@ -1,7 +1,6 @@
from ytdl_sub.downloaders.downloader import Downloader from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator
from ytdl_sub.entries.entry import Entry
class CollectionDownloadOptions(CollectionValidator, DownloaderValidator): class CollectionDownloadOptions(CollectionValidator, DownloaderValidator):
@ -41,6 +40,5 @@ class CollectionDownloadOptions(CollectionValidator, DownloaderValidator):
return self return self
class CollectionDownloader(Downloader[CollectionDownloadOptions, Entry]): class CollectionDownloader(Downloader[CollectionDownloadOptions]):
downloader_options_type = CollectionDownloadOptions downloader_options_type = CollectionDownloadOptions
downloader_entry_type = Entry

View file

@ -89,9 +89,7 @@ class SoundcloudAlbumsAndSinglesDownloadOptions(DownloaderValidator):
return self._url return self._url
class SoundcloudAlbumsAndSinglesDownloader( class SoundcloudAlbumsAndSinglesDownloader(Downloader[SoundcloudAlbumsAndSinglesDownloadOptions]):
Downloader[SoundcloudAlbumsAndSinglesDownloadOptions, Entry]
):
downloader_options_type = SoundcloudAlbumsAndSinglesDownloadOptions downloader_options_type = SoundcloudAlbumsAndSinglesDownloadOptions
downloader_entry_type = Entry downloader_entry_type = Entry

View file

@ -1,29 +0,0 @@
from abc import ABC
from typing import Generic
from typing import TypeVar
from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.entries.youtube import YoutubeVideo
class YoutubeDownloaderOptions(DownloaderValidator, ABC):
"""
Abstract source validator for all soundcloud sources.
"""
YoutubeDownloaderOptionsT = TypeVar("YoutubeDownloaderOptionsT", bound=YoutubeDownloaderOptions)
YoutubeVideoT = TypeVar("YoutubeVideoT", bound=YoutubeVideo)
class YoutubeDownloader(
Downloader[YoutubeDownloaderOptionsT, YoutubeVideoT],
Generic[YoutubeDownloaderOptionsT, YoutubeVideoT],
ABC,
):
"""
Class that handles downloading youtube entries via ytdl and converting them into
YoutubeVideo like objects. Reserved for any future logic that is shared amongst all YT
downloaders.
"""

View file

@ -1,17 +1,15 @@
from typing import Dict from typing import Dict
from typing import Generator
from typing import List from typing import List
from typing import Optional from typing import Optional
from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator
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.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
from ytdl_sub.validators.url_validator import YoutubeChannelUrlValidator from ytdl_sub.validators.url_validator import YoutubeChannelUrlValidator
class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions): class YoutubeChannelDownloaderOptions(DownloaderValidator):
""" """
Downloads all videos from a youtube channel. Downloads all videos from a youtube channel.
@ -104,9 +102,8 @@ class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions):
return self._channel_banner_path return self._channel_banner_path
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions, YoutubeVideo]): class YoutubeChannelDownloader(Downloader[YoutubeChannelDownloaderOptions]):
downloader_options_type = YoutubeChannelDownloaderOptions downloader_options_type = YoutubeChannelDownloaderOptions
downloader_entry_type = YoutubeVideo
# pylint: disable=line-too-long # pylint: disable=line-too-long
@classmethod @classmethod
@ -128,10 +125,3 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
) )
# pylint: enable=line-too-long # pylint: enable=line-too-long
def download(self) -> Generator[YoutubeVideo, None, None]:
"""
Downloads all videos from a channel
"""
for entry in super().download():
yield entry.to_type(YoutubeVideo)

View file

@ -3,9 +3,9 @@ from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloader from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.youtube.playlist import YoutubePlaylistDownloaderOptions from ytdl_sub.downloaders.youtube.playlist import YoutubePlaylistDownloaderOptions
from ytdl_sub.entries.youtube import YoutubeVideo from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.chapters import Chapters from ytdl_sub.utils.chapters import Chapters
from ytdl_sub.utils.chapters import Timestamp from ytdl_sub.utils.chapters import Timestamp
from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters
@ -58,11 +58,8 @@ class YoutubeMergePlaylistDownloaderOptions(YoutubePlaylistDownloaderOptions):
return self._add_chapters return self._add_chapters
class YoutubeMergePlaylistDownloader( class YoutubeMergePlaylistDownloader(Downloader[YoutubeMergePlaylistDownloaderOptions]):
YoutubeDownloader[YoutubeMergePlaylistDownloaderOptions, YoutubeVideo]
):
downloader_options_type = YoutubeMergePlaylistDownloaderOptions downloader_options_type = YoutubeMergePlaylistDownloaderOptions
downloader_entry_type = YoutubeVideo
supports_download_archive = False supports_download_archive = False
supports_subtitles = False supports_subtitles = False
supports_chapters = False supports_chapters = False
@ -102,7 +99,7 @@ class YoutubeMergePlaylistDownloader(
}, },
) )
def _get_chapters(self, merged_video: YoutubeVideo, add_chapters: bool) -> FileMetadata: def _get_chapters(self, merged_video: Entry, add_chapters: bool) -> FileMetadata:
titles: List[str] = [] titles: List[str] = []
timestamps: List[Timestamp] = [] timestamps: List[Timestamp] = []
@ -124,7 +121,7 @@ class YoutubeMergePlaylistDownloader(
return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file") return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file")
def _to_merged_video(self, entry_dict: Dict) -> YoutubeVideo: def _to_merged_video(self, entry_dict: Dict) -> Entry:
""" """
Adds a few entries not included in a playlist entry to make it look like a merged video Adds a few entries not included in a playlist entry to make it look like a merged video
entry_dict entry_dict
@ -143,9 +140,9 @@ class YoutubeMergePlaylistDownloader(
) )
entry_dict["webpage_url"] = self.download_options.playlist_url entry_dict["webpage_url"] = self.download_options.playlist_url
return YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory) return Entry(entry_dict=entry_dict, working_directory=self.working_directory)
def download(self) -> List[Tuple[YoutubeVideo, FileMetadata]]: def download(self) -> List[Tuple[Entry, FileMetadata]]:
"""Download a single Youtube video, then split it into multiple videos""" """Download a single Youtube video, then split it into multiple videos"""
entry_dict = self.extract_info(url=self.collection.collection_urls.list[0].url) entry_dict = self.extract_info(url=self.collection.collection_urls.list[0].url)
merged_video = self._to_merged_video(entry_dict=entry_dict) merged_video = self._to_merged_video(entry_dict=entry_dict)

View file

@ -1,15 +1,12 @@
from typing import Dict from typing import Dict
from typing import Generator
from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloader
from ytdl_sub.downloaders.youtube.abc import YoutubeDownloaderOptions
from ytdl_sub.entries.entry_parent import EntryParent
from ytdl_sub.entries.youtube import YoutubeVideo
from ytdl_sub.validators.url_validator import YoutubePlaylistUrlValidator from ytdl_sub.validators.url_validator import YoutubePlaylistUrlValidator
class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions): class YoutubePlaylistDownloaderOptions(DownloaderValidator):
""" """
Downloads all videos from a youtube playlist. Downloads all videos from a youtube playlist.
@ -54,9 +51,8 @@ class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions):
return self._playlist_url return self._playlist_url
class YoutubePlaylistDownloader(YoutubeDownloader[YoutubePlaylistDownloaderOptions, YoutubeVideo]): class YoutubePlaylistDownloader(Downloader[YoutubePlaylistDownloaderOptions]):
downloader_options_type = YoutubePlaylistDownloaderOptions downloader_options_type = YoutubePlaylistDownloaderOptions
downloader_entry_type = YoutubeVideo
# pylint: disable=line-too-long # pylint: disable=line-too-long
@classmethod @classmethod
@ -76,16 +72,3 @@ class YoutubePlaylistDownloader(YoutubeDownloader[YoutubePlaylistDownloaderOptio
) )
# pylint: enable=line-too-long # pylint: enable=line-too-long
@property
def playlist(self) -> EntryParent:
"""Get the playlist parent entry"""
assert len(self.parents) == 1, "Playlist should be the only entry parent"
return self.parents[0]
def download(self) -> Generator[YoutubeVideo, None, None]:
"""
Downloads all videos in a Youtube playlist.
"""
for entry in super().download():
yield entry.to_type(YoutubeVideo)

View file

@ -1,14 +1,12 @@
from typing import Dict from typing import Dict
from typing import List
from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.downloader import DownloaderValidator
from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator from ytdl_sub.downloaders.generic.collection_validator import CollectionValidator
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.validators.url_validator import YoutubeVideoUrlValidator from ytdl_sub.validators.url_validator import YoutubeVideoUrlValidator
class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions): class YoutubeVideoDownloaderOptions(DownloaderValidator):
""" """
Downloads a single youtube video. This download strategy is intended for CLI usage performing Downloads a single youtube video. This download strategy is intended for CLI usage performing
a one-time download of a video, not a subscription. a one-time download of a video, not a subscription.
@ -53,9 +51,8 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
return self._video_url return self._video_url
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, YoutubeVideo]): class YoutubeVideoDownloader(Downloader[YoutubeVideoDownloaderOptions]):
downloader_options_type = YoutubeVideoDownloaderOptions downloader_options_type = YoutubeVideoDownloaderOptions
downloader_entry_type = YoutubeVideo
@classmethod @classmethod
def ytdl_option_defaults(cls) -> Dict: def ytdl_option_defaults(cls) -> Dict:
@ -71,8 +68,3 @@ class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, Yo
super().ytdl_option_defaults(), super().ytdl_option_defaults(),
**{"break_on_existing": True}, **{"break_on_existing": True},
) )
def download(self) -> List[YoutubeVideo]:
"""Downloads the single video"""
for entry in super().download():
yield entry.to_type(YoutubeVideo)

View file

@ -4,7 +4,8 @@ from yt_dlp.utils import sanitize_filename
from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.base_entry import BaseEntry
from ytdl_sub.entries.base_entry import BaseEntryVariables from ytdl_sub.entries.base_entry import BaseEntryVariables
from ytdl_sub.entries.variables.kwargs import EXT, CHANNEL from ytdl_sub.entries.variables.kwargs import CHANNEL
from ytdl_sub.entries.variables.kwargs import EXT
from ytdl_sub.entries.variables.kwargs import PLAYLIST_COUNT from ytdl_sub.entries.variables.kwargs import PLAYLIST_COUNT
from ytdl_sub.entries.variables.kwargs import PLAYLIST_DESCRIPTION from ytdl_sub.entries.variables.kwargs import PLAYLIST_DESCRIPTION
from ytdl_sub.entries.variables.kwargs import PLAYLIST_INDEX from ytdl_sub.entries.variables.kwargs import PLAYLIST_INDEX

View file

@ -65,6 +65,8 @@ def mock_entry_to_dict(
"title_sanitized": "entry title", "title_sanitized": "entry title",
"ext": ext, "ext": ext,
"description": "", "description": "",
"channel": "abc123",
"channel_sanitized": "abc123",
"extractor": extractor, "extractor": extractor,
"uploader": "abc123", "uploader": "abc123",
"uploader_id": "abc123", "uploader_id": "abc123",

View file

@ -1,43 +0,0 @@
import pytest
from ytdl_sub.entries.soundcloud import SoundcloudTrack
@pytest.fixture
def url():
return "soundcloud.com/artist/track-asdfasdf"
@pytest.fixture
def mock_soundcloud_track_to_dict(mock_entry_to_dict):
return dict(
mock_entry_to_dict,
**{
"track_number": 1,
"track_number_padded": "01",
"album": mock_entry_to_dict["title"],
"album_sanitized": mock_entry_to_dict["title_sanitized"],
"album_year": mock_entry_to_dict["upload_year"],
"track_count": 1,
}
)
@pytest.fixture
def mock_soundcloud_track_kwargs(mock_entry_kwargs, url):
return dict(mock_entry_kwargs, **{"url": url})
@pytest.fixture
def mock_soundcloud_track(mock_soundcloud_track_kwargs):
return SoundcloudTrack(entry_dict=mock_soundcloud_track_kwargs, working_directory=".")
class TestSoundcloudTrack(object):
def test_to_dict(self, mock_soundcloud_track, mock_soundcloud_track_to_dict):
assert mock_soundcloud_track.to_dict() == mock_soundcloud_track_to_dict
def test_soundcloud_dict_contains_valid_formatters(
self, mock_soundcloud_track, validate_entry_dict_contains_valid_formatters
):
assert validate_entry_dict_contains_valid_formatters(mock_soundcloud_track)

View file

@ -1,59 +0,0 @@
import pytest
from ytdl_sub.entries.soundcloud import SoundcloudTrack
from ytdl_sub.entries.youtube import YoutubeVideo
@pytest.fixture
def playlist_index():
return 1
@pytest.fixture
def playlist_count():
return 1
@pytest.fixture
def channel():
return "the channel"
@pytest.fixture
def mock_youtube_video_to_dict(mock_entry_to_dict, playlist_index, playlist_count, channel):
return dict(
mock_entry_to_dict,
**{
"playlist_index": playlist_index,
"playlist_count": playlist_count,
"channel": channel,
"channel_sanitized": channel,
}
)
@pytest.fixture
def mock_youtube_video_kwargs(mock_entry_kwargs, playlist_index, playlist_count, channel):
return dict(
mock_entry_kwargs,
**{
"playlist_index": playlist_index,
"playlist_size": playlist_count,
"channel": channel,
}
)
@pytest.fixture
def mock_youtube_video(mock_youtube_video_kwargs):
return YoutubeVideo(entry_dict=mock_youtube_video_kwargs, working_directory=".")
class TestYoutubeVideo(object):
def test_to_dict(self, mock_youtube_video, mock_youtube_video_to_dict):
assert mock_youtube_video.to_dict() == mock_youtube_video_to_dict
def test_youtube_dict_contains_valid_formatters(
self, mock_youtube_video, validate_entry_dict_contains_valid_formatters
):
assert validate_entry_dict_contains_valid_formatters(mock_youtube_video)