#!/usr/bin/env python DESC = """ ______________________________________________________________ | | | Edit Metadata of MP3 files based on file name | |____________________________________________________________| """ import sys 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 == '': print('Warning, you are missing Genius key. Add it using --config\n\n') if SP_SECRET == '': print('Warning, you are missing Spotify Client Secret. Add it using --config\n\n') if SP_ID == '': 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 == '': genius_key = input('Enter Genius Client Access token : ') CONFIG['keys']['genius_key'] = str(genius_key) if SP_SECRET == '': sp_secret = input('Enter Spotify Secret token : ') CONFIG['keys']['spotify_client_secret'] = str(sp_secret) if SP_ID == '': 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 ''' 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 ''' 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: 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 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(norename, rename_format, file_name, metadata): ''' call eyed3 module to set mp3 song metadata as received from spotify ''' 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() new_path = path.dirname(file_name) + '{}.mp3'.format(song_title) rename(file_name, new_path) return def fix_music_files(spotify, files, norename, rename_format): need_to_improve = [] for file_name in files: metadata = get_metadata_spotify(spotify, improve_song_name(file_name)) if not metadata: need_to_improve.append(file_name) set_metadata(norename, rename_format, file_name, metadata) 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: # fix_music_files(spotify, files.append( # song_name), norename, rename_format) # elif repair_directory: chdir(repair_directory or '.') files = list_files() need_to_improve = fix_music_files(spotify, files, norename, rename_format) print(need_to_improve) if __name__ == "__main__": main()