377 lines
12 KiB
Python
377 lines
12 KiB
Python
#!/usr/bin/env python
|
|
|
|
DESC = """
|
|
|
|
____ _ _ __ __ __ ____ _____
|
|
| __ ) ___ __ _ _ _| |_(_)/ _|_ _| \/ | _ \___ /
|
|
| _ \ / _ \/ _` | | | | __| | |_| | | | |\/| | |_) ||_ \
|
|
| |_) | __/ (_| | |_| | |_| | _| |_| | | | | __/___) |
|
|
|____/ \___|\__,_|\__,_|\__|_|_| \__, |_| |_|_| |____/
|
|
|___/
|
|
|
|
______________________________________________________________
|
|
| |
|
|
| Edit Metadata of MP3 files based on file name |
|
|
|____________________________________________________________|
|
|
"""
|
|
|
|
|
|
import sys
|
|
import shutil
|
|
import os
|
|
from os import chdir, listdir, rename, walk, path, environ
|
|
from os.path import basename, dirname, realpath
|
|
import spotipy
|
|
import argparse
|
|
import configparser
|
|
import spotipy.oauth2 as oauth2
|
|
import re
|
|
from titlecase import titlecase
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import eyed3
|
|
import argparse
|
|
|
|
|
|
def setup_config():
|
|
'''
|
|
read api keys from config.ini file
|
|
'''
|
|
|
|
global CONFIG, GENIUS_KEY, SP_SECRET, SP_ID, config_path
|
|
|
|
CONFIG = configparser.ConfigParser()
|
|
config_path = realpath(__file__).replace(basename(__file__), '')
|
|
config_path = config_path + 'config.ini'
|
|
CONFIG.read(config_path)
|
|
|
|
GENIUS_KEY = CONFIG['keys']['genius_key']
|
|
SP_SECRET = CONFIG['keys']['spotify_client_secret']
|
|
SP_ID = CONFIG['keys']['spotify_client_id']
|
|
|
|
if GENIUS_KEY == '<insert genius key here>':
|
|
print('Warning, you are missing Genius key. Add it using --config\n\n')
|
|
|
|
if SP_SECRET == '<insert spotify client secret here>':
|
|
print('Warning, you are missing Spotify Client Secret. Add it using --config\n\n')
|
|
|
|
if SP_ID == '<insert spotify client id here>':
|
|
print('Warning, you are missing Spotify Client ID. Add it using --config\n\n')
|
|
|
|
|
|
def add_config_keys():
|
|
'''
|
|
Adds configuration keys in the config.ini file
|
|
'''
|
|
|
|
GENIUS_KEY = CONFIG['keys']['genius_key']
|
|
SP_SECRET = CONFIG['keys']['spotify_client_secret']
|
|
SP_ID = CONFIG['keys']['spotify_client_id']
|
|
|
|
if GENIUS_KEY == '<insert genius key here>':
|
|
genius_key = input('Enter Genius Client Access token : ')
|
|
CONFIG['keys']['genius_key'] = str(genius_key)
|
|
|
|
if SP_SECRET == '<insert spotify client secret here>':
|
|
sp_secret = input('Enter Spotify Secret token : ')
|
|
CONFIG['keys']['spotify_client_secret'] = str(sp_secret)
|
|
|
|
if SP_ID == '<insert spotify client id here>':
|
|
sp_id = input('Enter Spotify Client ID : ')
|
|
CONFIG['keys']['spotify_client_id'] = str(sp_id)
|
|
|
|
with open(config_path, 'w') as configfile:
|
|
CONFIG.write(configfile)
|
|
|
|
|
|
def improve_song_name(song):
|
|
'''
|
|
removes all unwanted words and numbers from file name so that the spotify search results can be improved
|
|
|
|
removes all numbers from beginning, then strip all punctuation marks from the string, then remove words in word_filters, then remove unwanted space
|
|
'''
|
|
|
|
char_filters = "()[]{}-:_/=!+\"\'"
|
|
word_filters = ('lyrics', 'lyric', 'by', 'video', 'official', 'hd', 'dirty', 'with', 'lyrics', 'feat', 'original', 'mix',
|
|
'www', 'com', 'mp3', 'audio', 'remixed', 'remix', 'full', 'version', 'music', 'hq', 'uploaded', 'explicit')
|
|
|
|
reg_exp = 's/^\d\d //'
|
|
song = song.strip()
|
|
song = song.lstrip("0123456789.- ")
|
|
# re.sub(reg_exp, '', song)
|
|
song = song[0:-4]
|
|
song = ''.join(
|
|
map(lambda c: " " if c in char_filters else c, song))
|
|
|
|
song = re.sub('|'.join(re.escape(key) for key in word_filters),
|
|
"", song, flags=re.IGNORECASE)
|
|
|
|
song = ' '.join(song.split()).strip()
|
|
|
|
return song
|
|
|
|
|
|
def get_song_name(title, artist):
|
|
'''
|
|
return search query for spotify api call
|
|
'''
|
|
|
|
return title + ' - ' + artist
|
|
|
|
|
|
def get_lyrics_genius(song_name):
|
|
'''
|
|
calls genius.com api for getting the url of the song lyrics page then scrapes that page to fetch the lyrics
|
|
'''
|
|
|
|
GENIUS_KEY = "iazjdOEEunvS_XOXhmJTcUzOsvrEjaNIftCKj7PLrgZjjWXiFTeoNHVmwYRDMkx9"
|
|
base_url = "https://api.genius.com"
|
|
headers = {'Authorization': 'Bearer %s' % (GENIUS_KEY)}
|
|
search_url = base_url + "/search"
|
|
data = {'q': song_name}
|
|
|
|
response = requests.get(search_url, data=data, headers=headers)
|
|
json = response.json()
|
|
|
|
try:
|
|
song_info = json['response']['hits'][0]['result']['api_path']
|
|
except KeyError:
|
|
print("Could not find lyrics for " + song_name)
|
|
return None
|
|
except IndexError:
|
|
print("Could not find lyrics for " + song_name)
|
|
return None
|
|
|
|
song_url = base_url + song_info
|
|
response = requests.get(song_url, headers=headers)
|
|
json = response.json()
|
|
song_path = json['response']['song']['path']
|
|
song_url = "http://genius.com" + song_path
|
|
page = requests.get(song_url)
|
|
html = BeautifulSoup(page.text, "html.parser")
|
|
|
|
# remove script tags that they put in the middle of the lyrics
|
|
[h.extract() for h in html('script')]
|
|
|
|
lyrics = html.find("div", class_="lyrics").get_text()
|
|
lyrics.replace('\n', ' ')
|
|
return lyrics
|
|
|
|
|
|
def get_metadata_spotify(spotify, song_name):
|
|
'''
|
|
call spotify.com api to get the metadata required, as much as possible
|
|
'''
|
|
|
|
print("trying to find data on Spotify...")
|
|
metadata = {}
|
|
try:
|
|
meta_tags = spotify.search(song_name, limit=1)['tracks']['items'][0]
|
|
except IndexError:
|
|
print("Could not find the song on Spotify")
|
|
return []
|
|
|
|
metadata['title'] = meta_tags['name']
|
|
metadata['artist'] = meta_tags['artists'][0]['name']
|
|
metadata['album'] = meta_tags['album']['name']
|
|
metadata['album_artist'] = meta_tags['album']['artists'][0]['name']
|
|
|
|
album_id = meta_tags['album']['id']
|
|
album_meta_tags = spotify.album(album_id)
|
|
|
|
metadata['release_date'] = album_meta_tags['release_date']
|
|
try:
|
|
metadata['genre'] = titlecase(album_meta_tags['genres'][0])
|
|
except IndexError:
|
|
try:
|
|
artist_id = meta_tags['artists'][0]['id']
|
|
artist_meta_tags = spotify.artist(artist_id)
|
|
metadata['genre'] = titlecase(artist_meta_tags['genres'][0])
|
|
|
|
except IndexError:
|
|
print("song genre could not be found.")
|
|
pass
|
|
|
|
metadata['track_num'] = meta_tags['track_number']
|
|
metadata['disc_num'] = meta_tags['disc_number']
|
|
|
|
metadata['albumart'] = meta_tags['album']['images'][0]['url']
|
|
|
|
lyrics = get_lyrics_genius(get_song_name(
|
|
metadata['title'], metadata['artist']))
|
|
|
|
if lyrics is not None:
|
|
metadata['lyrics'] = lyrics
|
|
|
|
print()
|
|
return metadata
|
|
|
|
|
|
def list_files():
|
|
'''
|
|
list all files in current directory with extension .mp3
|
|
'''
|
|
|
|
files = []
|
|
return [f for f in listdir('.') if f.endswith('.mp3')]
|
|
|
|
|
|
def set_metadata(file_name, metadata):
|
|
'''
|
|
call eyed3 module to set mp3 song metadata as received from spotify
|
|
'''
|
|
|
|
print("setting metadata for " + file_name)
|
|
print()
|
|
audiofile = eyed3.load(file_name)
|
|
tag = audiofile.tag
|
|
|
|
if 'genre' in metadata:
|
|
tag.genre = metadata['genre']
|
|
|
|
if 'lyrics' in metadata:
|
|
tag.lyrics.set(metadata['lyrics'])
|
|
|
|
img = requests.get(
|
|
metadata['albumart'], stream=True)
|
|
img = img.raw
|
|
|
|
albumart = img.read()
|
|
tag.images.set(3, albumart, 'image/jpeg')
|
|
|
|
tag.save(version=(2, 3, 0))
|
|
|
|
# if not norename:
|
|
# song_title = rename_format.format(
|
|
# title=metadata['title'] + ' -',
|
|
# artist=metadata['artist'] + ' -',
|
|
# album=metadata['album'] + ' -')
|
|
|
|
# song_title = song_title[:-1] if song_title.endswith('-') else song_title
|
|
# song_title = ' '.join(song_title.split()).strip()
|
|
|
|
# print("renaming " + file_name + "to " + song_title)
|
|
# new_path = path.dirname(file_name) + '{}.mp3'.format(song_title)
|
|
# rename(file_name, new_path)
|
|
|
|
print()
|
|
return
|
|
|
|
|
|
def fix_music_file(spotify, file_name, norename, rename_format):
|
|
print("------------------------------------------------------------------------")
|
|
print()
|
|
print()
|
|
print("Currently processing " + file_name)
|
|
metadata = get_metadata_spotify(spotify, improve_song_name(file_name))
|
|
if not metadata:
|
|
is_improvemet_needed = True
|
|
return is_improvemet_needed
|
|
else:
|
|
set_metadata(file_name, metadata)
|
|
is_improvemet_needed = False
|
|
|
|
rename_file = rename_to_format(
|
|
file_name, norename, rename_format, metadata)
|
|
|
|
shutil.move(rename_file, 'Music')
|
|
return is_improvemet_needed
|
|
|
|
|
|
def rename_to_format(file_name, norename, rename_format, metadata):
|
|
if not norename:
|
|
song_title = rename_format.format(
|
|
title=metadata['title'] + ' -',
|
|
artist=metadata['artist'] + ' -',
|
|
album=metadata['album'] + ' -')
|
|
|
|
song_title = song_title[:-1] if song_title.endswith('-') else song_title
|
|
song_title = ' '.join(song_title.split()).strip()
|
|
|
|
print("renaming " + file_name + "to " + song_title)
|
|
new_path = path.dirname(file_name) + '{}.mp3'.format(song_title)
|
|
rename(file_name, new_path)
|
|
return new_path
|
|
|
|
|
|
def fix_music_files(spotify, files, norename, rename_format):
|
|
need_to_improve = []
|
|
for file_name in files:
|
|
response = fix_music_file(spotify, file_name, norename, rename_format)
|
|
|
|
if response is True:
|
|
need_to_improve.append(file_name)
|
|
|
|
("------------------------------------------------------------------------")
|
|
print()
|
|
print()
|
|
|
|
return need_to_improve
|
|
|
|
|
|
def main():
|
|
'''
|
|
Deals with arguements and calls other functions
|
|
'''
|
|
|
|
setup_config()
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="{}".format(DESC), formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
# group = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
parser.add_argument('-d', '--dir', action="store", dest='repair_directory',
|
|
help='give path of music files\' directory', default=os.getcwd())
|
|
|
|
parser.add_argument('-s', '--song', action='store', dest='song_name',
|
|
help='Only fix metadata of the file specified', default=None)
|
|
|
|
parser.add_argument('-c', '--config', action='store_true', dest='config',
|
|
help="Add API Keys to config\n\n")
|
|
|
|
parser.add_argument('-n', '--norename', action='store_true',
|
|
help='Does not rename files to song title\n\n')
|
|
|
|
parser.add_argument('-f', '--format', action='store', dest='rename_format', help='''Specify the Name format used in renaming,
|
|
Valid Keywords are:
|
|
{title}{artist}{album}\n\n)''')
|
|
|
|
args = parser.parse_args()
|
|
|
|
repair_directory = args.repair_directory or '.'
|
|
song_name = args.song_name or None
|
|
norename = args.norename or False
|
|
rename_format = args.rename_format or '{title}'
|
|
config = args.config
|
|
|
|
if config:
|
|
add_config_keys()
|
|
|
|
auth = oauth2.SpotifyClientCredentials(
|
|
client_id="622a0e16a4914e3eadc2a37b4a134f1e", client_secret="6fe008a8b7754954a58a9849fa3172df")
|
|
token = auth.get_access_token()
|
|
spotify = spotipy.Spotify(auth=token)
|
|
|
|
files = []
|
|
|
|
if song_name is not None:
|
|
need_to_improve = fix_music_file(
|
|
spotify, song_name, norename, rename_format)
|
|
if need_to_improve is True:
|
|
print(song_name)
|
|
|
|
elif repair_directory:
|
|
chdir(repair_directory or '.')
|
|
if not os.path.exists("Music"):
|
|
os.makedirs("Music")
|
|
files = list_files()
|
|
need_to_improve = fix_music_files(
|
|
spotify, files, norename, rename_format)
|
|
print(need_to_improve)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|