Soundcloud downloads working

This commit is contained in:
nathom 2021-04-05 17:42:24 -07:00
parent fa72e82769
commit f3274693bb
6 changed files with 34 additions and 39 deletions

View file

@ -656,14 +656,7 @@ class TidalClient(ClientInterface):
class SoundCloudClient(ClientInterface): class SoundCloudClient(ClientInterface):
source = "soundcloud" source = "soundcloud"
max_quality = 0 max_quality = 0
logged_in = True
def __init__(self):
self.session = requests.Session()
self.session.headers.update(
{
"User-Agent": AGENT,
}
)
def login(self): def login(self):
raise NotImplementedError raise NotImplementedError
@ -671,10 +664,10 @@ class SoundCloudClient(ClientInterface):
def get(self, id, media_type="track"): def get(self, id, media_type="track"):
assert media_type in ("track", "playlist"), f"{media_type} not supported" assert media_type in ("track", "playlist"), f"{media_type} not supported"
if media_type == "track": if "http" in str(id):
resp, _ = self._get(f"{media_type}s/{id}")
elif "http" in id:
resp, _ = self._get(f"resolve?url={id}") resp, _ = self._get(f"resolve?url={id}")
elif media_type == "track":
resp, _ = self._get(f"{media_type}s/{id}")
else: else:
raise Exception(id) raise Exception(id)
@ -716,7 +709,7 @@ class SoundCloudClient(ClientInterface):
url = f"{SOUNDCLOUD_BASE}/{path}" url = f"{SOUNDCLOUD_BASE}/{path}"
logger.debug(f"Fetching url {url}") logger.debug(f"Fetching url {url}")
r = self.session.get(url, params=params) r = requests.get(url, params=params)
if resp_obj: if resp_obj:
return r return r

View file

