Skip to content

Instantly share code, notes, and snippets.

@tobbez
Last active August 29, 2015 14:17
Show Gist options
  • Save tobbez/9063ea0b39609431612f to your computer and use it in GitHub Desktop.
Save tobbez/9063ea0b39609431612f to your computer and use it in GitHub Desktop.
Rename music files in a directory according to tags
#!/usr/bin/env python2
# coding: utf-8
#
# Copyright (c) 2015, tobbez
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
from __future__ import division, print_function, unicode_literals
import mutagen
import os
import re
import sys
def sanitize_path_component(name):
"""Replace characters that are not allowed in filenames on common operating
systems with underscores"""
return re.sub(u'''['/<>:"\\|\\\\?\\*]''', u'_', name)
class MusicRenamer(object):
filename_re = '\\.(flac|mp3)$'
def run(self, args):
"""Entry point for the application"""
args = [arg.decode('utf-8') for arg in args]
if len(args) < 3 or '-h' in args or '--help' in args:
self.usage(args[0])
return
fmt = args[1]
for directory in args[2:]:
self.handle_dir(directory, fmt)
def usage(self, appname):
"""Print usage instructions"""
print('Usage: {} <FORMAT-STRING> <DIRECTORY> [DIRECTORY,...]'.format(appname))
print()
print('FORMAT-STRING is a python modern-style format string, with the following')
print('keys available:')
print(' artist')
print(' album')
print(' title')
print(' tracknumber')
print(' discnumber')
print()
print('''Example FORMAT-STRING: '{tracknumber}. {artist} - {title}' ''')
def get_tags(self, path):
"""Retrieve the tags for a file"""
tags = mutagen.File(path, easy=True)
res = {x: tags[x][0] for x in tags if x in ('artist',
'album',
'title',
'tracknumber',
'discnumber')}
if '/' in res['tracknumber']:
res['tracknumber'] = res['tracknumber'].split('/')[0]
res['tracknumber'] = int(res['tracknumber'])
if 'discnumber' not in res:
res['discnumber'] = ''
else:
if '/' in res['discnumber']:
res['discnumber'] = res['discnumber'].split('/')[0]
res['discnumber'] = int(res['discnumber'])
return res
def handle_dir(self, directory, format_string):
"""Handle (rename) all music files in the specified directory"""
tags = {}
for f in filter(lambda x: re.search(self.filename_re, x, re.I), os.listdir(directory)):
path = os.path.join(directory, f)
tags[path] = self.get_tags(path)
max_trackno = max(tags.values(), key=lambda x: x['tracknumber'])['tracknumber']
trackno_digits = max(2, len(str(max_trackno)))
for _, t in tags.iteritems():
t['tracknumber'] = str(t['tracknumber']).zfill(trackno_digits)
for old_path, t in sorted(tags.iteritems(), key=lambda x: x[1]['tracknumber']):
ext = old_path.split('.')[-1]
new_fn = sanitize_path_component(format_string.format(**t) + '.{}'.format(ext))
new_path = os.path.join(directory, new_fn)
if old_path != new_path:
print('{} » {}'.format(old_path, new_path))
os.rename(old_path, new_path)
if __name__ == '__main__':
MusicRenamer().run(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment