git.fiddlerwoaroof.com
Raw Blame History
#!/usr/bin/env python

# Copyright (c) 2015 Edward Langley
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# Neither the name of the project's author nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'''Sorts music into my preferred directory layout.
Usage: find -name '*.mp3' -o '*.m4a' . . . | python sorter.py <destination_directory>

Requires mutagen for tag handling (and, therefore, taglib)'''

import os
import shutil
import string
import mutagen
import argparse

parser=argparse.ArgumentParser()
parser.add_argument('destination')
args = parser.parse_args()

destination = args.destination
print('copying music to, %s' % destination)

import sys

class PathFinder(object):
    def __init__(self):
        pass

    def construct_tag_map(self, data):
        tag_map = {}
        for (tag, _) in data.tags:
            tag_map[tag.lower()] = tag
        return tag_map

    def get_path(self, data, orig_name):
        tag_map = self.construct_tag_map(data)
        print(tag_map, tag_map['title'], data.get(tag_map['title']))
        orig_name = orig_name.rpartition('.')[0]
        title=self.clean(data.get(tag_map['title'], ['Unknown Title'])[0])
        if title.strip() == '': title = orig_name
        album=self.clean(data.get(tag_map['album'], ['Unknown Album'])[0])
        if album.strip() == '': album = 'Unknown Album'

        if 'albumartist' in data: artist = data[tag_map['albumartist']][0]
        elif 'performer' in data: artist = data[tag_map['performer']][0]
        elif 'conductor' in data: artist = data[tag_map['conductor']][0]
        else: artist = data.get(tag_map['artist'], ['Unknown Artist'])[0]
        artist = self.clean(artist)
        if artist.strip() == '': artist = 'Unknown Artist'

        tracknumber=self.clean(data.get(tag_map['tracknumber'], ['--'])[0])
        if tracknumber != '--':
           try:
               tracknumber = tracknumber.partition('_')[0]
               tracknumber = '%02d' % int(tracknumber)
           except ValueError:
               pass
        discnumber=self.clean(data.get(tag_map['discnumber'], ['--'])[0].partition('/')[0])
        print(f'discnumber is: {discnumber}')
        if discnumber.strip() not in {'', '--'}: discnumber = '%02d' % int(discnumber)
        result =[ os.path.join(artist[0].upper(),artist,album), '%s_%s' % (tracknumber, title) ]
        if discnumber.strip() not in {'', '--'}:
            result[1] = '%s_%s' % (discnumber, result[1])
        return result

    validchars = frozenset('_.- %s%s' % (string.ascii_letters, string.digits))
    def clean(self, strin):
        strin = ''.join((c if c in self.validchars else " ") for c in strin)
        strin = '_'.join(strin.split())
        if len(strin) > 40:
            pa = strin[:28]
            pb = strin[-31:]
            strin = '...'.join([pa,pb])
        return strin





pf = PathFinder()
for line in sys.stdin:
    line = line.strip()
    try:
        file = mutagen.File(line, easy=True)
        x = 0
        suf = ''
        path, name = pf.get_path(file, os.path.basename(line))
        ext = line.rpartition('.')[-1]

        path = os.path.join(destination, path)

        checkname = '%s.%s' % ('__'.join([_f for _f in [name, suf] if _f]), ext)

        while os.path.exists(os.path.join(path, '%s.%s' % ('__'.join([_f for _f in [name, suf] if _f]), ext))):
            x += 1
            suf = '%02d' % x

        name = '%s.%s' % ('__'.join([_f for _f in [name, suf] if _f]), ext)
        try:
            os.makedirs(path)
        except OSError as e:
            if e.errno == 17:
                pass
            else:
                raise

        print(os.path.join(path, name))
        shutil.copy(line, os.path.join(path, name))
    except Exception as e:
        print('Error Processing: %s' % line,e, file=sys.stderr)
        print('Error Processing: %s' % line,e)