mirror of
https://github.com/KevinMidboe/spotify-downloader.git
synced 2025-12-08 20:39:08 +00:00
Improved conversion with ffmpeg (#60)
* Conversion with ffmpeg is more flexible now * Testing OK after introducing changes * Normalized genre and disc number across functions * Small indentation problems * Remove if condition for setting log level to error
This commit is contained in:
committed by
Ritiek Malhotra
parent
91947bca74
commit
84d0fb7379
167
spotdl.py
167
spotdl.py
@@ -9,13 +9,12 @@ from slugify import slugify
|
|||||||
from titlecase import titlecase
|
from titlecase import titlecase
|
||||||
from mutagen.mp4 import MP4, MP4Cover
|
from mutagen.mp4 import MP4, MP4Cover
|
||||||
import spotipy
|
import spotipy
|
||||||
|
import spotipy.oauth2 as oauth2
|
||||||
import eyed3
|
import eyed3
|
||||||
import requests
|
import requests
|
||||||
import pafy
|
import pafy
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
import pathlib
|
|
||||||
import spotipy.oauth2 as oauth2
|
|
||||||
|
|
||||||
|
|
||||||
def getInputLink(links):
|
def getInputLink(links):
|
||||||
@@ -35,8 +34,7 @@ def getInputLink(links):
|
|||||||
|
|
||||||
|
|
||||||
def isSpotify(raw_song):
|
def isSpotify(raw_song):
|
||||||
if (len(raw_song) == 22 and raw_song.replace(" ", "%20")
|
if (len(raw_song) == 22 and raw_song.replace(" ", "%20") == raw_song) or (raw_song.find('spotify') > -1):
|
||||||
== raw_song) or (raw_song.find('spotify') > -1):
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -124,14 +122,14 @@ def generateFileName(content):
|
|||||||
|
|
||||||
def downloadSong(content):
|
def downloadSong(content):
|
||||||
music_file = generateFileName(content)
|
music_file = generateFileName(content)
|
||||||
if args.input_ext == '.webm':
|
if input_ext == '.webm':
|
||||||
link = content.getbestaudio(preftype='webm')
|
link = content.getbestaudio(preftype='webm')
|
||||||
if link is not None:
|
if link is not None:
|
||||||
link.download(filepath='Music/' + music_file + args.input_ext)
|
link.download(filepath='Music/' + music_file + input_ext)
|
||||||
else:
|
else:
|
||||||
link = content.getbestaudio(preftype="m4a")
|
link = content.getbestaudio(preftype="m4a")
|
||||||
if link is not None:
|
if link is not None:
|
||||||
link.download(filepath="Music/" + music_file + ".m4a")
|
link.download(filepath='Music/' + music_file + input_ext)
|
||||||
|
|
||||||
|
|
||||||
def convertToMP3(music_file):
|
def convertToMP3(music_file):
|
||||||
@@ -156,32 +154,59 @@ def convertToMP3(music_file):
|
|||||||
os.remove('Music/' + music_file + '.m4a')
|
os.remove('Music/' + music_file + '.m4a')
|
||||||
|
|
||||||
|
|
||||||
def convertToM4A(music_file):
|
def convertWithFfmpeg(music_file):
|
||||||
# Here we prefer downloading .webm (Opus) audio and encode as m4a
|
# What are the differences and similarities between ffmpeg, libav, and avconv?
|
||||||
# in format prefered by iTunes - AAC (256k)
|
# https://stackoverflow.com/questions/9477115
|
||||||
# We are using ffmpeg with fdk_aac code and cutoff at 18kHz
|
# ffmeg encoders high to lower quality
|
||||||
# python3 spotdl.py -i '.webm' -o '.m4a'
|
# 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
|
||||||
|
#
|
||||||
if args.quiet:
|
if args.quiet:
|
||||||
ffmpeg_pre = 'ffmpeg -hide_banner -nostats -v panic -y '
|
ffmpeg_pre = 'ffmpeg -hide_banner -nostats -v panic -y '
|
||||||
else:
|
else:
|
||||||
ffmpeg_pre = 'ffmpeg -y '
|
ffmpeg_pre = 'ffmpeg -y '
|
||||||
os.system(ffmpeg_pre +
|
|
||||||
'-i "Music/' + music_file + args.input_ext + '" ' +
|
|
||||||
'-cutoff 18000 -c:a libfdk_aac -b:a 256k -vn ' +
|
|
||||||
'"Music/_' + music_file + args.output_ext + '" ')
|
|
||||||
|
|
||||||
os.remove('Music/' + music_file + args.input_ext)
|
if input_ext == '.m4a':
|
||||||
|
if output_ext == '.mp3':
|
||||||
|
ffmpeg_params = '-codec:v copy -codec:a libmp3lame -q:a 2 '
|
||||||
|
elif output_ext == '.webm':
|
||||||
|
ffmpeg_params = '-c:a libopus -vbr on -b:a 192k -vn '
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
elif input_ext == '.webm':
|
||||||
|
if output_ext == '.mp3':
|
||||||
|
ffmpeg_params = '-ab 192k -ar 44100 -vn '
|
||||||
|
elif output_ext == '.m4a':
|
||||||
|
ffmpeg_params = '-cutoff 20000 -c:a libfdk_aac -b:a 256k -vn '
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print('Unknown formats. Unable to convert.', input_ext, output_ext)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.quiet:
|
||||||
|
print(ffmpeg_pre +
|
||||||
|
'-i "Music/' + music_file + input_ext + '" ' +
|
||||||
|
ffmpeg_params +
|
||||||
|
'"Music/' + music_file + output_ext + '" ')
|
||||||
|
|
||||||
|
os.system(
|
||||||
|
ffmpeg_pre +
|
||||||
|
'-i "Music/' + music_file + input_ext + '" ' +
|
||||||
|
ffmpeg_params +
|
||||||
|
'"Music/' + music_file + output_ext + '" ')
|
||||||
|
os.remove('Music/' + music_file + input_ext)
|
||||||
|
|
||||||
|
|
||||||
def checkExists(music_file, raw_song, islist):
|
def checkExists(music_file, raw_song, islist):
|
||||||
if os.path.exists("Music/" + music_file + ".m4a.temp"):
|
if os.path.exists("Music/" + music_file + input_ext + ".temp"):
|
||||||
os.remove("Music/" + music_file + ".m4a.temp")
|
os.remove("Music/" + music_file + input_ext + ".temp")
|
||||||
if args.no_convert:
|
if args.no_convert:
|
||||||
extension = args.input_ext
|
extension = input_ext
|
||||||
else:
|
else:
|
||||||
if os.path.exists('Music/' + music_file + args.input_ext):
|
extension = output_ext
|
||||||
os.remove('Music/' + music_file + args.input_ext)
|
|
||||||
extension = args.output_ext
|
|
||||||
if os.path.isfile("Music/" + music_file + extension):
|
if os.path.isfile("Music/" + music_file + extension):
|
||||||
if extension == '.mp3':
|
if extension == '.mp3':
|
||||||
audiofile = eyed3.load("Music/" + music_file + extension)
|
audiofile = eyed3.load("Music/" + music_file + extension)
|
||||||
@@ -216,6 +241,8 @@ def fixSong(music_file, meta_tags):
|
|||||||
audiofile.tag.album_artist = meta_tags['artists'][0]['name']
|
audiofile.tag.album_artist = meta_tags['artists'][0]['name']
|
||||||
audiofile.tag.album = meta_tags['album']['name']
|
audiofile.tag.album = meta_tags['album']['name']
|
||||||
audiofile.tag.title = meta_tags['name']
|
audiofile.tag.title = meta_tags['name']
|
||||||
|
artist = spotify.artist(meta_tags['artists'][0]['id'])
|
||||||
|
audiofile.tag.genre = titlecase(artist['genres'][0])
|
||||||
audiofile.tag.track_num = meta_tags['track_number']
|
audiofile.tag.track_num = meta_tags['track_number']
|
||||||
audiofile.tag.disc_num = meta_tags['disc_number']
|
audiofile.tag.disc_num = meta_tags['disc_number']
|
||||||
audiofile.tag.release_date = spotify.album(
|
audiofile.tag.release_date = spotify.album(
|
||||||
@@ -248,7 +275,7 @@ def fixSongM4A(music_file, meta_tags):
|
|||||||
'disk': 'disk',
|
'disk': 'disk',
|
||||||
'cpil': 'cpil',
|
'cpil': 'cpil',
|
||||||
'tempo': 'tmpo'}
|
'tempo': 'tmpo'}
|
||||||
audiofile = MP4('Music/_' + music_file + args.output_ext)
|
audiofile = MP4('Music/' + music_file + output_ext)
|
||||||
audiofile[tags['artist']] = meta_tags['artists'][0]['name']
|
audiofile[tags['artist']] = meta_tags['artists'][0]['name']
|
||||||
audiofile[tags['album']] = meta_tags['album']['name']
|
audiofile[tags['album']] = meta_tags['album']['name']
|
||||||
audiofile[tags['title']] = meta_tags['name']
|
audiofile[tags['title']] = meta_tags['name']
|
||||||
@@ -257,6 +284,7 @@ def fixSongM4A(music_file, meta_tags):
|
|||||||
album = spotify.album(meta_tags['album']['id'])
|
album = spotify.album(meta_tags['album']['id'])
|
||||||
audiofile[tags['year']] = album['release_date']
|
audiofile[tags['year']] = album['release_date']
|
||||||
audiofile[tags['track']] = [(meta_tags['track_number'], 0)]
|
audiofile[tags['track']] = [(meta_tags['track_number'], 0)]
|
||||||
|
audiofile[tags['disk']] = [(meta_tags['disc_number'], 0)]
|
||||||
albumart = (
|
albumart = (
|
||||||
requests.get(meta_tags['album']['images'][0]['url'], stream=True)).raw
|
requests.get(meta_tags['album']['images'][0]['url'], stream=True)).raw
|
||||||
with open('last_albumart.jpg', 'wb') as out_file:
|
with open('last_albumart.jpg', 'wb') as out_file:
|
||||||
@@ -286,16 +314,17 @@ def grabSingle(raw_song, number=None):
|
|||||||
downloadSong(content)
|
downloadSong(content)
|
||||||
print('')
|
print('')
|
||||||
if not args.no_convert:
|
if not args.no_convert:
|
||||||
print('Converting ' + music_file + '.m4a to mp3')
|
print('Converting ' + music_file + input_ext + ' to ' + output_ext)
|
||||||
if args.output_ext == '.m4a':
|
if args.ffmpeg:
|
||||||
convertToM4A(music_file)
|
convertWithFfmpeg(music_file)
|
||||||
meta_tags = generateMetaTags(raw_song)
|
|
||||||
if meta_tags is not None:
|
|
||||||
print('Fixing meta-tags')
|
|
||||||
fixSongM4A(music_file, meta_tags)
|
|
||||||
else:
|
else:
|
||||||
convertToMP3(music_file)
|
convertToMP3(music_file)
|
||||||
meta_tags = generateMetaTags(raw_song)
|
meta_tags = generateMetaTags(raw_song)
|
||||||
|
if output_ext == '.m4a':
|
||||||
|
if meta_tags is not None:
|
||||||
|
print('Fixing meta-tags')
|
||||||
|
fixSongM4A(music_file, meta_tags)
|
||||||
|
elif output_ext == '.mp3':
|
||||||
if meta_tags is not None:
|
if meta_tags is not None:
|
||||||
print('Fixing meta-tags')
|
print('Fixing meta-tags')
|
||||||
fixSong(music_file, meta_tags)
|
fixSong(music_file, meta_tags)
|
||||||
@@ -348,6 +377,9 @@ def getArgs(argv=None):
|
|||||||
help='choose the song to download manually', action='store_true')
|
help='choose the song to download manually', action='store_true')
|
||||||
parser.add_argument('-l', '--list', default=False,
|
parser.add_argument('-l', '--list', default=False,
|
||||||
help='download songs present in list.txt', action='store_true')
|
help='download songs present in list.txt', action='store_true')
|
||||||
|
parser.add_argument('-f', '--ffmpeg', default=False,
|
||||||
|
help='Use ffmpeg instead of libav for conversion. If not set defaults to libav',
|
||||||
|
action='store_true')
|
||||||
parser.add_argument('-q', '--quiet', default=False,
|
parser.add_argument('-q', '--quiet', default=False,
|
||||||
help='spare us output of ffmpeg conversion', action='store_true')
|
help='spare us output of ffmpeg conversion', action='store_true')
|
||||||
parser.add_argument('-i', '--input_ext', default='.m4a',
|
parser.add_argument('-i', '--input_ext', default='.m4a',
|
||||||
@@ -364,39 +396,7 @@ def graceQuit():
|
|||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def spotifyDownload():
|
||||||
|
|
||||||
# Python 3 compatibility
|
|
||||||
if version_info > (3, 0):
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
eyed3.log.setLevel("ERROR")
|
|
||||||
|
|
||||||
os.chdir(path[0])
|
|
||||||
|
|
||||||
if not os.path.exists("Music"):
|
|
||||||
os.makedirs("Music")
|
|
||||||
open('list.txt', 'a').close()
|
|
||||||
|
|
||||||
# Please respect this user token :)
|
|
||||||
oauth2 = oauth2.SpotifyClientCredentials(client_id='4fe3fecfe5334023a1472516cc99d805',
|
|
||||||
client_secret='0f02b7c483c04257984695007a4a8d5c')
|
|
||||||
token = oauth2.get_access_token()
|
|
||||||
|
|
||||||
spotify = spotipy.Spotify(auth=token)
|
|
||||||
|
|
||||||
# Set up arguments
|
|
||||||
args = getArgs()
|
|
||||||
|
|
||||||
if args.no_convert:
|
|
||||||
print("-n, --no-convert skip the conversion process and meta-tags")
|
|
||||||
if args.manual:
|
|
||||||
print("-m, --manual choose the song to download manually")
|
|
||||||
print('')
|
|
||||||
if args.list:
|
|
||||||
grabList(file='list.txt')
|
|
||||||
exit()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
for temp in os.listdir('Music/'):
|
for temp in os.listdir('Music/'):
|
||||||
if temp.endswith('.m4a.temp'):
|
if temp.endswith('.m4a.temp'):
|
||||||
@@ -409,3 +409,42 @@ if __name__ == '__main__':
|
|||||||
print('')
|
print('')
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
graceQuit()
|
graceQuit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
# Python 3 compatibility
|
||||||
|
if version_info > (3, 0):
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
os.chdir(path[0])
|
||||||
|
if not os.path.exists("Music"):
|
||||||
|
os.makedirs("Music")
|
||||||
|
open('list.txt', 'a').close()
|
||||||
|
|
||||||
|
# Please respect this user token :)
|
||||||
|
oauth2 = oauth2.SpotifyClientCredentials(
|
||||||
|
client_id='4fe3fecfe5334023a1472516cc99d805',
|
||||||
|
client_secret='0f02b7c483c04257984695007a4a8d5c')
|
||||||
|
token = oauth2.get_access_token()
|
||||||
|
spotify = spotipy.Spotify(auth=token)
|
||||||
|
|
||||||
|
# Set up arguments
|
||||||
|
args = getArgs()
|
||||||
|
if args.ffmpeg:
|
||||||
|
input_ext = args.input_ext
|
||||||
|
output_ext = args.output_ext
|
||||||
|
else:
|
||||||
|
input_ext = '.m4a'
|
||||||
|
output_ext = '.mp3'
|
||||||
|
if args.no_convert:
|
||||||
|
print("-n, --no-convert skip the conversion process and meta-tags")
|
||||||
|
if args.manual:
|
||||||
|
print("-m, --manual choose the song to download manually")
|
||||||
|
print('')
|
||||||
|
if args.list:
|
||||||
|
grabList(file='list.txt')
|
||||||
|
exit()
|
||||||
|
eyed3.log.setLevel("ERROR")
|
||||||
|
|
||||||
|
spotifyDownload()
|
||||||
|
|||||||
Reference in New Issue
Block a user