-
Notifications
You must be signed in to change notification settings - Fork 0
/
group.py
executable file
·111 lines (92 loc) · 3.27 KB
/
group.py
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
#!/usr/bin/env python
#
# Create a list of candidates for mashups given a folder of music that has been
# tagged with analyze.py.
#
# The basic algorithm for grouping songs is as follows:
# * For all songs, generate the set of all "reasonable" BPMs. This is
# essentially the bpm +/- some number n where we also include doubles and
# halves of the song's BPM that fall within a certain range.
# * Create a histogram of these reasonable BPMs.
# * Allow the user to either:
# ** dump out a file of all the mappings, or
# ** query the histogram for song matches.
# * The above is done with or without using key.
#
# Author: Yacin Nadji <yacin@gatech.edu>
#
import sys
from optparse import OptionParser
from functools import partial
from collections import defaultdict
from mutagen.id3 import ID3, ID3NoHeaderError
from mutagen.easyid3 import EasyID3
sys.path.append('wulib')
from wulib import flatten, rwalk
def goodbpm(bpm, minbpm=60, maxbpm=190):
return bpm > minbpm and bpm < maxbpm
def poweroftwos(bpm):
return [bpm / x for x in range(2, 8, 2)] + \
[bpm] + \
[bpm * x for x in range(2, 8, 2)]
def allbpms(bpm, maxdiff=5, minbpm=60, maxbpm=190):
doubles = poweroftwos(bpm)
bpms = []
for dbl in doubles:
bpms.append([dbl - x for x in range(1, maxdiff + 1)])
bpms.append([dbl])
bpms.append([dbl + x for x in range(1, maxdiff + 1)])
isgood = partial(goodbpm, minbpm=minbpm, maxbpm=maxbpm)
return filter(isgood, flatten(bpms))
def gettag(tags, tagname):
try: return tags[tagname][0]
except KeyError: return ''
def artist(query):
"""Interactive search function. Search by artist."""
pass
def title(query):
"""Interactive search function. Search by title."""
pass
def bpm(query):
"""Interactive search function. Search by bpm."""
pass
def interact(interactlocals):
import IPython.ipapi
IPython.ipapi.launch_new_instance(interactlocals)
def main(argv):
"""main function for standalone usage"""
usage = "usage: %prog [options] dir"
parser = OptionParser(usage=usage)
parser.add_option('-f', '--full', default=False, action='store_true',
help='Dump ALL groups')
(options, args) = parser.parse_args(argv)
if len(args) != 1:
parser.print_help()
return 2
# do stuff
mp3s = rwalk(args[0], '*.mp3')
bpms = defaultdict(list)
for mp3 in mp3s:
try:
tags = ID3(mp3)
easytags = EasyID3(mp3)
except ID3NoHeaderError:
continue
if 'TXXX:mashupid' in tags:
for pitchedbpm in allbpms(int(float(tags.get('TBPM').text[0]))):
bpms[pitchedbpm].append((mp3, gettag(easytags, 'artist'),
gettag(easytags, 'title'),
gettag(easytags, 'genre')))
bpmgroups = sorted(bpms.keys())
if options.full:
for bpm in bpmgroups:
print('BPM: %d\n' % bpm)
for path, artist, title, genre in bpms[bpm]:
print('File: %s' % path)
print('Artist: %s' % artist)
print('Title: %s' % title)
print('Genre: %s' % genre)
else:
interact()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))