mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	A bit of thread refactoring
This commit is contained in:
		| @@ -68,31 +68,37 @@ def get_arguments(argv=None, to_merge=True): | |||||||
|     group.add_argument( |     group.add_argument( | ||||||
|         "-l", |         "-l", | ||||||
|         "--list", |         "--list", | ||||||
|         help="download tracks from a file" |         help="download tracks from a file (WARNING: this file will be modified!)" | ||||||
|     ) |     ) | ||||||
|     group.add_argument( |     group.add_argument( | ||||||
|         "-p", |         "-p", | ||||||
|         "--playlist", |         "--playlist", | ||||||
|         help="load tracks from playlist URL into <playlist_name>.txt", |         help="load tracks from playlist URL into <playlist_name>.txt or " | ||||||
|  |              "if `--write-to=<path/to/file.txt>` has been passed", | ||||||
|     ) |     ) | ||||||
|     group.add_argument( |     group.add_argument( | ||||||
|         "-b", "--album", help="load tracks from album URL into <album_name>.txt" |         "-b" | ||||||
|  |         "--album", | ||||||
|  |         help="load tracks from album URL into <album_name>.txt or if " | ||||||
|  |              "`--write-to=<path/to/file.txt>` has been passed" | ||||||
|     ) |     ) | ||||||
|     group.add_argument( |     group.add_argument( | ||||||
|         "-ab", |         "-ab", | ||||||
|         "--all-albums", |         "--all-albums", | ||||||
|         help="load all tracks from artist URL into <artist_name>.txt", |         help="load all tracks from artist URL into <artist_name>.txt " | ||||||
|  |              "or if `--write-to=<path/to/file.txt>` has been passed" | ||||||
|     ) |     ) | ||||||
|     group.add_argument( |     group.add_argument( | ||||||
|         "-u", |         "-u", | ||||||
|         "--username", |         "--username", | ||||||
|         help="load tracks from user's playlist into <playlist_name>.txt", |         help="load tracks from user's playlist into <playlist_name>.txt " | ||||||
|  |              "or if `--write-to=<path/to/file.txt>` has been passed" | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--write-m3u", |         "--write-m3u", | ||||||
|         help="generate an .m3u playlist file with youtube links given " |         help="generate an .m3u playlist file with youtube links given " | ||||||
|             "a text file containing tracks", |              "a text file containing tracks", | ||||||
|         action="store_true", |         action="store_true", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
| @@ -102,13 +108,6 @@ def get_arguments(argv=None, to_merge=True): | |||||||
|         help="choose the track to download manually from a list of matching tracks", |         help="choose the track to download manually from a list of matching tracks", | ||||||
|         action="store_true", |         action="store_true", | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |  | ||||||
|         "-nr", |  | ||||||
|         "--no-remove-original", |  | ||||||
|         default=config["no-remove-original"], |  | ||||||
|         help="do not remove the original file after conversion", |  | ||||||
|         action="store_true", |  | ||||||
|     ) |  | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "-nm", |         "-nm", | ||||||
|         "--no-metadata", |         "--no-metadata", | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
| from spotdl.metadata.providers import ProviderSpotify | from spotdl.metadata.providers import ProviderSpotify | ||||||
| from spotdl.metadata.providers import ProviderYouTube | from spotdl.metadata.providers import ProviderYouTube | ||||||
| from spotdl.metadata.embedders import EmbedderDefault | from spotdl.metadata.embedders import EmbedderDefault | ||||||
| from spotdl.encode.encoders import EncoderFFmpeg, EncoderAvconv |  | ||||||
|  | from spotdl.encode.encoders import EncoderFFmpeg | ||||||
|  | from spotdl.encode.encoders import EncoderAvconv | ||||||
|  |  | ||||||
| from spotdl.lyrics.providers import LyricWikia | from spotdl.lyrics.providers import LyricWikia | ||||||
| from spotdl.lyrics.providers import Genius | from spotdl.lyrics.providers import Genius | ||||||
|  |  | ||||||
| @@ -11,7 +14,6 @@ import spotdl.util | |||||||
|  |  | ||||||
| import os | import os | ||||||
| import urllib.request | import urllib.request | ||||||
| import threading |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def search_metadata(track, lyrics=True, search_format="{artist} - {track-name}"): | def search_metadata(track, lyrics=True, search_format="{artist} - {track-name}"): | ||||||
| @@ -46,7 +48,7 @@ def download_track(track, arguments): | |||||||
|  |  | ||||||
| def download_track_from_metadata(metadata, arguments): | def download_track_from_metadata(metadata, arguments): | ||||||
|     track = Track(metadata, cache_albumart=(not arguments.no_metadata)) |     track = Track(metadata, cache_albumart=(not arguments.no_metadata)) | ||||||
|     # log.info(log_fmt) |     print(metadata["name"]) | ||||||
|  |  | ||||||
|     stream = metadata["streams"].get( |     stream = metadata["streams"].get( | ||||||
|         quality=arguments.quality, |         quality=arguments.quality, | ||||||
| @@ -114,45 +116,41 @@ def download_tracks_from_file(path, arguments): | |||||||
|  |  | ||||||
|     # Remove duplicates and empty elements |     # Remove duplicates and empty elements | ||||||
|     # Also strip whitespaces from elements (if any) |     # Also strip whitespaces from elements (if any) | ||||||
|     spotdl.util.remove_duplicates(tracks, condition=lambda x: x, operation=str.strip) |     spotdl.util.remove_duplicates( | ||||||
|  |         tracks, | ||||||
|  |         condition=lambda x: x, | ||||||
|  |         operation=str.strip | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     # Overwrite file |     # Overwrite file | ||||||
|     with open(path, "w") as fout: |     with open(path, "w") as fout: | ||||||
|         fout.writelines(tracks) |         fout.writelines(tracks) | ||||||
|  |  | ||||||
|     next_track_metadata = threading.Thread(target=lambda: None) |  | ||||||
|     next_track_metadata.start() |  | ||||||
|     tracks_count = len(tracks) |     tracks_count = len(tracks) | ||||||
|     current_iteration = 1 |     current_iteration = 1 | ||||||
|  |  | ||||||
|     def mutable_assignment(mutable_resource, track): |     next_track = tracks.pop(0) | ||||||
|         mutable_resource["next_track"] = search_metadata( |  | ||||||
|             track, |  | ||||||
|             search_format=arguments.search_format |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     metadata = { |     metadata = { | ||||||
|         "current_track": None, |         "current_track": None, | ||||||
|         "next_track": None, |         "next_track": spotdl.util.ThreadWithReturnValue( | ||||||
|  |             target=search_metadata, | ||||||
|  |             args=(next_track, True, arguments.search_format) | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
|  |     metadata["next_track"].start() | ||||||
|     while tracks_count > 0: |     while tracks_count > 0: | ||||||
|         current_track = tracks.pop(0) |  | ||||||
|         tracks_count -= 1 |         tracks_count -= 1 | ||||||
|         metadata["current_track"] = metadata["next_track"] |         metadata["current_track"] = metadata["next_track"].join() | ||||||
|         metadata["next_track"] = None |         metadata["next_track"] = None | ||||||
|         try: |         try: | ||||||
|             if metadata["current_track"] is None: |  | ||||||
|                 metadata["current_track"] = search_metadata( |  | ||||||
|                     current_track, |  | ||||||
|                     search_format=arguments.search_format |  | ||||||
|                 ) |  | ||||||
|             if tracks_count > 0: |             if tracks_count > 0: | ||||||
|                 next_track = tracks[0] |                 current_track = next_track | ||||||
|                 next_track_metadata = threading.Thread( |                 next_track = tracks.pop(0) | ||||||
|                     target=mutable_assignment, |                 metadata["next_track"] = spotdl.util.ThreadWithReturnValue( | ||||||
|                     args=(metadata, next_track) |                     target=search_metadata, | ||||||
|  |                     args=(next_track, True, arguments.search_format) | ||||||
|                 ) |                 ) | ||||||
|                 next_track_metadata.start() |                 metadata["next_track"].start() | ||||||
|  |  | ||||||
|             log_fmt=(str(current_iteration) + ". {artist} - {track-name}") |             log_fmt=(str(current_iteration) + ". {artist} - {track-name}") | ||||||
|             # log.info(log_fmt) |             # log.info(log_fmt) | ||||||
| @@ -161,7 +159,6 @@ def download_tracks_from_file(path, arguments): | |||||||
|                 arguments |                 arguments | ||||||
|             ) |             ) | ||||||
|             current_iteration += 1 |             current_iteration += 1 | ||||||
|             next_track_metadata.join() |  | ||||||
|         except (urllib.request.URLError, TypeError, IOError) as e: |         except (urllib.request.URLError, TypeError, IOError) as e: | ||||||
|             # log.exception(e.args[0]) |             # log.exception(e.args[0]) | ||||||
|             # log.warning("Failed. Will retry after other songs\n") |             # log.warning("Failed. Will retry after other songs\n") | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import spotdl.util | |||||||
|  |  | ||||||
| DEFAULT_CONFIGURATION = { | DEFAULT_CONFIGURATION = { | ||||||
|     "spotify-downloader": { |     "spotify-downloader": { | ||||||
|         "no-remove-original": False, |  | ||||||
|         "manual": False, |         "manual": False, | ||||||
|         "no-metadata": False, |         "no-metadata": False, | ||||||
|         "no-fallback-metadata": False, |         "no-fallback-metadata": False, | ||||||
|   | |||||||
| @@ -2,11 +2,12 @@ import tqdm | |||||||
|  |  | ||||||
| import urllib.request | import urllib.request | ||||||
| import subprocess | import subprocess | ||||||
| import threading |  | ||||||
|  |  | ||||||
| from spotdl.encode.encoders import EncoderFFmpeg | from spotdl.encode.encoders import EncoderFFmpeg | ||||||
| from spotdl.metadata.embedders import EmbedderDefault | from spotdl.metadata.embedders import EmbedderDefault | ||||||
|  |  | ||||||
|  | import spotdl.util | ||||||
|  |  | ||||||
| CHUNK_SIZE= 16 * 1024 | CHUNK_SIZE= 16 * 1024 | ||||||
|  |  | ||||||
| class Track: | class Track: | ||||||
| @@ -14,25 +15,18 @@ class Track: | |||||||
|         self.metadata = metadata |         self.metadata = metadata | ||||||
|         self._chunksize = CHUNK_SIZE |         self._chunksize = CHUNK_SIZE | ||||||
|  |  | ||||||
|         self._cache_resources = { |  | ||||||
|             "albumart": {"content": None, "threadinstance": None } |  | ||||||
|         } |  | ||||||
|         if cache_albumart: |         if cache_albumart: | ||||||
|             self._albumart_thread = self._cache_albumart() |             self._albumart_thread = self._cache_albumart() | ||||||
|  |  | ||||||
|     def _fetch_response_content_threaded(self, mutable_resource, url): |         self._cache_albumart = cache_albumart | ||||||
|         content = urllib.request.urlopen(url).read() |  | ||||||
|         mutable_resource["content"] = content |  | ||||||
|  |  | ||||||
|     def _cache_albumart(self): |     def _cache_albumart(self): | ||||||
|         # A hack to get a thread's return value |         albumart_thread = spotdl.util.ThreadWithReturnValue( | ||||||
|         albumart_thread = threading.Thread( |             target=lambda url: urllib.request.urlopen(url).read(), | ||||||
|             target=self._fetch_response_content_threaded, |             args=(self.metadata["album"]["images"][0]["url"],) | ||||||
|             args=(self._cache_resources["albumart"], |  | ||||||
|                   self.metadata["album"]["images"][0]["url"]), |  | ||||||
|         ) |         ) | ||||||
|         albumart_thread.start() |         albumart_thread.start() | ||||||
|         self._cache_resources["albumart"]["threadinstance"] = albumart_thread |         return albumart_thread | ||||||
|  |  | ||||||
|     def _calculate_total_chunks(self, filesize): |     def _calculate_total_chunks(self, filesize): | ||||||
|         return (filesize // self._chunksize) + 1 |         return (filesize // self._chunksize) + 1 | ||||||
| @@ -79,14 +73,15 @@ class Track: | |||||||
|         process.wait() |         process.wait() | ||||||
|  |  | ||||||
|     def apply_metadata(self, input_path, encoding=None, embedder=EmbedderDefault()): |     def apply_metadata(self, input_path, encoding=None, embedder=EmbedderDefault()): | ||||||
|         albumart = self._cache_resources["albumart"] |         if self._cache_albumart: | ||||||
|         if albumart["threadinstance"]: |             albumart = self._albumart_thread.join() | ||||||
|             albumart["threadinstance"].join() |         else: | ||||||
|  |             albumart = None | ||||||
|  |  | ||||||
|         embedder.apply_metadata( |         embedder.apply_metadata( | ||||||
|             input_path, |             input_path, | ||||||
|             self.metadata, |             self.metadata, | ||||||
|             cached_albumart=albumart["content"], |             cached_albumart=albumart, | ||||||
|             encoding=encoding, |             encoding=encoding, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ import sys | |||||||
| import math | import math | ||||||
| import urllib.request | import urllib.request | ||||||
|  |  | ||||||
|  | import threading | ||||||
|  |  | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     import winreg |     import winreg | ||||||
| @@ -18,6 +20,26 @@ except ImportError: | |||||||
|     sys.exit(5) |     sys.exit(5) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # This has been referred from | ||||||
|  | # https://stackoverflow.com/a/6894023/6554943 | ||||||
|  | # It's because threaded functions do not return by default | ||||||
|  | # Whereas this will return the value when `join` method | ||||||
|  | # is called. | ||||||
|  | class ThreadWithReturnValue(threading.Thread): | ||||||
|  |     def __init__(self, target=lambda: None, args=()): | ||||||
|  |         super().__init__(target=target, args=args) | ||||||
|  |         self._return = None | ||||||
|  |     def run(self): | ||||||
|  |         if self._target is not None: | ||||||
|  |             self._return = self._target( | ||||||
|  |                 *self._args, | ||||||
|  |                 **self._kwargs | ||||||
|  |             ) | ||||||
|  |     def join(self, *args): | ||||||
|  |         super().join(*args) | ||||||
|  |         return self._return | ||||||
|  |  | ||||||
|  |  | ||||||
| def merge(base, overrider): | def merge(base, overrider): | ||||||
|     """ Override default dict with config dict. """ |     """ Override default dict with config dict. """ | ||||||
|     merger = base.copy() |     merger = base.copy() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user