128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
from abc import ABC
|
|
from typing import Dict
|
|
from typing import Generic
|
|
from typing import List
|
|
from typing import TypeVar
|
|
|
|
from ytdl_subscribe.downloaders.downloader import Downloader
|
|
from ytdl_subscribe.downloaders.downloader import DownloaderValidator
|
|
from ytdl_subscribe.entries.soundcloud import SoundcloudAlbum
|
|
from ytdl_subscribe.entries.soundcloud import SoundcloudTrack
|
|
from ytdl_subscribe.validators.validators import BoolValidator
|
|
from ytdl_subscribe.validators.validators import StringValidator
|
|
|
|
###############################################################################
|
|
# Abstract Soundcloud downloader + options
|
|
|
|
|
|
class SoundcloudDownloaderOptions(DownloaderValidator, ABC):
|
|
"""
|
|
Abstract source validator for all soundcloud sources.
|
|
"""
|
|
|
|
_optional_keys = {"skip_premiere_tracks"}
|
|
|
|
def __init__(self, name: str, value: dict):
|
|
super().__init__(name=name, value=value)
|
|
self.skip_premiere_tracks = self._validate_key(
|
|
"skip_premiere_tracks", BoolValidator, default=True
|
|
)
|
|
|
|
|
|
SoundcloudDownloaderOptionsT = TypeVar(
|
|
"SoundcloudDownloaderOptionsT", bound=SoundcloudDownloaderOptions
|
|
)
|
|
|
|
|
|
class SoundcloudDownloader(
|
|
Downloader[SoundcloudDownloaderOptionsT, SoundcloudTrack],
|
|
Generic[SoundcloudDownloaderOptionsT],
|
|
ABC,
|
|
):
|
|
"""
|
|
Class that handles downloading soundcloud entries via ytdl and converting them into
|
|
SoundcloudTrack / SoundcloudAlbumTrack objects
|
|
"""
|
|
|
|
downloader_entry_type = SoundcloudTrack
|
|
|
|
@classmethod
|
|
def ytdl_option_defaults(cls) -> Dict:
|
|
"""Returns default format to be best mp3"""
|
|
return {
|
|
"format": "bestaudio[ext=mp3]",
|
|
}
|
|
|
|
@classmethod
|
|
def artist_url(cls, artist_name: str) -> str:
|
|
"""Returns full artist url"""
|
|
return f"https://soundcloud.com/{artist_name}"
|
|
|
|
def download_albums(self, artist_name: str) -> List[SoundcloudAlbum]:
|
|
"""
|
|
Given an artist name, download all of their albums
|
|
"""
|
|
artist_albums_url = f"{self.artist_url(artist_name)}/albums"
|
|
|
|
info = self.extract_info(url=artist_albums_url)
|
|
albums = [
|
|
SoundcloudAlbum(entry_dict=e, working_directory=self.working_directory)
|
|
for e in info["entries"]
|
|
]
|
|
|
|
return [album for album in albums if album.track_count > 0]
|
|
|
|
def download_tracks(self, artist_name) -> List[SoundcloudTrack]:
|
|
"""
|
|
Given an artist name, download all of their tracks
|
|
"""
|
|
artist_tracks_url = f"{self.artist_url(artist_name)}/tracks"
|
|
|
|
info = self.extract_info(url=artist_tracks_url)
|
|
return [
|
|
SoundcloudTrack(entry_dict=e, working_directory=self.working_directory)
|
|
for e in info["entries"]
|
|
]
|
|
|
|
|
|
###############################################################################
|
|
# Soundcloud albums and singles downloader + options
|
|
|
|
|
|
class SoundcloudAlbumsAndSinglesDownloadOptions(SoundcloudDownloaderOptions):
|
|
_required_keys = {"username"}
|
|
|
|
def __init__(self, name, value):
|
|
super().__init__(name, value)
|
|
self.username = self._validate_key(key="username", validator=StringValidator)
|
|
|
|
|
|
class SoundcloudAlbumsAndSinglesDownloader(
|
|
SoundcloudDownloader[SoundcloudAlbumsAndSinglesDownloadOptions]
|
|
):
|
|
downloader_options_type = SoundcloudAlbumsAndSinglesDownloadOptions
|
|
|
|
def download(self) -> List[SoundcloudTrack]:
|
|
"""
|
|
Soundcloud subscription to download albums and tracks as singles.
|
|
"""
|
|
tracks: List[SoundcloudTrack] = []
|
|
|
|
# Get the album info first. This tells us which track ids belong
|
|
# to an album. Unfortunately we cannot use download_archive or info.json for this
|
|
albums: List[SoundcloudAlbum] = self.download_albums(
|
|
artist_name=self.download_options.username.value
|
|
)
|
|
|
|
for album in albums:
|
|
tracks += album.album_tracks(
|
|
skip_premiere_tracks=self.download_options.skip_premiere_tracks.value
|
|
)
|
|
|
|
# only add tracks that are not part of an album
|
|
single_tracks = self.download_tracks(artist_name=self.download_options.username.value)
|
|
tracks += [
|
|
track for track in single_tracks if not any(album.contains(track) for album in albums)
|
|
]
|
|
|
|
return tracks
|