mirror of
				https://github.com/KevinMidboe/spotify-downloader.git
				synced 2025-10-29 18:00:15 +00:00 
			
		
		
		
	Add logging capability (#175)
* Refactoring and addition of logzero (#172) * Refactored convert.py and added logging. * Added logging and refactored. * Added logzero to requirements.txt * Added logging to metadata.py * Created a log in misc.py. Updated slugify import. * Some general improvement * Improve message layout * Improve test mechanism * Implement debug level logging * Fix some minor mistakes * Make pytest happy * Remove unimplemented --verbose option * Update ISSUE_TEMPLATE.md * Rename LICENSE * Remove obvious from log.debug() * Show track URL when writing to file (debug)
This commit is contained in:
		| @@ -10,22 +10,23 @@ Please follow the guide below | ||||
| - [ ] [Searched](https://github.com/ritiek/spotify-downloader/issues?utf8=%E2%9C%93&q=is%3Aissue) for similar issues including closed ones | ||||
|  | ||||
| #### What is the purpose of your *issue*? | ||||
| - [ ] Script won't run | ||||
| - [ ] Encountered bug | ||||
| - [ ] Feature request | ||||
| - [ ] Bug | ||||
| - [ ] Feature Request | ||||
| - [ ] Question | ||||
| - [ ] Other | ||||
|  | ||||
| #### System information | ||||
| - Your `python` version: `python 3.x` | ||||
| - Your operating system: `Ubuntu 16.04` | ||||
|  | ||||
| ### Description | ||||
| <!-- Provide as much information possible with relevant examples and whatever you have tried below --> | ||||
|  | ||||
| <!-- Provide as much information possible and whatever you have tried below --> | ||||
|  | ||||
|  | ||||
| ### Log | ||||
| <!-- Run the script with `--log-level=DEBUG` and paste the output below within the code block--> | ||||
| <details> | ||||
|  | ||||
| ``` | ||||
| paste the output over here | ||||
| ``` | ||||
|  | ||||
| </details> | ||||
|  | ||||
| <!-- Give your issue a relevant title and you are good to go --> | ||||
|   | ||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @@ -17,7 +17,7 @@ | ||||
|   - Track number | ||||
|   - Disc number | ||||
|   - Release date | ||||
|   - And some more... | ||||
|   - And more... | ||||
|  | ||||
| - Works straight out of the box and does not require to generate or mess with your API keys. | ||||
|  | ||||
| @@ -78,8 +78,10 @@ Assuming you have Python 3 ([preferably v3.6 or above to stay away from Unicode | ||||
| - For all available options, run `python3 spotdl.py --help`. | ||||
|  | ||||
| ``` | ||||
| usage: spotdl.py [-h] (-s SONG | -l LIST | -p PLAYLIST | -u USERNAME) [-m] | ||||
|                  [-nm] [-a] [-f FOLDER] [-v] [-i INPUT_EXT] [-o OUTPUT_EXT] | ||||
| usage: spotdl.py [-h] | ||||
|                  (-s SONG | -l LIST | -p PLAYLIST | -b ALBUM | -u USERNAME) | ||||
|                  [-m] [-nm] [-a] [-f FOLDER] [-v] [-i INPUT_EXT] | ||||
|                  [-o OUTPUT_EXT] [-ll {INFO,WARNING,ERROR,DEBUG}] | ||||
|  | ||||
| Download and convert songs from Spotify, Youtube etc. | ||||
|  | ||||
| @@ -103,13 +105,15 @@ optional arguments: | ||||
|   -f FOLDER, --folder FOLDER | ||||
|                         path to folder where files will be stored in (default: | ||||
|                         Music/) | ||||
|   -v, --verbose         show debug output (default: False) | ||||
|   -i INPUT_EXT, --input_ext INPUT_EXT | ||||
|   -i INPUT_EXT, --input-ext INPUT_EXT | ||||
|                         prefered input format .m4a or .webm (Opus) (default: | ||||
|                         .m4a) | ||||
|   -o OUTPUT_EXT, --output_ext OUTPUT_EXT | ||||
|   -o OUTPUT_EXT, --output-ext OUTPUT_EXT | ||||
|                         prefered output extension .mp3 or .m4a (AAC) (default: | ||||
|                         .mp3) | ||||
|   -ll {INFO,WARNING,ERROR,DEBUG}, --log-level {INFO,WARNING,ERROR,DEBUG} | ||||
|                         possible values - ['INFO', 'WARNING', 'ERROR', | ||||
|                         'DEBUG'] (default: INFO) | ||||
| ``` | ||||
|  | ||||
| #### Download by Name | ||||
| @@ -204,11 +208,12 @@ Beside some other characters, spaces will be replaced by underscores. There's no | ||||
|  | ||||
| Just make sure your working directory is the one you have the music files in. | ||||
|  | ||||
| ## Return codes | ||||
| ## Exit codes | ||||
|  | ||||
| - `0` - Success | ||||
| - `1` - Unknown error | ||||
| - `2` - Command line error (e.g. invalid args) | ||||
| - `-1` - KeyboardInterrupt | ||||
| - `10` - Invalid playlist URL | ||||
| - `11` - Playlist not found | ||||
|  | ||||
|   | ||||
| @@ -1,50 +1,54 @@ | ||||
| import subprocess | ||||
| import os | ||||
| from core.logger import log | ||||
|  | ||||
|  | ||||
| """ | ||||
| What are the differences and similarities between ffmpeg, libav, and avconv? | ||||
| """What are the differences and similarities between ffmpeg, libav, and avconv? | ||||
| https://stackoverflow.com/questions/9477115 | ||||
|  | ||||
| ffmeg encoders high to lower quality | ||||
| libopus > libvorbis >= libfdk_aac > aac > libmp3lame | ||||
|  | ||||
| libfdk_aac due to copyrights needs to be compiled by end user | ||||
| on MacOS brew install ffmpeg --with-fdk-aac will do just that. Other OS? | ||||
| https://trac.ffmpeg.org/wiki/Encode/AAC | ||||
| """ | ||||
|  | ||||
| def song(input_song, output_song, folder, avconv=False, verbose=False): | ||||
|     """Do the audio format conversion.""" | ||||
|  | ||||
| def song(input_song, output_song, folder, avconv=False): | ||||
|     """ Do the audio format conversion. """ | ||||
|     if not input_song == output_song: | ||||
|         print('Converting {0} to {1}'.format( | ||||
|         log.info('Converting {0} to {1}'.format( | ||||
|             input_song, output_song.split('.')[-1])) | ||||
|         if avconv: | ||||
|             exit_code = convert_with_avconv(input_song, output_song, folder, verbose) | ||||
|             exit_code = convert_with_avconv(input_song, output_song, folder) | ||||
|         else: | ||||
|             exit_code = convert_with_ffmpeg(input_song, output_song, folder, verbose) | ||||
|             exit_code = convert_with_ffmpeg(input_song, output_song, folder) | ||||
|         return exit_code | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def convert_with_avconv(input_song, output_song, folder, verbose): | ||||
|     """Convert the audio file using avconv.""" | ||||
|     if verbose: | ||||
| def convert_with_avconv(input_song, output_song, folder): | ||||
|     """ Convert the audio file using avconv. """ | ||||
|     if log.level == 10: | ||||
|         level = 'debug' | ||||
|     else: | ||||
|         level = '0' | ||||
|  | ||||
|     command = ['avconv', | ||||
|                '-loglevel', level, | ||||
|                '-i',        os.path.join(folder, input_song), | ||||
|                '-ab',       '192k', | ||||
|     command = ['avconv', '-loglevel', level, '-i', | ||||
|                os.path.join(folder, input_song), '-ab', '192k', | ||||
|                os.path.join(folder, output_song)] | ||||
|  | ||||
|     log.debug(command) | ||||
|  | ||||
|     return subprocess.call(command) | ||||
|  | ||||
|  | ||||
| def convert_with_ffmpeg(input_song, output_song, folder, verbose): | ||||
|     """Convert the audio file using FFmpeg.""" | ||||
| def convert_with_ffmpeg(input_song, output_song, folder): | ||||
|     """ Convert the audio file using FFmpeg. """ | ||||
|     ffmpeg_pre = 'ffmpeg -y ' | ||||
|     if not verbose: | ||||
|  | ||||
|     if not log.level == 10: | ||||
|         ffmpeg_pre += '-hide_banner -nostats -v panic ' | ||||
|  | ||||
|     input_ext = input_song.split('.')[-1] | ||||
| @@ -63,6 +67,9 @@ def convert_with_ffmpeg(input_song, output_song, folder, verbose): | ||||
|             ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 192k -vn ' | ||||
|  | ||||
|     command = '{0}-i {1} {2}{3}'.format( | ||||
|         ffmpeg_pre, os.path.join(folder, input_song), ffmpeg_params, os.path.join(folder, output_song)).split(' ') | ||||
|         ffmpeg_pre, os.path.join(folder, input_song), | ||||
|         ffmpeg_params, os.path.join(folder, output_song)).split(' ') | ||||
|  | ||||
|     log.debug(command) | ||||
|  | ||||
|     return subprocess.call(command) | ||||
|   | ||||
| @@ -1,28 +1,31 @@ | ||||
| import sys | ||||
| import os | ||||
| import argparse | ||||
| import spotipy.oauth2 as oauth2 | ||||
| from urllib.request import quote | ||||
| from slugify import slugify | ||||
| from slugify import SLUG_OK, slugify | ||||
| 
 | ||||
| import sys | ||||
| import os | ||||
| from core.logger import log, log_leveller, _LOG_LEVELS_STR | ||||
| 
 | ||||
| 
 | ||||
| def input_link(links): | ||||
|     """Let the user input a number.""" | ||||
|     """ Let the user input a choice. """ | ||||
|     while True: | ||||
|         try: | ||||
|             the_chosen_one = int(input('>> Choose your number: ')) | ||||
|             log.info('Choose your number:') | ||||
|             the_chosen_one = int(input('> ')) | ||||
|             if 1 <= the_chosen_one <= len(links): | ||||
|                 return links[the_chosen_one - 1] | ||||
|             elif the_chosen_one == 0: | ||||
|                 return None | ||||
|             else: | ||||
|                 print('Choose a valid number!') | ||||
|                 log.warning('Choose a valid number!') | ||||
|         except ValueError: | ||||
|             print('Choose a valid number!') | ||||
|             log.warning('Choose a valid number!') | ||||
| 
 | ||||
| 
 | ||||
| def trim_song(file): | ||||
|     """Remove the first song from file.""" | ||||
|     """ Remove the first song from file. """ | ||||
|     with open(file, 'r') as file_in: | ||||
|         data = file_in.read().splitlines(True) | ||||
|     with open(file, 'w') as file_out: | ||||
| @@ -60,26 +63,32 @@ def get_arguments(): | ||||
|         '-f', '--folder', default=(os.path.join(sys.path[0], 'Music')), | ||||
|         help='path to folder where files will be stored in') | ||||
|     parser.add_argument( | ||||
|         '-v', '--verbose', default=False, help='show debug output', | ||||
|         action='store_true') | ||||
|     parser.add_argument( | ||||
|         '-i', '--input_ext', default='.m4a', | ||||
|         '-i', '--input-ext', default='.m4a', | ||||
|         help='prefered input format .m4a or .webm (Opus)') | ||||
|     parser.add_argument( | ||||
|         '-o', '--output_ext', default='.mp3', | ||||
|         '-o', '--output-ext', default='.mp3', | ||||
|         help='prefered output extension .mp3 or .m4a (AAC)') | ||||
|     parser.add_argument( | ||||
|         '-ll', '--log-level', default='INFO', | ||||
|         choices=_LOG_LEVELS_STR, | ||||
|         type=str.upper, | ||||
|         help='possible values - {}'.format(_LOG_LEVELS_STR)) | ||||
| 
 | ||||
|     return parser.parse_args() | ||||
|     parsed = parser.parse_args() | ||||
|     parsed.log_level = log_leveller(parsed.log_level) | ||||
| 
 | ||||
|     return parsed | ||||
| 
 | ||||
| 
 | ||||
| def is_spotify(raw_song): | ||||
|     """Check if the input song is a Spotify link.""" | ||||
|     """ Check if the input song is a Spotify link. """ | ||||
|     status = len(raw_song) == 22 and raw_song.replace(" ", "%20") == raw_song | ||||
|     status = status or raw_song.find('spotify') > -1 | ||||
|     return status | ||||
| 
 | ||||
| 
 | ||||
| def is_youtube(raw_song): | ||||
|     """Check if the input song is a YouTube link.""" | ||||
|     """ Check if the input song is a YouTube link. """ | ||||
|     status = len(raw_song) == 11 and raw_song.replace(" ", "%20") == raw_song | ||||
|     status = status and not raw_song.lower() == raw_song | ||||
|     status = status or 'youtube.com/watch?v=' in raw_song | ||||
| @@ -87,7 +96,7 @@ def is_youtube(raw_song): | ||||
| 
 | ||||
| 
 | ||||
| def sanitize_title(title): | ||||
|     """Generate filename of the song to be downloaded.""" | ||||
|     """ Generate filename of the song to be downloaded. """ | ||||
|     title = title.replace(' ', '_') | ||||
|     title = title.replace('/', '_') | ||||
| 
 | ||||
| @@ -97,7 +106,7 @@ def sanitize_title(title): | ||||
| 
 | ||||
| 
 | ||||
| def generate_token(): | ||||
|     """Generate the token. Please respect these credentials :)""" | ||||
|     """ Generate the token. Please respect these credentials :) """ | ||||
|     credentials = oauth2.SpotifyClientCredentials( | ||||
|         client_id='4fe3fecfe5334023a1472516cc99d805', | ||||
|         client_secret='0f02b7c483c04257984695007a4a8d5c') | ||||
| @@ -106,7 +115,7 @@ def generate_token(): | ||||
| 
 | ||||
| 
 | ||||
| def generate_search_url(song, viewsort=False): | ||||
|     """Generate YouTube search URL for the given song.""" | ||||
|     """ Generate YouTube search URL for the given song. """ | ||||
|     # urllib.request.quote() encodes URL with special characters | ||||
|     song = quote(song) | ||||
|     if viewsort: | ||||
| @@ -125,15 +134,11 @@ def filter_path(path): | ||||
|             os.remove(os.path.join(path, temp)) | ||||
| 
 | ||||
| 
 | ||||
| def grace_quit(): | ||||
|     print('\n\nExiting.') | ||||
|     sys.exit(0) | ||||
| 
 | ||||
| def get_sec(time_str): | ||||
|     v = time_str.split(':', 3) | ||||
|     v.reverse() | ||||
|     sec = 0 | ||||
|    if len(v) > 0: #seconds | ||||
|     if len(v) > 0:  # seconds | ||||
|         sec += int(v[0]) | ||||
|     if len(v) > 1:  # minutes | ||||
|         sec += int(v[1]) * 60 | ||||
							
								
								
									
										16
									
								
								core/logger.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								core/logger.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import logzero | ||||
| import logging | ||||
|  | ||||
| _LOG_LEVELS_STR = ['INFO', 'WARNING', 'ERROR', 'DEBUG'] | ||||
|  | ||||
| def log_leveller(log_level_str): | ||||
|     loggin_levels = [logging.INFO, logging.WARNING, logging.ERROR, logging.DEBUG] | ||||
|     log_level_str_index = _LOG_LEVELS_STR.index(log_level_str) | ||||
|     loggin_level = loggin_levels[log_level_str_index] | ||||
|     return loggin_level | ||||
|  | ||||
|  | ||||
| # Create a logger | ||||
| log_format = ("%(color)s%(levelname)s:%(end_color)s %(message)s") | ||||
| formatter = logzero.LogFormatter(fmt=log_format) | ||||
| log = logzero.setup_logger(formatter=formatter, level=logging.INFO) | ||||
| @@ -1,6 +1,7 @@ | ||||
| from mutagen.easyid3 import EasyID3 | ||||
| from mutagen.id3 import ID3, APIC | ||||
| from mutagen.mp4 import MP4, MP4Cover | ||||
| from core.logger import log | ||||
|  | ||||
| import urllib.request | ||||
|  | ||||
| @@ -24,23 +25,23 @@ def compare(music_file, metadata): | ||||
|  | ||||
|  | ||||
| def embed(music_file, meta_tags): | ||||
|     """Embed metadata.""" | ||||
|     """ Embed metadata. """ | ||||
|     if meta_tags is None: | ||||
|         print('Could not find meta-tags') | ||||
|         log.warning('Could not find metadata') | ||||
|         return None | ||||
|     elif music_file.endswith('.m4a'): | ||||
|         print('Fixing meta-tags') | ||||
|         log.info('Applying metadata') | ||||
|         return embed_m4a(music_file, meta_tags) | ||||
|     elif music_file.endswith('.mp3'): | ||||
|         print('Fixing meta-tags') | ||||
|         log.info('Applying metadata') | ||||
|         return embed_mp3(music_file, meta_tags) | ||||
|     else: | ||||
|         print('Cannot embed meta-tags into given output extension') | ||||
|         log.warning('Cannot embed metadata into given output extension') | ||||
|         return False | ||||
|  | ||||
|  | ||||
| def embed_mp3(music_file, meta_tags): | ||||
|     """Embed metadata to MP3 files.""" | ||||
|     """ Embed metadata to MP3 files. """ | ||||
|     # EasyID3 is fun to use ;) | ||||
|     audiofile = EasyID3(music_file) | ||||
|     audiofile['artist'] = meta_tags['artists'][0]['name'] | ||||
| @@ -81,7 +82,7 @@ def embed_mp3(music_file, meta_tags): | ||||
|  | ||||
|  | ||||
| def embed_m4a(music_file, meta_tags): | ||||
|     """Embed metadata to M4A files.""" | ||||
|     """ Embed metadata to M4A files. """ | ||||
|     # Apple has specific tags - see mutagen docs - | ||||
|     # http://mutagen.readthedocs.io/en/latest/api/mp4.html | ||||
|     tags = {'album': '\xa9alb', | ||||
|   | ||||
| @@ -6,3 +6,4 @@ spotipy >= 2.4.4 | ||||
| mutagen >= 1.37 | ||||
| unicode-slugify >= 0.1.3 | ||||
| titlecase >= 0.10.0 | ||||
| logzero >= 1.3.1 | ||||
							
								
								
									
										249
									
								
								spotdl.py
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								spotdl.py
									
									
									
									
									
								
							| @@ -1,9 +1,10 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: UTF-8 -*- | ||||
|  | ||||
| from core import logger | ||||
| from core import metadata | ||||
| from core import convert | ||||
| from core import misc | ||||
| from core import internals | ||||
| from bs4 import BeautifulSoup | ||||
| from titlecase import titlecase | ||||
| from slugify import slugify | ||||
| @@ -13,21 +14,26 @@ import urllib.request | ||||
| import os | ||||
| import sys | ||||
| import time | ||||
| import sys | ||||
| import platform | ||||
| import pprint | ||||
|  | ||||
|  | ||||
| def generate_songname(tags): | ||||
|     """Generate a string of the format '[artist] - [song]' for the given spotify song.""" | ||||
|     """ Generate a string of the format '[artist] - [song]' for the given spotify song. """ | ||||
|     raw_song = u'{0} - {1}'.format(tags['artists'][0]['name'], tags['name']) | ||||
|     return raw_song | ||||
|  | ||||
|  | ||||
| def generate_metadata(raw_song): | ||||
|     """Fetch a song's metadata from Spotify.""" | ||||
|     if misc.is_spotify(raw_song): | ||||
|     """ Fetch a song's metadata from Spotify. """ | ||||
|     if internals.is_spotify(raw_song): | ||||
|         # fetch track information directly if it is spotify link | ||||
|         log.debug('Fetching metadata for given track URL') | ||||
|         meta_tags = spotify.track(raw_song) | ||||
|     else: | ||||
|         # otherwise search on spotify and fetch information from first result | ||||
|         log.debug('Searching for "{}" on Spotify'.format(raw_song)) | ||||
|         try: | ||||
|             meta_tags = spotify.search(raw_song, limit=1)['tracks']['items'][0] | ||||
|         except IndexError: | ||||
| @@ -52,49 +58,64 @@ def generate_metadata(raw_song): | ||||
|     meta_tags[u'publisher'] = album['label'] | ||||
|     meta_tags[u'total_tracks'] = album['tracks']['total'] | ||||
|  | ||||
|     log.debug(pprint.pformat(meta_tags)) | ||||
|     return meta_tags | ||||
|  | ||||
|  | ||||
| def is_video(result): | ||||
|     # ensure result is not a channel | ||||
|     not_video = result.find('channel') is not None or \ | ||||
|                 'yt-lockup-channel' in result.parent.attrs['class'] or \ | ||||
|                 'yt-lockup-channel' in result.attrs['class'] | ||||
|  | ||||
|     # ensure result is not a mix/playlist | ||||
|     not_video = not_video or \ | ||||
|                'yt-lockup-playlist' in result.parent.attrs['class'] | ||||
|  | ||||
|     # ensure video result is not an advertisement | ||||
|     not_video = not_video or \ | ||||
|                 result.find('googleads') is not None | ||||
|  | ||||
|     video = not not_video | ||||
|     return video | ||||
|  | ||||
|  | ||||
| def generate_youtube_url(raw_song, meta_tags, tries_remaining=5): | ||||
|     """Search for the song on YouTube and generate a URL to its video.""" | ||||
|     """ Search for the song on YouTube and generate a URL to its video. """ | ||||
|     # prevents an infinite loop but allows for a few retries | ||||
|     if tries_remaining == 0: | ||||
|         log.debug('No tries left. I quit.') | ||||
|         return | ||||
|  | ||||
|     if meta_tags is None: | ||||
|         song = raw_song | ||||
|         search_url = misc.generate_search_url(song, viewsort=False) | ||||
|         search_url = internals.generate_search_url(song, viewsort=False) | ||||
|     else: | ||||
|         song = generate_songname(meta_tags) | ||||
|         search_url = misc.generate_search_url(song, viewsort=True) | ||||
|         search_url = internals.generate_search_url(song, viewsort=True) | ||||
|     log.debug('Opening URL: {0}'.format(search_url)) | ||||
|  | ||||
|     item = urllib.request.urlopen(search_url).read() | ||||
|     # item = unicode(item, 'utf-8') | ||||
|     items_parse = BeautifulSoup(item, "html.parser") | ||||
|  | ||||
|     videos = [] | ||||
|     for x in items_parse.find_all('div', {'class': 'yt-lockup-dismissable yt-uix-tile'}): | ||||
|         # ensure result is not a channel | ||||
|         if x.find('channel') is not None or 'yt-lockup-channel' in x.parent.attrs['class'] or 'yt-lockup-channel' in x.attrs['class']: | ||||
|             continue | ||||
|  | ||||
|         # ensure result is not a mix/playlist | ||||
|         if 'yt-lockup-playlist' in x.parent.attrs['class']: | ||||
|             continue | ||||
|  | ||||
|         # confirm the video result is not an advertisement | ||||
|         if x.find('googleads') is not None: | ||||
|         if not is_video(x): | ||||
|             continue | ||||
|  | ||||
|         y = x.find('div', class_='yt-lockup-content') | ||||
|         link = y.find('a')['href'] | ||||
|         title = y.find('a')['title'] | ||||
|  | ||||
|         try: | ||||
|             videotime = x.find('span', class_="video-time").get_text() | ||||
|         except AttributeError: | ||||
|             log.debug('Could not find video duration on YouTube, retrying..') | ||||
|             return generate_youtube_url(raw_song, meta_tags, tries_remaining - 1) | ||||
|  | ||||
|         youtubedetails = {'link': link, 'title': title, 'videotime': videotime, 'seconds':misc.get_sec(videotime)} | ||||
|         youtubedetails = {'link': link, 'title': title, 'videotime': videotime, | ||||
|                           'seconds': internals.get_sec(videotime)} | ||||
|         videos.append(youtubedetails) | ||||
|         if meta_tags is None: | ||||
|             break | ||||
| @@ -102,20 +123,26 @@ def generate_youtube_url(raw_song, meta_tags, tries_remaining=5): | ||||
|     if not videos: | ||||
|         return None | ||||
|  | ||||
|     log.debug(pprint.pformat(videos)) | ||||
|  | ||||
|     if args.manual: | ||||
|         print(song) | ||||
|         print('') | ||||
|         print('0. Skip downloading this song') | ||||
|         log.info(song) | ||||
|         log.info('0. Skip downloading this song.\n') | ||||
|         # fetch all video links on first page on YouTube | ||||
|         for i, v in enumerate(videos): | ||||
|           print(u'{0}. {1} {2} {3}'.format(i+1, v['title'], v['videotime'], "http://youtube.com"+v['link'])) | ||||
|         print('') | ||||
|             log.info(u'{0}. {1} {2} {3}'.format(i+1, v['title'], v['videotime'], | ||||
|                   "http://youtube.com"+v['link'])) | ||||
|         # let user select the song to download | ||||
|         result = misc.input_link(videos) | ||||
|         result = internals.input_link(videos) | ||||
|         if result is None: | ||||
|             return None | ||||
|     else: | ||||
|         if meta_tags is not None: | ||||
|         if meta_tags is None: | ||||
|             # if the metadata could not be acquired, take the first result | ||||
|             # from Youtube because the proper song length is unknown | ||||
|             result = videos[0] | ||||
|             log.debug('Since no metadata found on Spotify, going with the first result') | ||||
|         else: | ||||
|             # filter out videos that do not have a similar length to the Spotify song | ||||
|             duration_tolerance = 10 | ||||
|             max_duration_tolerance = 20 | ||||
| @@ -130,24 +157,23 @@ def generate_youtube_url(raw_song, meta_tags, tries_remaining=5): | ||||
|                 possible_videos_by_duration = list(filter(lambda x: abs(x['seconds'] - (int(meta_tags['duration_ms'])/1000)) <= duration_tolerance, videos)) | ||||
|                 duration_tolerance += 1 | ||||
|                 if duration_tolerance > max_duration_tolerance: | ||||
|                     print(meta_tags['name'], 'by', meta_tags['artists'][0]['name'], 'was not found') | ||||
|                     log.error("{0} by {1} was not found.\n".format(meta_tags['name'],meta_tags['artists'][0]['name'])) | ||||
|                     return None | ||||
|  | ||||
|             result = possible_videos_by_duration[0] | ||||
|         else: | ||||
|             # if the metadata could not be acquired, take the first result from Youtube because the proper song length is unknown | ||||
|             result = videos[0] | ||||
|  | ||||
|     full_link = None | ||||
|     if result: | ||||
|         full_link = u'youtube.com{0}'.format(result['link']) | ||||
|         full_link = u'http://youtube.com{0}'.format(result['link']) | ||||
|     else: | ||||
|         full_link = None | ||||
|  | ||||
|     log.debug('Best matching video link: {}'.format(full_link)) | ||||
|     return full_link | ||||
|  | ||||
|  | ||||
| def go_pafy(raw_song, meta_tags): | ||||
|     """Parse track from YouTube.""" | ||||
|     if misc.is_youtube(raw_song): | ||||
| def go_pafy(raw_song, meta_tags=None): | ||||
|     """ Parse track from YouTube. """ | ||||
|     if internals.is_youtube(raw_song): | ||||
|         track_info = pafy.new(raw_song) | ||||
|     else: | ||||
|         track_url = generate_youtube_url(raw_song, meta_tags) | ||||
| @@ -161,7 +187,7 @@ def go_pafy(raw_song, meta_tags): | ||||
|  | ||||
|  | ||||
| def get_youtube_title(content, number=None): | ||||
|     """Get the YouTube video's title.""" | ||||
|     """ Get the YouTube video's title. """ | ||||
|     title = content.title | ||||
|     if number is None: | ||||
|         return title | ||||
| @@ -170,7 +196,7 @@ def get_youtube_title(content, number=None): | ||||
|  | ||||
|  | ||||
| def feed_playlist(username): | ||||
|     """Fetch user playlists when using the -u option.""" | ||||
|     """ Fetch user playlists when using the -u option. """ | ||||
|     playlists = spotify.user_playlists(username) | ||||
|     links = [] | ||||
|     check = 1 | ||||
| @@ -180,9 +206,10 @@ def feed_playlist(username): | ||||
|             # in rare cases, playlists may not be found, so playlists['next'] | ||||
|             # is None. Skip these. Also see Issue #91. | ||||
|             if playlist['name'] is not None: | ||||
|                 print(u'{0:>5}. {1:<30}  ({2} tracks)'.format( | ||||
|                 log.info(u'{0:>5}. {1:<30}  ({2} tracks)'.format( | ||||
|                     check, playlist['name'], | ||||
|                     playlist['tracks']['total'])) | ||||
|                 log.debug(playlist['external_urls']['spotify']) | ||||
|                 links.append(playlist) | ||||
|                 check += 1 | ||||
|         if playlists['next']: | ||||
| @@ -190,9 +217,7 @@ def feed_playlist(username): | ||||
|         else: | ||||
|             break | ||||
|  | ||||
|     print('') | ||||
|     playlist = misc.input_link(links) | ||||
|     print('') | ||||
|     playlist = internals.input_link(links) | ||||
|     write_playlist(playlist['owner']['id'], playlist['id']) | ||||
|  | ||||
|  | ||||
| @@ -205,9 +230,11 @@ def write_tracks(text_file, tracks): | ||||
|                 else: | ||||
|                     track = item | ||||
|                 try: | ||||
|                     file_out.write(track['external_urls']['spotify'] + '\n') | ||||
|                     track_url = track['external_urls']['spotify'] | ||||
|                     file_out.write(track_url + '\n') | ||||
|                     log.debug(track_url) | ||||
|                 except KeyError: | ||||
|                     print(u'Skipping track {0} by {1} (local only?)'.format( | ||||
|                     log.warning(u'Skipping track {0} by {1} (local only?)'.format( | ||||
|                         track['name'], track['artists'][0]['name'])) | ||||
|             # 1 page = 50 results | ||||
|             # check if there are more pages | ||||
| @@ -218,11 +245,11 @@ def write_tracks(text_file, tracks): | ||||
|  | ||||
|  | ||||
| def write_playlist(username, playlist_id): | ||||
|     results = spotify.user_playlist( | ||||
|             username, playlist_id, fields='tracks,next,name') | ||||
|     results = spotify.user_playlist(username, playlist_id, | ||||
|                                     fields='tracks,next,name') | ||||
|     text_file = u'{0}.txt'.format(slugify(results['name'], ok='-_()[]{}')) | ||||
|  | ||||
|     print(u'Feeding {0} tracks to {1}'.format(results['tracks']['total'], text_file)) | ||||
|     log.info(u'Writing {0} tracks to {1}'.format( | ||||
|                results['tracks']['total'], text_file)) | ||||
|     tracks = results['tracks'] | ||||
|     write_tracks(text_file, tracks) | ||||
|  | ||||
| @@ -230,54 +257,61 @@ def write_playlist(username, playlist_id): | ||||
| def write_album(album): | ||||
|     tracks = spotify.album_tracks(album['id']) | ||||
|     text_file = u'{0}.txt'.format(slugify(album['name'], ok='-_()[]{}')) | ||||
|     print(u'Feeding {0} tracks to {1}'.format(tracks['total'], text_file)) | ||||
|  | ||||
|     log.info(u'writing {0} tracks to {1}'.format( | ||||
|                tracks['total'], text_file)) | ||||
|     write_tracks(text_file, tracks) | ||||
|  | ||||
|  | ||||
| def download_song(file_name, content): | ||||
|     """Download the audio file from YouTube.""" | ||||
|     """ Download the audio file from YouTube. """ | ||||
|     if args.input_ext in (".webm", ".m4a"): | ||||
|         link = content.getbestaudio(preftype=args.input_ext[1:]) | ||||
|     else: | ||||
|         return False | ||||
|  | ||||
|     log.debug('Downloading from URL: ' + link.url) | ||||
|     if link is None: | ||||
|         return False | ||||
|     else: | ||||
|         link.download( | ||||
|             filepath='{0}{1}'.format(os.path.join(args.folder, file_name), args.input_ext)) | ||||
|         filepath = '{0}{1}'.format(os.path.join(args.folder, file_name), | ||||
|                                    args.input_ext) | ||||
|         link.download(filepath=filepath) | ||||
|         return True | ||||
|  | ||||
|  | ||||
| def check_exists(music_file, raw_song, meta_tags, islist=True): | ||||
|     """Check if the input song already exists in the given folder.""" | ||||
|     """ Check if the input song already exists in the given folder. """ | ||||
|     log.debug('Cleaning any temp files and checking ' | ||||
|               'if "{}" already exists'.format(music_file)) | ||||
|     songs = os.listdir(args.folder) | ||||
|     for song in songs: | ||||
|         if song.endswith('.temp'): | ||||
|             os.remove(os.path.join(args.folder, song)) | ||||
|             continue | ||||
|         # check if any song with similar name is already present in the given folder | ||||
|         file_name = misc.sanitize_title(music_file) | ||||
|         file_name = internals.sanitize_title(music_file) | ||||
|         if song.startswith(file_name): | ||||
|             log.debug('Found an already existing song: "{}"'.format(song)) | ||||
|             if internals.is_spotify(raw_song): | ||||
|                 # check if the already downloaded song has correct metadata | ||||
|             already_tagged = metadata.compare(os.path.join(args.folder, song), meta_tags) | ||||
|  | ||||
|                 # if not, remove it and download again without prompt | ||||
|             if misc.is_spotify(raw_song) and not already_tagged: | ||||
|                 already_tagged = metadata.compare(os.path.join(args.folder, song), | ||||
|                                                   meta_tags) | ||||
|                 log.debug('Checking if it is already tagged correctly? {}', | ||||
|                                                             already_tagged) | ||||
|                 if not already_tagged: | ||||
|                     os.remove(os.path.join(args.folder, song)) | ||||
|                     return False | ||||
|  | ||||
|             # do not prompt and skip the current song | ||||
|             # if already downloaded when using list | ||||
|             if islist: | ||||
|                 print('Song already exists') | ||||
|                 return True | ||||
|             # if downloading only single song, prompt to re-download | ||||
|             if islist: | ||||
|                 log.warning('Song already exists') | ||||
|                 return True | ||||
|             else: | ||||
|                 prompt = input('Song with same name has already been downloaded. ' | ||||
|                                'Re-download? (y/n): ').lower() | ||||
|                 if prompt == 'y': | ||||
|                 log.info('Song with same name has already been downloaded. ' | ||||
|                          'Re-download? (y/N): ') | ||||
|                 prompt = input('> ') | ||||
|                 if prompt.lower() == 'y': | ||||
|                     os.remove(os.path.join(args.folder, song)) | ||||
|                     return False | ||||
|                 else: | ||||
| @@ -286,7 +320,7 @@ def check_exists(music_file, raw_song, meta_tags, islist=True): | ||||
|  | ||||
|  | ||||
| def grab_list(text_file): | ||||
|     """Download all songs from the list.""" | ||||
|     """ Download all songs from the list. """ | ||||
|     with open(text_file, 'r') as listed: | ||||
|         lines = (listed.read()).splitlines() | ||||
|     # ignore blank lines in text_file (if any) | ||||
| @@ -294,37 +328,36 @@ def grab_list(text_file): | ||||
|         lines.remove('') | ||||
|     except ValueError: | ||||
|         pass | ||||
|     print(u'Total songs in list: {0} songs'.format(len(lines))) | ||||
|     print('') | ||||
|     # nth input song | ||||
|     log.info(u'Preparing to download {} songs'.format(len(lines))) | ||||
|     number = 1 | ||||
|  | ||||
|     for raw_song in lines: | ||||
|         print('') | ||||
|         try: | ||||
|             grab_single(raw_song, number=number) | ||||
|         # token expires after 1 hour | ||||
|         except spotipy.client.SpotifyException: | ||||
|             # refresh token when it expires | ||||
|             new_token = misc.generate_token() | ||||
|             log.debug('Token expired, generating new one and authorizing') | ||||
|             new_token = internals.generate_token() | ||||
|             global spotify | ||||
|             spotify = spotipy.Spotify(auth=new_token) | ||||
|             grab_single(raw_song, number=number) | ||||
|         # detect network problems | ||||
|         except (urllib.request.URLError, TypeError, IOError): | ||||
|             lines.append(raw_song) | ||||
|             # remove the downloaded song from .txt | ||||
|             misc.trim_song(text_file) | ||||
|             # and append it to the last line in .txt | ||||
|             # remove the downloaded song from file | ||||
|             internals.trim_song(text_file) | ||||
|             # and append it at the end of file | ||||
|             with open(text_file, 'a') as myfile: | ||||
|                 myfile.write(raw_song + '\n') | ||||
|             print('Failed to download song. Will retry after other songs.') | ||||
|             log.warning('Failed to download song. Will retry after other songs\n') | ||||
|             # wait 0.5 sec to avoid infinite looping | ||||
|             time.sleep(0.5) | ||||
|             continue | ||||
|         except KeyboardInterrupt: | ||||
|             misc.grace_quit() | ||||
|         finally: | ||||
|             print('') | ||||
|         misc.trim_song(text_file) | ||||
|  | ||||
|         log.debug('Removing downloaded song from text file') | ||||
|         internals.trim_song(text_file) | ||||
|         number += 1 | ||||
|  | ||||
|  | ||||
| @@ -340,14 +373,14 @@ def grab_playlist(playlist): | ||||
|         username = splits[-3] | ||||
|     except IndexError: | ||||
|         # Wrong format, in either case | ||||
|         print('The provided playlist URL is not in a recognized format!') | ||||
|         log.error('The provided playlist URL is not in a recognized format!') | ||||
|         sys.exit(10) | ||||
|     playlist_id = splits[-1] | ||||
|     try: | ||||
|         write_playlist(username, playlist_id) | ||||
|     except spotipy.client.SpotifyException: | ||||
|         print('Unable to find playlist') | ||||
|         print('Make sure the playlist is set to publicly visible and then try again') | ||||
|         log.error('Unable to find playlist') | ||||
|         log.info('Make sure the playlist is set to publicly visible and then try again') | ||||
|         sys.exit(11) | ||||
|  | ||||
|  | ||||
| @@ -366,64 +399,72 @@ def grab_album(album): | ||||
|  | ||||
|  | ||||
| def grab_single(raw_song, number=None): | ||||
|     """Logic behind downloading a song.""" | ||||
|     """ Logic behind downloading a song. """ | ||||
|     if number: | ||||
|         islist = True | ||||
|     else: | ||||
|         islist = False | ||||
|  | ||||
|     if misc.is_youtube(raw_song): | ||||
|     if internals.is_youtube(raw_song): | ||||
|         log.debug('Input song is a YouTube URL') | ||||
|         content = go_pafy(raw_song, meta_tags=None) | ||||
|         raw_song = slugify(content.title).replace('-', ' ') | ||||
|  | ||||
|         meta_tags = generate_metadata(raw_song) | ||||
|     else: | ||||
|         meta_tags = generate_metadata(raw_song) | ||||
|         content = go_pafy(raw_song, meta_tags) | ||||
|  | ||||
|     if content is None: | ||||
|         log.debug('Found no matching video') | ||||
|         return | ||||
|  | ||||
|     # print '[number]. [artist] - [song]' if downloading from list | ||||
|     # otherwise print '[artist] - [song]' | ||||
|     print(get_youtube_title(content, number)) | ||||
|     # log '[number]. [artist] - [song]' if downloading from list | ||||
|     # otherwise log '[artist] - [song]' | ||||
|     log.info(get_youtube_title(content, number)) | ||||
|     # generate file name of the song to download | ||||
|     songname = content.title | ||||
|  | ||||
|     if meta_tags is not None: | ||||
|         refined_songname = generate_songname(meta_tags) | ||||
|         log.debug('Refining songname from "{0}" to "{1}"'.format(songname, refined_songname)) | ||||
|         if not refined_songname == ' - ': | ||||
|             songname = refined_songname | ||||
|  | ||||
|     file_name = misc.sanitize_title(songname) | ||||
|     file_name = internals.sanitize_title(songname) | ||||
|  | ||||
|     if not check_exists(file_name, raw_song, meta_tags, islist=islist): | ||||
|         if download_song(file_name, content): | ||||
|             print('') | ||||
|             input_song = file_name + args.input_ext | ||||
|             output_song = file_name + args.output_ext | ||||
|             print('') | ||||
|             convert.song(input_song, output_song, args.folder, | ||||
|                          avconv=args.avconv, verbose=args.verbose) | ||||
|                          avconv=args.avconv) | ||||
|             if not args.input_ext == args.output_ext: | ||||
|                 os.remove(os.path.join(args.folder, input_song)) | ||||
|  | ||||
|             if not args.no_metadata: | ||||
|                 metadata.embed(os.path.join(args.folder, output_song), meta_tags) | ||||
|         else: | ||||
|             print('No audio streams available') | ||||
|             log.error('No audio streams available') | ||||
|  | ||||
|  | ||||
| class TestArgs(object): | ||||
|     manual = False | ||||
|     input_ext = '.m4a' | ||||
|     output_ext = '.mp3' | ||||
|     folder = 'Music/' | ||||
|  | ||||
| # token is mandatory when using Spotify's API | ||||
| # https://developer.spotify.com/news-stories/2017/01/27/removing-unauthenticated-calls-to-the-web-api/ | ||||
| token = misc.generate_token() | ||||
| token = internals.generate_token() | ||||
| spotify = spotipy.Spotify(auth=token) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     args = misc.get_arguments() | ||||
|     misc.filter_path(args.folder) | ||||
|     args = internals.get_arguments() | ||||
|     internals.filter_path(args.folder) | ||||
|  | ||||
|     logger.log = logger.logzero.setup_logger(formatter=logger.formatter, | ||||
|                                       level=args.log_level) | ||||
|     log = logger.log | ||||
|     log.debug('Python version: {}'.format(sys.version)) | ||||
|     log.debug('Platform: {}'.format(platform.platform())) | ||||
|     log.debug(pprint.pformat(args.__dict__)) | ||||
|  | ||||
|     try: | ||||
|         if args.song: | ||||
|             grab_single(raw_song=args.song) | ||||
|         elif args.list: | ||||
| @@ -434,6 +475,8 @@ if __name__ == '__main__': | ||||
|             grab_album(album=args.album) | ||||
|         elif args.username: | ||||
|             feed_playlist(username=args.username) | ||||
| else: | ||||
|     misc.filter_path('Music') | ||||
|     args = TestArgs() | ||||
|         sys.exit(0) | ||||
|  | ||||
|     except KeyboardInterrupt as e: | ||||
|         log.exception(e) | ||||
|         sys.exit(-1) | ||||
|   | ||||
| @@ -1,31 +1,44 @@ | ||||
| # -*- coding: UTF-8 -*- | ||||
|  | ||||
| from spotdl import logger | ||||
| import spotdl | ||||
| import os | ||||
|  | ||||
| raw_song = "Tony's Videos VERY SHORT VIDEO 28.10.2016" | ||||
|  | ||||
| for x in os.listdir(spotdl.args.folder): | ||||
|     os.remove(os.path.join(spotdl.args.folder, x)) | ||||
|  | ||||
| class TestArgs: | ||||
|     manual = False | ||||
|     input_ext = '.m4a' | ||||
|     output_ext = '.mp3' | ||||
|     folder = 'test' | ||||
|     log_level = logger.logging.DEBUG | ||||
|  | ||||
| test_args = TestArgs() | ||||
| setattr(spotdl, "args", test_args) | ||||
|  | ||||
| spotdl.log = logger.logzero.setup_logger(formatter=logger.formatter, | ||||
|                                   level=spotdl.args.log_level) | ||||
|  | ||||
|  | ||||
| def test_youtube_url(): | ||||
|     expect_url = 'youtube.com/watch?v=qOOcy2-tmbk' | ||||
|     expect_url = 'http://youtube.com/watch?v=qOOcy2-tmbk' | ||||
|     url = spotdl.generate_youtube_url(raw_song, meta_tags=None) | ||||
|     assert url == expect_url | ||||
|  | ||||
|  | ||||
| def test_youtube_title(): | ||||
|     expect_title = "Tony's Videos VERY SHORT VIDEO 28.10.2016" | ||||
|     global content | ||||
|     content = spotdl.go_pafy(raw_song, meta_tags=None) | ||||
|     global title | ||||
|     expect_title = "Tony's Videos VERY SHORT VIDEO 28.10.2016" | ||||
|     content = spotdl.go_pafy(raw_song, meta_tags=None) | ||||
|     title = spotdl.get_youtube_title(content) | ||||
|     assert title == expect_title | ||||
|  | ||||
| def test_check_exists(): | ||||
|     expect_check = False | ||||
|     # prerequisites for determining filename | ||||
|     file_name = spotdl.misc.sanitize_title(title) | ||||
|     file_name = spotdl.internals.sanitize_title(title) | ||||
|     check = spotdl.check_exists(file_name, raw_song, meta_tags=None, islist=True) | ||||
|     assert check == expect_check | ||||
|  | ||||
| @@ -33,7 +46,7 @@ def test_check_exists(): | ||||
| def test_download(): | ||||
|     expect_download = True | ||||
|     # prerequisites for determining filename | ||||
|     file_name = spotdl.misc.sanitize_title(title) | ||||
|     file_name = spotdl.internals.sanitize_title(title) | ||||
|     download = spotdl.download_song(file_name, content) | ||||
|     assert download == expect_download | ||||
|  | ||||
| @@ -42,7 +55,9 @@ def test_convert(): | ||||
|     # exit code 0 = success | ||||
|     expect_convert = 0 | ||||
|     # prerequisites for determining filename | ||||
|     file_name = spotdl.misc.sanitize_title(title) | ||||
|     file_name = spotdl.internals.sanitize_title(title) | ||||
|     global input_song | ||||
|     global output_song | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     output_song = file_name + spotdl.args.output_ext | ||||
|     convert = spotdl.convert.song(input_song, output_song, spotdl.args.folder) | ||||
| @@ -53,11 +68,8 @@ def test_metadata(): | ||||
|     expect_metadata = None | ||||
|     # prerequisites for determining filename | ||||
|     meta_tags = spotdl.generate_metadata(raw_song) | ||||
|     meta_tags = spotdl.generate_metadata(raw_song) | ||||
|     file_name = spotdl.misc.sanitize_title(title) | ||||
|     output_song = file_name + spotdl.args.output_ext | ||||
|     file_name = spotdl.internals.sanitize_title(title) | ||||
|     metadata_output = spotdl.metadata.embed(os.path.join(spotdl.args.folder, output_song), meta_tags) | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     metadata_input = spotdl.metadata.embed(os.path.join(spotdl.args.folder, input_song), meta_tags) | ||||
|     assert (metadata_output == expect_metadata) and (metadata_input == expect_metadata) | ||||
|  | ||||
| @@ -65,8 +77,8 @@ def test_metadata(): | ||||
| def test_check_exists2(): | ||||
|     expect_check = True | ||||
|     # prerequisites for determining filename | ||||
|     file_name = spotdl.misc.sanitize_title(title) | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     file_name = spotdl.internals.sanitize_title(title) | ||||
|     os.remove(os.path.join(spotdl.args.folder, input_song)) | ||||
|     check = spotdl.check_exists(file_name, raw_song, meta_tags=None, islist=True) | ||||
|     os.remove(os.path.join(spotdl.args.folder, output_song)) | ||||
|     assert check == expect_check | ||||
|   | ||||
| @@ -1,12 +1,25 @@ | ||||
| # -*- coding: UTF-8 -*- | ||||
|  | ||||
| from spotdl import logger | ||||
| import spotdl | ||||
| import os | ||||
|  | ||||
| raw_song = 'http://open.spotify.com/track/0JlS7BXXD07hRmevDnbPDU' | ||||
|  | ||||
| for x in os.listdir(spotdl.args.folder): | ||||
|     os.remove(os.path.join(spotdl.args.folder, x)) | ||||
|  | ||||
| class TestArgs: | ||||
|     manual = False | ||||
|     input_ext = '.m4a' | ||||
|     output_ext = '.mp3' | ||||
|     folder = 'test' | ||||
|     log_level = 'DEBUG' | ||||
|  | ||||
| test_args = TestArgs() | ||||
| setattr(spotdl, "args", test_args) | ||||
|  | ||||
| spotdl.log = logger.logzero.setup_logger(formatter=logger.formatter, | ||||
|                                   level=spotdl.args.log_level) | ||||
| spotdl.internals.filter_path(spotdl.args.folder) | ||||
|  | ||||
|  | ||||
| def test_spotify_title(): | ||||
| @@ -35,7 +48,7 @@ def test_check_exists(): | ||||
|     # prerequisites for determining filename | ||||
|     songname = spotdl.generate_songname(meta_tags) | ||||
|     global file_name | ||||
|     file_name = spotdl.misc.sanitize_title(songname) | ||||
|     file_name = spotdl.internals.sanitize_title(songname) | ||||
|     check = spotdl.check_exists(file_name, raw_song, meta_tags, islist=True) | ||||
|     assert check == expect_check | ||||
|  | ||||
| @@ -52,6 +65,8 @@ def test_convert(): | ||||
|     # exit code 0 = success | ||||
|     expect_convert = 0 | ||||
|     # prerequisites for determining filename | ||||
|     global input_song | ||||
|     global output_song | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     output_song = file_name + spotdl.args.output_ext | ||||
|     convert = spotdl.convert.song(input_song, output_song, spotdl.args.folder) | ||||
| @@ -61,9 +76,7 @@ def test_convert(): | ||||
| def test_metadata(): | ||||
|     expect_metadata = True | ||||
|     # prerequisites for determining filename | ||||
|     output_song = file_name + spotdl.args.output_ext | ||||
|     metadata_output = spotdl.metadata.embed(os.path.join(spotdl.args.folder, output_song), meta_tags) | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     metadata_input = spotdl.metadata.embed(os.path.join(spotdl.args.folder, input_song), meta_tags) | ||||
|     assert metadata_output == (metadata_input == expect_metadata) | ||||
|  | ||||
| @@ -71,7 +84,7 @@ def test_metadata(): | ||||
| def test_check_exists2(): | ||||
|     expect_check = True | ||||
|     # prerequisites for determining filename | ||||
|     input_song = file_name + spotdl.args.input_ext | ||||
|     os.remove(os.path.join(spotdl.args.folder, input_song)) | ||||
|     check = spotdl.check_exists(file_name, raw_song, meta_tags, islist=True) | ||||
|     os.remove(os.path.join(spotdl.args.folder, output_song)) | ||||
|     assert check == expect_check | ||||
|   | ||||
| @@ -33,8 +33,7 @@ def test_tracks(): | ||||
|                 try: | ||||
|                     fout.write(track['external_urls']['spotify'] + '\n') | ||||
|                 except KeyError: | ||||
|                     title = track['name'] + ' by '+ track['artists'][0]['name'] | ||||
|                     print('Skipping track ' + title + ' (local only?)') | ||||
|                     pass | ||||
|             # 1 page = 50 results | ||||
|             # check if there are more pages | ||||
|             if tracks['next']: | ||||
| @@ -45,7 +44,7 @@ def test_tracks(): | ||||
|     with open('list.txt', 'r') as listed: | ||||
|         expect_song = (listed.read()).splitlines()[0] | ||||
|  | ||||
|     spotdl.misc.trim_song('list.txt') | ||||
|     spotdl.internals.trim_song('list.txt') | ||||
|     with open('list.txt', 'a') as myfile: | ||||
|         myfile.write(expect_song) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user