Files
spotify-downloader/spotdl/metadata_search.py
2020-05-10 18:52:50 +05:30

261 lines
9.7 KiB
Python

from spotdl.metadata.providers import ProviderSpotify
from spotdl.metadata.providers import ProviderYouTube
from spotdl.lyrics.providers import Genius
from spotdl.lyrics.exceptions import LyricsNotFoundError
import spotdl.metadata
import spotdl.util
from spotdl.metadata.exceptions import SpotifyMetadataNotFoundError
from spotdl.command_line.exceptions import NoYouTubeVideoFoundError
from spotdl.command_line.exceptions import NoYouTubeVideoMatchError
import sys
import logging
logger = logging.getLogger(__name__)
PROVIDERS = {
"spotify": ProviderSpotify,
"youtube": ProviderYouTube,
}
def prompt_for_youtube_search_result(videos):
max_index_length = len(str(len(videos)))
max_title_length = max(len(v["title"]) for v in videos)
msg = "{index:>{max_index}}. Skip downloading this track".format(
index=0,
max_index=max_index_length,
)
print(msg, file=sys.stderr)
for index, video in enumerate(videos, 1):
vid_details = "{index:>{max_index}}. {title:<{max_title}}\n{new_line_gap} {url} [{duration}]".format(
index=index,
max_index=max_index_length,
title=video["title"],
max_title=max_title_length,
new_line_gap=" " * max_index_length,
url=video["url"],
duration=video["duration"],
)
print(vid_details, file=sys.stderr)
print("", file=sys.stderr)
selection = spotdl.util.prompt_user_for_selection(range(1, len(videos)+1))
if selection is None:
return None
return videos[selection-1]
class MetadataSearch:
def __init__(self, track, lyrics=False, yt_search_format="{artist} - {track-name}", yt_manual=False, providers=PROVIDERS):
self.track = track
self.track_type = spotdl.util.track_type(track)
self.lyrics = lyrics
self.yt_search_format = yt_search_format
self.yt_manual = yt_manual
self.providers = {}
for provider, parent in providers.items():
self.providers[provider] = parent()
self.lyric_provider = Genius()
def get_lyrics(self, query):
try:
lyrics = self.lyric_provider.from_query(query)
except LyricsNotFoundError as e:
logger.warning(e.args[0])
lyrics = None
return lyrics
def _make_lyric_search_query(self, metadata):
if self.track_type == "query":
lyric_query = self.track
else:
lyric_search_format = "{artist} - {track-name}"
lyric_query = spotdl.metadata.format_string(
lyric_search_format,
metadata
)
return lyric_query
def on_youtube_and_spotify(self):
track_type_mapper = {
"spotify": self._on_youtube_and_spotify_for_type_spotify,
"youtube": self._on_youtube_and_spotify_for_type_youtube,
"query": self._on_youtube_and_spotify_for_type_query,
}
caller = track_type_mapper[self.track_type]
metadata = caller()
if not self.lyrics:
return metadata
lyric_query = self._make_lyric_search_query(metadata)
metadata["lyrics"] = spotdl.util.ThreadWithReturnValue(
target=self.get_lyrics,
args=(lyric_query,),
)
return metadata
def on_youtube(self):
track_type_mapper = {
"spotify": self._on_youtube_for_type_spotify,
"youtube": self._on_youtube_for_type_youtube,
"query": self._on_youtube_for_type_query,
}
caller = track_type_mapper[self.track_type]
metadata = caller(self.track)
if not self.lyrics:
return metadata
lyric_query = self._make_lyric_search_query(metadata)
metadata["lyrics"] = spotdl.util.ThreadWithReturnValue(
target=self.get_lyrics,
arguments=(lyric_query,),
)
return metadata
def on_spotify(self):
track_type_mapper = {
"spotify": self._on_spotify_for_type_spotify,
"youtube": self._on_spotify_for_type_youtube,
"query": self._on_spotify_for_type_query,
}
caller = track_type_mapper[self.track_type]
metadata = caller(self.track)
if not self.lyrics:
return metadata
lyric_query = self._make_lyric_search_query(metadata)
metadata["lyrics"] = spotdl.util.ThreadWithReturnValue(
target=self.get_lyrics,
arguments=(lyric_query,),
)
return metadata
def best_on_youtube_search(self):
track_type_mapper = {
"spotify": self._best_on_youtube_search_for_type_spotify,
"youtube": self._best_on_youtube_search_for_type_youtube,
"query": self._best_on_youtube_search_for_type_query,
}
caller = track_type_mapper[self.track_type]
video = caller(self.track)
return video
def _best_on_youtube_search_for_type_query(self, query):
videos = self.providers["youtube"].search(query)
if not videos:
raise NoYouTubeVideoFoundError(
'YouTube returned no videos for the search query "{}".'.format(query)
)
if self.yt_manual:
video = prompt_for_youtube_search_result(videos)
else:
video = videos.bestmatch()
if video is None:
raise NoYouTubeVideoMatchError(
'No matching videos found on YouTube for the search query "{}".'.format(
search_query
)
)
return video
def _best_on_youtube_search_for_type_youtube(self, url):
video = self._best_on_youtube_search_for_type_query(url)
return video
def _best_on_youtube_search_for_type_spotify(self, url):
spotify_metadata = self._on_spotify_for_type_spotify(self.track)
search_query = spotdl.metadata.format_string(self.yt_search_format, spotify_metadata)
video = self._best_on_youtube_search_for_type_query(search_query)
return video
def _on_youtube_and_spotify_for_type_spotify(self):
logger.debug("Extracting YouTube and Spotify metadata for input Spotify URI.")
spotify_metadata = self._on_spotify_for_type_spotify(self.track)
search_query = spotdl.metadata.format_string(self.yt_search_format, spotify_metadata)
youtube_video = self._best_on_youtube_search_for_type_spotify(search_query)
youtube_metadata = self.providers["youtube"].from_url(youtube_video["url"])
metadata = spotdl.util.merge(
youtube_metadata,
spotify_metadata
)
return metadata
def _on_youtube_and_spotify_for_type_youtube(self):
logger.debug("Extracting YouTube and Spotify metadata for input YouTube URL.")
youtube_metadata = self._on_youtube_for_type_youtube(self.track)
search_query = spotdl.metadata.format_string("{track-name}", youtube_metadata)
spotify_metadata = self._on_spotify_for_type_query(search_query)
metadata = spotdl.util.merge(
youtube_metadata,
spotify_metadata
)
return metadata
def _on_youtube_and_spotify_for_type_query(self):
logger.debug("Extracting YouTube and Spotify metadata for input track query.")
search_query = self.track
# Make use of threads here to search on both YouTube & Spotify
# at the same time.
spotify_metadata = spotdl.util.ThreadWithReturnValue(
target=self._on_spotify_for_type_query,
args=(search_query,)
)
spotify_metadata.start()
youtube_metadata = self._on_youtube_for_type_query(search_query)
metadata = spotdl.util.merge(
youtube_metadata,
spotify_metadata.join()
)
return metadata
def _on_youtube_for_type_spotify(self):
logger.debug("Extracting YouTube metadata for input Spotify URI.")
spotify_metadata = self._on_spotify_for_type_spotify(self.track)
search_query = spotdl.metadata.format_string(self.yt_search_format, spotify_metadata)
youtube_video = self._best_on_youtube_search_for_type_spotify(search_query)
youtube_metadata = self.providers["youtube"].from_url(youtube_video["url"])
return youtube_metadata
def _on_youtube_for_type_youtube(self, url):
logger.debug("Extracting YouTube metadata for input YouTube URL.")
youtube_metadata = self.providers["youtube"].from_url(url)
return youtube_metadata
def _on_youtube_for_type_query(self, query):
logger.debug("Extracting YouTube metadata for input track query.")
youtube_video = self._best_on_youtube_search_for_type_query(query)
youtube_metadata = self.providers["youtube"].from_url(youtube_video["url"])
return youtube_metadata
def _on_spotify_for_type_youtube(self, url):
logger.debug("Extracting Spotify metadata for input YouTube URL.")
youtube_metadata = self.providers["youtube"].from_url(url)
search_query = spotdl.metadata.format_string("{track-name}", youtube_metadata)
spotify_metadata = self.providers["spotify"].from_query(search_query)
return spotify_metadata
def _on_spotify_for_type_spotify(self, url):
logger.debug("Extracting Spotify metadata for input Spotify URI.")
spotify_metadata = self.providers["spotify"].from_url(url)
return spotify_metadata
def _on_spotify_for_type_query(self, query):
logger.debug("Extracting Spotify metadata for input track query.")
try:
spotify_metadata = self.providers["spotify"].from_query(query)
except SpotifyMetadataNotFoundError as e:
logger.warn(e.args[0])
spotify_metadata = {}
return spotify_metadata