public
Description: random pieces of code
Homepage: http://darrenf.org/
Clone URL: git://github.com/darrenf/odds-and-sods.git
odds-and-sods / fix-unknowns.py
100644 151 lines (130 sloc) 5.555 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/python
""" Generate shell scripts to fix "Unknown Album" problems.
 
ASSUMPTIONS:
 
* pork.py is in /opt/pork
* tracks.sh.template is in /opr/pork/templates
* id3lib and PyYAML are installed
* jinja2 is installed
 
This script walks all subdirectories under the supplied one, or a default:
 
/storage/music/mp3/CDs/U/Unknown Artist/
 
searching for those containing a file called 'tracklist.txt'. If present,
this file is parsed according to a simple format.
 
* the first line is the album title
* all subsequent lines are tracks
* track lines are 3 values, separated by tabs
* track number
* artist
* name
 
If the file parses according to that format, a shell script is written
to the same directory. The script is called 'tracks.sh', and contains
the commands to tag and rename the mp3s.
 
If this is all successful, 'tracklist.txt' is renamed 'tracklist-done.txt'.
This is so fix-unknowns.py does not operate on the same directory twice,
for example when scheduled to run via cron.
 
TODO: loads. Seriously, there's loads more that can happen here. Here's
some ideas.
 
* Submit tags up to freedb
* Rename the directory itself
* Move the directory into the correct place
* Take /etc/ripit/config into account regarding directory structure,
mp3 naming convention, etc
* Work on non-mp3s (like flac; ie, use a different template and
spit out different commands)
* Execute tracks.sh and remove it afterwards (ooh, that'd be nice).
* I'm sure there's loads more
 
"""
 
import os, sys
sys.path.insert(0,'/opt/pork')
from pork import Renderer
 
base = os.path.join('/storage','music','mp3','CDs','U','Unknown Artist')
config = { 'template': '/opt/pork/templates/tracks.sh.template',
           'engine': 'jinja2' }
 
def parse_tracklist(dir):
    """ Parse a tracklist.txt file.
 
Accepts a directory as argument.
If there's a tracklist.txt file inside the given directory, and it
successfully parses, then a dictionary is returned. If there is no
such file, or there is but it doesn't parse, then returns None.
"""
    file = os.path.join(dir,'tracklist.txt')
    if not os.path.exists(file):
        return None
    try:
        handle = open(file)
    except IOError, e:
        print "### couldn't open tracklist.txt"
        print "### error was [%s]" % e
        return None
    # we will track the unique artists. if there's more than one,
    # then the album is officially by "Various Artists". Because I say so.
    artists = []
    # first line is the album title. end of.
    album_title = handle.readline()
    # this matches the YAML we want to spit out
    album = { 'album': album_title.rstrip(),
              'songs': [] }
    songs = handle.readlines()
    for line_num, song in enumerate(songs):
        # 0-indexed
        line_num = line_num + 1
        try:
            num, artist, name = song.rstrip().split('\t')
        except ValueError:
            # we bail at the very first error!
            print "### error with track [%s]" % line_num
            print "### [%s]" % song.rstrip()
            return None
        else:
            album['songs'].append({ 'num': int(num),
                                    'artist': str(artist),
                                    'name': str(name) })
            if artist not in artists:
                artists.append(artist)
    # hmm. I thought I was going to use this, but I haven't yet. Maybe we'll
    # do something with the TPE2 tag, or use it to decide between
    # Various Artists vs <Artist> when moving the directory, if/when I get
    # round to writing that part. Who knows how useful it will be?
    if len(artists) > 1:
        album['compilation'] = True
    album['total'] = len(album['songs'])
    return album
 
def fix_unknown_album(arg, dirname, fnames):
    """ Path-walking tracks.sh emitter.
 
This function is called from by os.path.walk. For each directory, if it
contains a file called 'tracklist.txt' it tries to parse it; if that works,
it uses the info in it to create a file called 'tracks.sh' in the same
directory. Finally, it renames 'tracklist.txt' to 'tracklist-done.txt'.
 
tracks.sh contains the commands to tag and rename the mp3s according to
the tracklist.
"""
    # ignore the top level directory
    if dirname == base:
        return
    if 'tracklist.txt' in fnames:
        print "### found tracklist.txt in [%s]" % dirname
        tracklist = parse_tracklist(dirname)
        if tracklist:
            path = os.path.join(base, dirname)
            # EPIC PORK WIN
            # ahem
            # what I mean by that is, the pork.py Renderer does everything
            # we want already. there's no need to create any yaml first,
            # all pork.py does is turn that back into the dictionaries
            # we've already created.
            target = os.path.join(path, 'tracks.sh')
            config.update({'target': target})
            Renderer(config, tracklist).spit()
            os.rename(os.path.join(path, 'tracklist.txt'),
                      os.path.join(path, 'tracklist-done.txt'))
        else:
            print "### didn't parse, ignoring"
    else:
        print "### no tracklist.txt in [%s], ignoring" % dirname
 
def main(dir=base):
    os.path.walk(dir, fix_unknown_album, None)
 
if __name__ == '__main__':
    # accept an argument, otherwise just go to the default place
    if len(sys.argv) > 1:
        main(sys.argv[1])
    else:
        main()