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:
@@ -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,18 +134,14 @@ 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
|
||||
sec += int(v[0])
|
||||
if len(v) > 1: # minutes
|
||||
sec += int(v[1]) * 60
|
||||
if len(v) > 2: # hours
|
||||
sec += int(v[2]) * 3600
|
||||
return sec
|
||||
v = time_str.split(':', 3)
|
||||
v.reverse()
|
||||
sec = 0
|
||||
if len(v) > 0: # seconds
|
||||
sec += int(v[0])
|
||||
if len(v) > 1: # minutes
|
||||
sec += int(v[1]) * 60
|
||||
if len(v) > 2: # hours
|
||||
sec += int(v[2]) * 3600
|
||||
return sec
|
||||
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',
|
||||
|
||||
Reference in New Issue
Block a user