@ -54,6 +54,9 @@ class Config:
"deezer": { "deezer": {
"quality": 2, "quality": 2,
}, },
"soundcloud": {
"quality": 0,
},
"database": {"enabled": True, "path": None}, "database": {"enabled": True, "path": None},
"conversion": { "conversion": {
"enabled": False, "enabled": False,

View file

@ -135,13 +135,14 @@ FOLDER_FORMAT = (
TRACK_FORMAT = "{tracknumber}. {artist} - {title}" TRACK_FORMAT = "{tracknumber}. {artist} - {title}"
URL_REGEX = ( URL_REGEX = (
r"https:\/\/(?:www|open|play|listen)?\.?(\w+)\.com(?:(?:\/(track|playlist|album|" r"https:\/\/(?:www|open|play|listen)?\.?(qobuz|tidal|deezer)\.com(?:(?:\/(track|playlist|album|"
r"artist|label))|(?:\/[-\w]+?))+\/([-\w]+)" r"artist|label))|(?:\/[-\w]+?))+\/([-\w]+)"
) )
SOUNDCLOUD_URL_REGEX = r"https://soundcloud.com/[-\w:]+" SOUNDCLOUD_URL_REGEX = r"https://soundcloud.com/[-\w:/]+"
SOUNDCLOUD_CLIENT_ID = "a3e059563d7fd3372b49b37f00a00bcf" SOUNDCLOUD_CLIENT_ID = "a3e059563d7fd3372b49b37f00a00bcf"
TIDAL_MAX_Q = 7 TIDAL_MAX_Q = 7
DEEZER_MAX_Q = 6 DEEZER_MAX_Q = 6
AVAILABLE_QUALITY_IDS = (0, 1, 2, 3, 4) AVAILABLE_QUALITY_IDS = (0, 1, 2, 3, 4)
MEDIA_TYPES = ("track", "album", "artist", "label", "playlist")

View file

@ -1,4 +1,5 @@
import logging import logging
from pprint import pprint
import os import os
import re import re
import sys import sys
@ -11,7 +12,7 @@ import click
from .clients import DeezerClient, QobuzClient, SoundCloudClient, TidalClient from .clients import DeezerClient, QobuzClient, SoundCloudClient, TidalClient
from .config import Config from .config import Config
from .constants import CONFIG_PATH, DB_PATH, SOUNDCLOUD_URL_REGEX, URL_REGEX from .constants import (CONFIG_PATH, DB_PATH, SOUNDCLOUD_URL_REGEX, URL_REGEX, MEDIA_TYPES)
from .db import MusicDB from .db import MusicDB
from .downloader import Album, Artist, Label, Playlist, Track from .downloader import Album, Artist, Label, Playlist, Track
from .exceptions import AuthenticationError, ParsingError from .exceptions import AuthenticationError, ParsingError
@ -127,6 +128,11 @@ class MusicDL(list):
client = self.get_client(source) client = self.get_client(source)
if media_type not in MEDIA_TYPES:
if 'playlist' in media_type: # for SoundCloud
media_type = 'playlist'
assert media_type in MEDIA_TYPES, media_type
item = MEDIA_CLASS[media_type](client=client, id=item_id) item = MEDIA_CLASS[media_type](client=client, id=item_id)
self.append(item) self.append(item)
@ -209,12 +215,14 @@ class MusicDL(list):
:raises exceptions.ParsingError :raises exceptions.ParsingError
""" """
parsed = self.url_parse.findall(url) parsed = self.url_parse.findall(url) # Qobuz, Tidal, Dezer
soundcloud_urls = self.soundcloud_url_parse.findall(url) soundcloud_urls = self.soundcloud_url_parse.findall(url)
if len(soundcloud_urls) > 0: soundcloud_items = [self.clients["soundcloud"].get(u) for u in soundcloud_urls]
parsed.extend(
self.clients["soundcloud"].resolve(u) for u in soundcloud_urls parsed.extend(
) ("soundcloud", item["kind"], url)
for item, url in zip(soundcloud_items, soundcloud_urls)
)
logger.debug(f"Parsed urls: {parsed}") logger.debug(f"Parsed urls: {parsed}")

View file

@ -121,7 +121,6 @@ class Track:
assert hasattr(self, "id"), "id must be set before loading metadata" assert hasattr(self, "id"), "id must be set before loading metadata"
self.resp = self.client.get(self.id, media_type="track") self.resp = self.client.get(self.id, media_type="track")
pprint(self.resp)
self.meta = TrackMetadata( self.meta = TrackMetadata(
track=self.resp, source=self.client.source track=self.resp, source=self.client.source
) # meta dict -> TrackMetadata object ) # meta dict -> TrackMetadata object
@ -133,7 +132,7 @@ class Track:
elif self.client.source == "deezer": elif self.client.source == "deezer":
self.cover_url = self.resp["album"]["cover_medium"] self.cover_url = self.resp["album"]["cover_medium"]
elif self.client.source == "soundcloud": elif self.client.source == "soundcloud":
self.cover_url = self.resp["artwork_url"].replace("large", "t500x500") self.cover_url = (self.resp["artwork_url"] or self.resp['user'].get("avatar_url")).replace("large", "t500x500")
else: else:
raise InvalidSourceError(self.client.source) raise InvalidSourceError(self.client.source)
except KeyError: except KeyError:
@ -169,7 +168,7 @@ class Track:
:type progress_bar: bool :type progress_bar: bool
""" """
# args override attributes # args override attributes
self.quality = min((quality or self.quality), self.client.max_quality) self.quality = min(quality, self.client.max_quality)
self.folder = parent_folder or self.folder self.folder = parent_folder or self.folder
self.file_format = kwargs.get("track_format", TRACK_FORMAT) self.file_format = kwargs.get("track_format", TRACK_FORMAT)
@ -194,6 +193,7 @@ class Track:
return False return False
if hasattr(self, "cover_url"): # only for playlists and singles if hasattr(self, "cover_url"): # only for playlists and singles
logger.debug("Downloading cover")
self.download_cover() self.download_cover()
if self.client.source == "soundcloud": if self.client.source == "soundcloud":
@ -203,7 +203,7 @@ class Track:
dl_info = self.client.get_file_url(url_id, self.quality) dl_info = self.client.get_file_url(url_id, self.quality)
temp_file = os.path.join(gettempdir(), f"~{self.id}_{quality}.tmp") temp_file = os.path.join(gettempdir(), f"~{hash(self.id)}_{quality}.tmp")
logger.debug("Temporary file path: %s", temp_file) logger.debug("Temporary file path: %s", temp_file)
if self.client.source == "qobuz": if self.client.source == "qobuz":
@ -240,7 +240,7 @@ class Track:
[ [
"ffmpeg", "ffmpeg",
"-i", "-i",
dl_info, dl_info['url'],
"-c", "-c",
"copy", "copy",
"-y", "-y",
@ -288,7 +288,7 @@ class Track:
assert hasattr(self, "cover_url"), "must set cover_url attribute" assert hasattr(self, "cover_url"), "must set cover_url attribute"
self.cover_path = os.path.join(self.folder, f"cover{hash(self.meta.album)}.jpg") self.cover_path = os.path.join(self.folder, f"cover{hash(self.cover_url)}.jpg")
logger.debug(f"Downloading cover from {self.cover_url}") logger.debug(f"Downloading cover from {self.cover_url}")
click.secho(f"\nDownloading cover art for {self!s}", fg="blue") click.secho(f"\nDownloading cover art for {self!s}", fg="blue")
@ -1019,7 +1019,7 @@ class Playlist(Tracklist):
self.name = self.meta["name"] self.name = self.meta["name"]
tracklist = self.meta["tracks"]["items"] tracklist = self.meta["tracks"]["items"]
def gen_cover(track): # ? def gen_cover(track):
return track["album"]["image"]["small"] return track["album"]["image"]["small"]
def meta_args(track): def meta_args(track):
@ -1047,7 +1047,6 @@ class Playlist(Tracklist):
return track["album"]["cover_medium"] return track["album"]["cover_medium"]
elif self.client.source == "soundcloud": elif self.client.source == "soundcloud":
pprint(self.meta)
self.name = self.meta["title"] self.name = self.meta["title"]
tracklist = self.meta["tracks"] tracklist = self.meta["tracks"]
@ -1126,7 +1125,6 @@ class Playlist(Tracklist):
:param client: :param client:
:type client: ClientInterface :type client: ClientInterface
""" """
print(item.keys())
if client.source == "qobuz": if client.source == "qobuz":
return { return {
"name": item["name"], "name": item["name"],
@ -1223,7 +1221,7 @@ class Artist(Tracklist):
def download( def download(
self, self,
parent_folder: str = "Downloads", parent_folder: str = "StreamripDownloads",
filters: Optional[Tuple] = None, filters: Optional[Tuple] = None,
no_repeats: bool = False, no_repeats: bool = False,
quality: int = 6, quality: int = 6,

View file

@ -154,22 +154,14 @@ class TrackMetadata:
elif self.__source == "soundcloud": elif self.__source == "soundcloud":
self.title = track["title"].strip() self.title = track["title"].strip()
print(f"{self.title=}")
self.genre = track["genre"] self.genre = track["genre"]
print(f"{self.genre=}")
self.artist = track["user"]["username"] self.artist = track["user"]["username"]
self.albumartist = self.artist self.albumartist = self.artist
print(f"{self.artist=}")
self.year = track["created_at"][:4] self.year = track["created_at"][:4]
print(f"{self.year=}")
self.label = track["label_name"] self.label = track["label_name"]
print(f"{self.label=}") self.description = track["description"]
self.comment = track["description"]
print(f"{self.comment=}")
self.tracknumber = 0 self.tracknumber = 0
print(f"{self.tracknumber=}")
self.tracktotal = 0 self.tracktotal = 0
print(f"{self.tracktotal=}")
else: else:
raise ValueError(self.__source) raise ValueError(self.__source)