I have a number of playlists on Gondolin, which is a headless machine. I wanted to be able to easily add a given mp3 file to the playlists which are in m3u format. That means that each entry has both the filename and an extended line with some basic metadata, in particular the track length in seconds, the track artist and name. I wanted a script that could extract this information from the mp3 file and make adding the entry easy. So I wrote this in Python. It’s rough and ready and it is probably not very Pythonic but it’s working for me. The script should create a playlist if it doesn’t currently exist, and check for a newline at the end of the file so that the appended lines are really on a new line. ItWorksForMe (TM).
My basic usage is
playlist-append -m the_mp3_file.mp3 -p the_playlist.m3u -r /var/media/mp3
the last parameter is the path relative to which the mp3 filename should be written to. This is useful for me because I rsync the whole tree between machines, as you will see there are options for writing an absolute pathname if you prefer. I should probably rewrite the script to do it relative to the playlist, but that’s another day.
#!/usr/bin/env python # # Trivial script to extract meta data from an mp3 file and add # the mp3 file and data to an existing m3u file # # Colin Turner <firstname.lastname@example.org> # 2014 # GPL v2 # # v 20140801.0 Initial Version # # v 20140802.0 # The mp3 filename is now, by default, written relative to the path of # the playlist if possible. # import eyeD3 import re import os # We want to be able to process some command line options. from optparse import OptionParser def append(options, artist, title, seconds): 'append the fetched data to the playlist' mp3_filename = resolve_mp3_filename(options) # Check if the playlist file exists there_is_no_spoon = not os.path.isfile(options.out_filename) with open(options.out_filename, 'a+') as playlist: # was the file frshly created? if there_is_no_spoon: # So write the header print >> playlist, "#EXTM3U" else: # There was a file, so check the last character, in case there was no \n playlist.seek(-1, os.SEEK_END) last_char = playlist.read(1) if(last_char != '\n'): print >> playlist # OK, now able to write print >> playlist, "#EXTINF:%u,%s - %s" % (seconds, artist, title) print >> playlist, "%s" % mp3_filename def resolve_mp3_filename(options): 'resolve the mp3 filename appropriately, if we can, and if we are asked to' 'there are three modes, depending on command line parameters:' '-l we write the filename precisely as on the command list' '-r specifies a base relative which to write the filename' 'otherwise we try to resolve relative to the directory of the playlist' 'the absolute filename will be the fall back position is resolution is impossible' if options.leave_filename: # we have been specifcally told not to resolve the filename mp3_filename = options.in_filename if options.verbose: print "Filename resolution disabled." if not options.leave_filename and not len(options.relative_to): # Neither argument used, automatcally resolve relative to the playlist (playlist_path, playlist_name) = os.path.split(os.path.abspath(options.out_filename)) options.relative_to = playlist_path + os.path.sep if options.verbose: print "Automatic filename resolution relative to playlist base %s" % options.relative_to if len(options.relative_to): # We have been told to map the path relative to another path mp3_filename = os.path.abspath(options.in_filename) # Check that the root is actually present if mp3_filename.find(options.relative_to) == 0: # It is present and at the start of the line mp3_filename = mp3_filename.replace(options.relative_to, '', 1) if options.verbose: print "mp3 filename will be written as %s..." % mp3_filename return mp3_filename def get_meta_data(options): 'perform the append on the playlist' # read the existing data into three arrays in a tuple if options.verbose: print "Opening MP3 file %s ..." % options.in_filename if eyeD3.isMp3File(options.in_filename): # Ok, so it's an mp3 audioFile = eyeD3.Mp3AudioFile(options.in_filename) tag = audioFile.getTag() artist = tag.getArtist() title = tag.getTitle() seconds = audioFile.getPlayTime() if not options.quiet: print "%s - %s (%s s)" % (artist, title, seconds) # OK, we have the required information, now time to write to the playlist return artist, title, seconds else: print "Not a valid mp3 file." exit(1) def print_banner(): print "playlist-append" def main(): 'the main function that kicks everything else off' usage = "usage: %prog [options] arg" parser = OptionParser(usage) parser.add_option("-m", "--mp3-file", dest="in_filename", help="the FILENAME of the mp3 file to add") parser.add_option("-p", "--playlist-file", dest="out_filename", help="the FILENAME of the playlist to append to") parser.add_option("-l", "--leave-filename", dest="leave_filename", action="store_false", help="leaves the mp3 path as specified on the command line, rather than resolving it") parser.add_option("-r", "--relative-to", dest="relative_to", default="", help="resolves mp3 filename relative to this path") parser.add_option("-v", "--verbose", action="store_true", dest="verbose") parser.add_option("-q", "--quiet", default=False, action="store_true", dest="quiet") (options, args) = parser.parse_args() # if len(args) == 0: # parser.error("use -h for more help") if not options.quiet: print_banner() (artist, title, seconds) = get_meta_data(options) append(options, artist, title, seconds) if not options.quiet: print "Appended to playlist..." if __name__ == '__main__': main()