mirror of
https://github.com/nathom/streamrip.git
synced 2024-09-19 19:28:46 -04:00
Soundcloud downloads working
This commit is contained in:
parent
fa72e82769
commit
f3274693bb
6 changed files with 34 additions and 39 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue