137 lines
4.4 KiB
Python
137 lines
4.4 KiB
Python
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.entries.entry import Entry
|
|
from ytdl_sub.validators.url_validator import SoundcloudUsernameUrlValidator
|
|
from ytdl_sub.validators.validators import BoolValidator
|
|
|
|
|
|
class SoundcloudAlbumsAndSinglesDownloadOptions(DownloaderValidator):
|
|
"""
|
|
Downloads a soundcloud user's entire discography. Groups together album tracks and considers
|
|
any track not in an album as a single. Also includes any collaboration tracks.
|
|
|
|
Usage:
|
|
|
|
.. code-block:: yaml
|
|
|
|
presets:
|
|
my_example_preset:
|
|
soundcloud:
|
|
# required
|
|
download_strategy: "albums_and_singles"
|
|
url: "soundcloud.com/username"
|
|
# optional
|
|
skip_premiere_tracks: True
|
|
|
|
"""
|
|
|
|
_required_keys = {"url"}
|
|
_optional_keys = {"skip_premiere_tracks"}
|
|
|
|
def __init__(self, name, value):
|
|
super().__init__(name, value)
|
|
self._url = self._validate_key(
|
|
key="url", validator=SoundcloudUsernameUrlValidator
|
|
).username_url
|
|
self._skip_premiere_tracks = self._validate_key(
|
|
"skip_premiere_tracks", BoolValidator, default=True
|
|
)
|
|
|
|
@property
|
|
def collection_validator(self) -> CollectionValidator:
|
|
"""Downloads the album tracks first, then the tracks"""
|
|
return CollectionValidator(
|
|
name=self._name,
|
|
value={
|
|
"urls": [
|
|
{
|
|
"url": f"{self._url}/tracks",
|
|
"variables": {
|
|
"track_number": "1",
|
|
"track_number_padded": "01",
|
|
"track_count": "1",
|
|
"album": "{title}",
|
|
"album_sanitized": "{title_sanitized}",
|
|
"album_year": "{upload_year}",
|
|
},
|
|
},
|
|
{
|
|
"url": f"{self._url}/albums",
|
|
"variables": {
|
|
"track_number": "{playlist_index}",
|
|
"track_number_padded": "{playlist_index_padded}",
|
|
"track_count": "{playlist_count}",
|
|
"album": "{playlist}",
|
|
"album_sanitized": "{playlist_sanitized}",
|
|
"album_year": "{playlist_max_upload_year}",
|
|
},
|
|
},
|
|
]
|
|
},
|
|
)
|
|
|
|
@property
|
|
def skip_premiere_tracks(self) -> bool:
|
|
"""
|
|
Optional. True to skip tracks that require purchasing. False otherwise. Defaults to True.
|
|
"""
|
|
return self._skip_premiere_tracks.value
|
|
|
|
@property
|
|
def url(self) -> str:
|
|
"""
|
|
Required. The Soundcloud user's url, i.e. ``soundcloud.com/the_username``
|
|
"""
|
|
return self._url
|
|
|
|
|
|
class SoundcloudAlbumsAndSinglesDownloader(
|
|
Downloader[SoundcloudAlbumsAndSinglesDownloadOptions, Entry]
|
|
):
|
|
downloader_options_type = SoundcloudAlbumsAndSinglesDownloadOptions
|
|
downloader_entry_type = Entry
|
|
|
|
supports_subtitles = False
|
|
supports_chapters = False
|
|
|
|
@classmethod
|
|
def ytdl_option_defaults(cls) -> Dict:
|
|
"""
|
|
Default `ytdl_options`_ for ``albums_and_singles``
|
|
|
|
.. code-block:: yaml
|
|
|
|
ytdl_options:
|
|
ignoreerrors: True # ignore errors like hidden videos, age restriction, etc
|
|
format: "bestaudio[ext=mp3]" # download format the best possible mp3
|
|
"""
|
|
return dict(
|
|
super().ytdl_option_defaults(),
|
|
**{
|
|
"format": "bestaudio[ext=mp3]",
|
|
},
|
|
)
|
|
|
|
def _should_skip(self, entry: Entry) -> bool:
|
|
if not self.download_options.skip_premiere_tracks:
|
|
return False
|
|
|
|
for url in [entry.kwargs_get("url", ""), entry.webpage_url]:
|
|
if "/preview/" in url:
|
|
return True
|
|
|
|
return False
|
|
|
|
def download(self) -> Generator[Entry, None, None]:
|
|
"""
|
|
Soundcloud subscription to download albums and tracks as singles.
|
|
"""
|
|
for entry in super().download():
|
|
if self._should_skip(entry):
|
|
continue
|
|
|
|
yield entry
|