mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2025-12-08 20:39:08 +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