mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			261 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			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_copy(
 | |
|             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_copy(
 | |
|             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_copy(
 | |
|             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
 | |
| 
 |