mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2025-12-08 20:39:08 +00:00
Custom YouTube search string (#261)
* Custom YouTube search string * Fix sorting issues on < Python 3.6
This commit is contained in:
14
README.md
14
README.md
@@ -90,9 +90,9 @@ but make sure `$ python -V` gives you a `Python 3.x.x`!
|
||||
```
|
||||
usage: spotdl.py [-h]
|
||||
(-s SONG | -l LIST | -p PLAYLIST | -b ALBUM | -u USERNAME)
|
||||
[-m] [-nm] [-a] [-f FOLDER] [--overwrite {skip,force,prompt}]
|
||||
[-i {.webm,.m4a}] [-o OUTPUT_EXT] [-ff] [-dm] [-d] [-mo]
|
||||
[-ns] [-ll {INFO,WARNING,ERROR,DEBUG}] [-c CONFIG]
|
||||
[-m] [-nm] [-a] [-f FOLDER] [--overwrite {force,skip,prompt}]
|
||||
[-i {.webm,.m4a}] [-o OUTPUT_EXT] [-ff] [-sf] [-dm] [-d]
|
||||
[-mo] [-ns] [-ll {INFO,WARNING,ERROR,DEBUG}] [-c CONFIG]
|
||||
|
||||
Download and convert songs from Spotify, Youtube etc.
|
||||
|
||||
@@ -116,7 +116,7 @@ optional arguments:
|
||||
-f FOLDER, --folder FOLDER
|
||||
path to folder where files will be stored in (default:
|
||||
Music)
|
||||
--overwrite {skip,force,prompt}
|
||||
--overwrite {force,skip,prompt}
|
||||
change the overwrite policy (default: prompt)
|
||||
-i {.webm,.m4a}, --input-ext {.webm,.m4a}
|
||||
prefered input format .m4a or .webm (Opus) (default:
|
||||
@@ -130,6 +130,12 @@ optional arguments:
|
||||
'genre', 'disc_number', 'duration', 'year',
|
||||
'original_date', 'track_number', 'total_tracks',
|
||||
'isrc'] (default: {artist} - {track_name})
|
||||
-sf, --search-format Search format to search for on YouTube, each tag is
|
||||
surrounded by curly braces. Possible formats:
|
||||
['track_name', 'artist', 'album', 'album_artist',
|
||||
'genre', 'disc_number', 'duration', 'year',
|
||||
'original_date', 'track_number', 'total_tracks',
|
||||
'isrc'] (default: {artist} - {track_name} lyrics)
|
||||
-dm, --download-only-metadata
|
||||
download songs for which metadata is found (default:
|
||||
False)
|
||||
|
||||
@@ -23,6 +23,7 @@ default_conf = { 'spotify-downloader':
|
||||
'music-videos-only' : False,
|
||||
'no-spaces' : False,
|
||||
'file-format' : '{artist} - {track_name}',
|
||||
'search-format' : '{artist} - {track_name} lyrics',
|
||||
'youtube-api-key' : None,
|
||||
'log-level' : 'INFO' }
|
||||
}
|
||||
@@ -71,7 +72,8 @@ def override_config(config_file, parser, raw_args=None):
|
||||
parser.set_defaults(music_videos_only=config['music-videos-only'])
|
||||
parser.set_defaults(no_spaces=config['no-spaces'])
|
||||
parser.set_defaults(file_format=config['file-format'])
|
||||
parser.set_defaults(no_spaces=config['youtube-api-key'])
|
||||
parser.set_defaults(search_format=config['search-format'])
|
||||
parser.set_defaults(youtube_api_key=config['youtube-api-key'])
|
||||
parser.set_defaults(log_level=config['log-level'])
|
||||
|
||||
return parser.parse_args(raw_args)
|
||||
@@ -131,8 +133,12 @@ def get_arguments(raw_args=None, to_group=True, to_merge=True):
|
||||
'-ff', '--file-format', default=config['file-format'],
|
||||
help='File format to save the downloaded song with, each tag '
|
||||
'is surrounded by curly braces. Possible formats: '
|
||||
'{}'.format([internals.formats[x] for x in internals.formats]),
|
||||
action='store_true')
|
||||
'{}'.format([internals.formats[x] for x in internals.formats]))
|
||||
parser.add_argument(
|
||||
'-sf', '--search-format', default=config['search-format'],
|
||||
help='Search format to search for on YouTube, each tag '
|
||||
'is surrounded by curly braces. Possible formats: '
|
||||
'{}'.format([internals.formats[x] for x in internals.formats]))
|
||||
parser.add_argument(
|
||||
'-dm', '--download-only-metadata', default=config['download-only-metadata'],
|
||||
help='download songs for which metadata is found',
|
||||
|
||||
@@ -66,7 +66,7 @@ def is_youtube(raw_song):
|
||||
return status
|
||||
|
||||
|
||||
def generate_songname(file_format, tags):
|
||||
def format_string(string_format, tags):
|
||||
""" Generate a string of the format '[artist] - [song]' for the given spotify song. """
|
||||
format_tags = dict(formats)
|
||||
format_tags[0] = tags['name']
|
||||
@@ -83,13 +83,13 @@ def generate_songname(file_format, tags):
|
||||
format_tags[11] = tags['external_ids']['isrc']
|
||||
|
||||
for x in formats:
|
||||
file_format = file_format.replace('{' + formats[x] + '}',
|
||||
string_format = string_format.replace('{' + formats[x] + '}',
|
||||
str(format_tags[x]))
|
||||
|
||||
if const.args.no_spaces:
|
||||
file_format = file_format.replace(' ', '_')
|
||||
string_format = string_format.replace(' ', '_')
|
||||
|
||||
return file_format
|
||||
return string_format
|
||||
|
||||
|
||||
def sanitize_title(title):
|
||||
|
||||
@@ -68,12 +68,12 @@ def download_song(file_name, content):
|
||||
return False
|
||||
|
||||
|
||||
def generate_search_url(song):
|
||||
def generate_search_url(query):
|
||||
""" Generate YouTube search URL for the given song. """
|
||||
# urllib.request.quote() encodes URL with special characters
|
||||
song = urllib.request.quote(song)
|
||||
# urllib.request.quote() encodes string with special characters
|
||||
quoted_query = urllib.request.quote(query)
|
||||
# Special YouTube URL filter to search only for videos
|
||||
url = 'https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q={0}'.format(song)
|
||||
url = 'https://www.youtube.com/results?sp=EgIQAQ%253D%253D&q={0}'.format(quoted_query)
|
||||
return url
|
||||
|
||||
|
||||
@@ -109,6 +109,12 @@ class GenerateYouTubeURL:
|
||||
self.raw_song = raw_song
|
||||
self.meta_tags = meta_tags
|
||||
|
||||
if meta_tags is None:
|
||||
self.search_query = raw_song
|
||||
else:
|
||||
self.search_query = internals.format_string(const.args.search_format,
|
||||
meta_tags)
|
||||
|
||||
def _best_match(self, videos):
|
||||
""" Select the best matching video from a list of videos. """
|
||||
if const.args.manual:
|
||||
@@ -161,13 +167,7 @@ class GenerateYouTubeURL:
|
||||
log.debug('No tries left. I quit.')
|
||||
return
|
||||
|
||||
if self.meta_tags is None:
|
||||
song = self.raw_song
|
||||
search_url = generate_search_url(song)
|
||||
else:
|
||||
song = internals.generate_songname(const.args.file_format,
|
||||
self.meta_tags)
|
||||
search_url = generate_search_url(song)
|
||||
search_url = generate_search_url(self.search_query)
|
||||
log.debug('Opening URL: {0}'.format(search_url))
|
||||
|
||||
item = urllib.request.urlopen(search_url).read()
|
||||
@@ -212,9 +212,7 @@ class GenerateYouTubeURL:
|
||||
song = self.raw_song
|
||||
query['q'] = song
|
||||
else:
|
||||
song = '{0} - {1}'.format(self.meta_tags['artists'][0]['name'],
|
||||
self.meta_tags['name'])
|
||||
query['q'] = song
|
||||
query['q'] = self.search_query
|
||||
log.debug('query: {0}'.format(query))
|
||||
|
||||
data = pafy.call_gdata('search', query)
|
||||
|
||||
@@ -132,7 +132,7 @@ def download_single(raw_song, number=None):
|
||||
songname = content.title
|
||||
|
||||
if meta_tags is not None:
|
||||
refined_songname = internals.generate_songname(const.args.file_format, meta_tags)
|
||||
refined_songname = internals.format_string(const.args.file_format, meta_tags)
|
||||
log.debug('Refining songname from "{0}" to "{1}"'.format(songname, refined_songname))
|
||||
if not refined_songname == ' - ':
|
||||
songname = refined_songname
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import yaml
|
||||
|
||||
from core import handle
|
||||
from core import const
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
|
||||
def test_log_str_to_int():
|
||||
@@ -21,11 +25,23 @@ class TestConfig:
|
||||
assert config == expect_config
|
||||
|
||||
def test_modified_config(self):
|
||||
default_config = handle.default_conf['spotify-downloader']
|
||||
modified_config = dict(default_config)
|
||||
modified_config['file-format'] = 'just_a_test'
|
||||
config = handle.merge(default_config, modified_config)
|
||||
assert config['file-format'] == modified_config['file-format']
|
||||
global modified_config
|
||||
modified_config = dict(handle.default_conf)
|
||||
modified_config['spotify-downloader']['file-format'] = 'just_a_test'
|
||||
merged_config = handle.merge(handle.default_conf, modified_config)
|
||||
assert merged_config == modified_config
|
||||
|
||||
def test_custom_config_path(self, tmpdir):
|
||||
parser = argparse.ArgumentParser()
|
||||
with open(config_path, 'w') as config_file:
|
||||
yaml.dump(modified_config, config_file, default_flow_style=False)
|
||||
overridden_config = handle.override_config(config_path,
|
||||
parser,
|
||||
raw_args='')
|
||||
modified_values = [ str(value) for value in modified_config['spotify-downloader'].values() ]
|
||||
overridden_config.folder = os.path.realpath(overridden_config.folder)
|
||||
overridden_values = [ str(value) for value in overridden_config.__dict__.values() ]
|
||||
assert sorted(overridden_values) == sorted(modified_values)
|
||||
|
||||
|
||||
def test_grouped_arguments(tmpdir):
|
||||
|
||||
@@ -25,13 +25,13 @@ def test_metadata():
|
||||
class TestFileFormat:
|
||||
def test_with_spaces(self):
|
||||
expect_title = 'David André Østby - Intro'
|
||||
title = internals.generate_songname(const.args.file_format, meta_tags)
|
||||
title = internals.format_string(const.args.file_format, meta_tags)
|
||||
assert title == expect_title
|
||||
|
||||
def test_without_spaces(self):
|
||||
expect_title = 'David_André_Østby_-_Intro'
|
||||
const.args.no_spaces = True
|
||||
title = internals.generate_songname(const.args.file_format, meta_tags)
|
||||
title = internals.format_string(const.args.file_format, meta_tags)
|
||||
assert title == expect_title
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_check_track_exists_before_download(tmpdir):
|
||||
expect_check = False
|
||||
const.args.folder = str(tmpdir)
|
||||
# prerequisites for determining filename
|
||||
songname = internals.generate_songname(const.args.file_format, meta_tags)
|
||||
songname = internals.format_string(const.args.file_format, meta_tags)
|
||||
global file_name
|
||||
file_name = internals.sanitize_title(songname)
|
||||
check = spotdl.check_exists(file_name, raw_song, meta_tags)
|
||||
|
||||
Reference in New Issue
Block a user