A bit of thread refactoring

This commit is contained in:
Ritiek Malhotra
2020-04-12 17:09:15 +05:30
parent a253c308a6
commit 9a088ee26d
5 changed files with 69 additions and 57 deletions

View File

@@ -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",

View File

@@ -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")

View File

@@ -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,

View File

@@ -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,
) )

View File

@@ -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()