From 7347330a42698b10e228509c658abe1e54bf1a9b Mon Sep 17 00:00:00 2001 From: nathom Date: Thu, 22 Apr 2021 17:22:33 -0700 Subject: [PATCH] Add support for Youtube urls --- .gitignore | 1 + streamrip/bases.py | 38 +++++++++++++++++++++++++++++++++++++- streamrip/config.py | 3 +++ streamrip/constants.py | 2 +- streamrip/core.py | 25 +++++++++++++++++++------ streamrip/tracklists.py | 4 ++-- 6 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 1456aad..fc71f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ StreamripDownloads *.mkv *.aac *.pyc +*test.py diff --git a/streamrip/bases.py b/streamrip/bases.py index a9141bd..3a03ef7 100644 --- a/streamrip/bases.py +++ b/streamrip/bases.py @@ -765,7 +765,7 @@ class Tracklist(list): else: for item in self: - if self.client.source != 'soundcloud': + if self.client.source != "soundcloud": # soundcloud only gets metadata after `target` is called # message will be printed in `target` click.secho(f'\nDownloading "{item!s}"', fg="blue") @@ -951,3 +951,39 @@ class Tracklist(list): if isinstance(key, int): super().__setitem__(key, val) + + +class YoutubeVideo: + """Dummy class implemented for consistency with the Media API.""" + + class DummyClient: + source = 'youtube' + + def __init__(self, url: str): + self.url = url + self.client = self.DummyClient() + + def download(self, parent_folder='StreamripDownloads', **kwargs): + filename_formatter = "%(track_number)s.%(track)s.%(container)s" + filename = os.path.join(parent_folder, filename_formatter) + + p = subprocess.Popen( + [ + "youtube-dl", + "-x", + "--add-metadata", + "--audio-format", + "mp3", + "--embed-thumbnail", + "-o", + filename, + self.url, + ] + ) + p.wait() + + def load_meta(self, *args, **kwargs): + pass + + def tag(self, *args, **kwargs): + pass diff --git a/streamrip/config.py b/streamrip/config.py index 094db84..d89a1a1 100644 --- a/streamrip/config.py +++ b/streamrip/config.py @@ -69,6 +69,9 @@ class Config: "soundcloud": { "quality": 0, }, + "youtube": { + "quality": 0, + }, "database": {"enabled": True, "path": None}, "conversion": { "enabled": False, diff --git a/streamrip/constants.py b/streamrip/constants.py index 0ccc05b..272cb7d 100644 --- a/streamrip/constants.py +++ b/streamrip/constants.py @@ -145,7 +145,7 @@ LASTFM_URL_REGEX = r"https://www.last.fm/user/\w+/playlists/\w+" QOBUZ_INTERPRETER_URL_REGEX = ( r"https?://www\.qobuz\.com/\w\w-\w\w/interpreter/[-\w]+/[-\w]+" ) - +YOUTUBE_URL_REGEX = r"https://www\.youtube\.com/watch\?v=\w+" TIDAL_MAX_Q = 7 diff --git a/streamrip/core.py b/streamrip/core.py index 00bac6b..4f30b5d 100644 --- a/streamrip/core.py +++ b/streamrip/core.py @@ -12,13 +12,14 @@ import click import requests from tqdm import tqdm -from .bases import Track, Video +from .bases import Track, Video, YoutubeVideo from .clients import DeezerClient, QobuzClient, SoundCloudClient, TidalClient from .config import Config from .constants import ( CONFIG_PATH, DB_PATH, LASTFM_URL_REGEX, + YOUTUBE_URL_REGEX, MEDIA_TYPES, QOBUZ_INTERPRETER_URL_REGEX, SOUNDCLOUD_URL_REGEX, @@ -58,6 +59,7 @@ class MusicDL(list): self.soundcloud_url_parse = re.compile(SOUNDCLOUD_URL_REGEX) self.lastfm_url_parse = re.compile(LASTFM_URL_REGEX) self.interpreter_url_parse = re.compile(QOBUZ_INTERPRETER_URL_REGEX) + self.youtube_url_parse = re.compile(YOUTUBE_URL_REGEX) self.config = config if self.config is None: @@ -89,7 +91,17 @@ class MusicDL(list): :raises ParsingError """ - for source, url_type, item_id in self.parse_urls(url): + # youtube is handled by youtube-dl, so much of the + # processing is not necessary + youtube_urls = self.youtube_url_parse.findall(url) + if youtube_urls != []: + self.extend(YoutubeVideo(u) for u in youtube_urls) + + parsed = self.parse_urls(url) + if not parsed and len(self) == 0: + raise ParsingError(url) + + for source, url_type, item_id in parsed: if item_id in self.db: logger.info( f"ID {item_id} already downloaded, use --no-db to override." @@ -170,6 +182,10 @@ class MusicDL(list): item.client.source ) + if item is YoutubeVideo: + item.download(**arguments) + continue + arguments["quality"] = self.config.session[item.client.source]["quality"] if isinstance(item, Artist): filters_ = tuple( @@ -266,10 +282,7 @@ class MusicDL(list): logger.debug(f"Parsed urls: {parsed}") - if parsed != []: - return parsed - - raise ParsingError(f"Error parsing URL: `{url}`") + return parsed def handle_lastfm_urls(self, urls): # https://www.last.fm/user/nathan3895/playlists/12058911 diff --git a/streamrip/tracklists.py b/streamrip/tracklists.py index 71efc58..913c025 100644 --- a/streamrip/tracklists.py +++ b/streamrip/tracklists.py @@ -415,10 +415,10 @@ class Playlist(Tracklist): self.download_message() def _download_item(self, item: Track, **kwargs): - kwargs['parent_folder'] = self.folder + kwargs["parent_folder"] = self.folder if self.client.source == "soundcloud": item.load_meta() - click.secho(f"Downloading {item!s}", fg='blue') + click.secho(f"Downloading {item!s}", fg="blue") if kwargs.get("set_playlist_to_album", False): item["album"] = self.name