-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslicer.py
140 lines (128 loc) · 4.88 KB
/
slicer.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
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
import pprint
import sys
import os
import json
import argparse
import numpy as np
import scipy.io.wavfile as sp
def samples_to_string(samples, rate, usems=False):
minutes = (st / rate) / 60
seconds = (st / rate) % 60
if (usems):
ms = ((st % rate) / (rate / 1000.))
return "%d:%02d:%03d" % (minutes, seconds, ms)
else:
return "%d:%02d" % (minutes, seconds)
def string_to_samples(s, rate):
items = s.split(":")
minutes = int(items[0])
seconds = int(items[1], base=10)
if (len(items) == 3):
ms = int(times[2])
return (minutes * rate * 60) + (seconds * rate) + int(ms * (rate / 1000.))
else:
return (minutes * rate * 60) + (seconds * rate)
def get_amplitudes(d, rate, samples_per_slice, shift_per_slice):
samples_per_slice = int(samples_per_slice * rate)
shift_per_slice = int(shift_per_slice * rate)
loc_max = len(d) - samples_per_slice
shifts = samples_per_slice / shift_per_slice
amplitudes = []
for loc in xrange(0, loc_max, shift_per_slice):
sample = abs(np.mean((d[loc:(loc + samples_per_slice)]), 1))
amplitudes.append({'start': loc, 'end': loc + samples_per_slice,
'mean': np.mean(sample)})
return amplitudes
def get_deltas(amps):
deltas = []
for i in xrange(len(amps) - 1):
deltas.append({
'delta': amps[i+1]['mean'] - amps[i]['mean'],
'start_val': amps[i]['mean'],
'end_val': amps[i+1]['mean'],
'start_sample': amps[i]['start'],
'end_sample': amps[i+1]['end']
})
return deltas
def find_track_gaps(d):
large_delta = 300
small_delta = 300
small_amp = 800
min_interest_distance = 60 * 44100
interest = []
for i in xrange(len(d) - 1):
gap = d[i]
chg = d[i+1]
if (abs(gap['delta']) < small_delta and chg['delta'] > large_delta and
((gap['start_val'] + gap['end_val']) / 2) < small_amp and
((len(interest) == 0 and
gap['start_sample'] >= min_interest_distance) or
(interest[-1]['end_sample'] + min_interest_distance <=
gap['end_sample']))):
interest.append(gap)
final_sample = d[-1]['end_sample']
if (final_sample - interest[-1]['end_sample'] < min_interest_distance):
interest = interest[:-1]
return interest
def process_audio_file(data, rate):
amps = get_amplitudes(data, rate, 1, 0.5)
deltas = get_deltas(amps)
track_pts = find_track_gaps(deltas)
print "Found %d songs." % (len(track_pts) + 1)
#pprint.pprint(track_pts)
pullback = rate / 2
song_starts = [i['end_sample'] - pullback for i in track_pts]
return song_starts
def chop_audio_file(data, rate, song_starts, save_dir):
# Last item in song_starts should be the end of the last file
if not os.path.exists(save_dir):
os.makedirs(save_dir)
for i in xrange(len(song_starts) + 1):
filename = "%s/%03d.wav" % (save_dir, i)
if (i == 0):
start = 0
end = song_starts[i]
elif (i < len(song_starts)):
start = song_starts[i-1]
end = song_starts[i]
else:
start = song_starts[-1]
end = len(data)
print "Writing file %s:%s to %s..." % (filename,
samples_to_string(state, rate), samples_to_string(end, rate))
sp.write(filename, rate, data[start:end])
def save_song_starts(filename, rate, song_starts):
start_strings = [samples_to_string(st, rate, usems=True) for st in song_starts]
f = open(filename, 'w')
json.dump(start_strings, f)
f.close()
def load_song_starts(filename, rate):
f = open(filename, 'r')
start_strs = json.load(f)
f.close();
starts = [string_to_samples(s) for s in start_strs]
return starts
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="slice vinyl rips into individual tracks")
parser.add_argument("infile", help="input audio file")
parser.add_argument("outfile",
help="output filename (for -d) or directory")
group = parser.add_mutually_exclusive_group()
group.add_argument("-d", "--detect",
help="detect track starts and write to file",
action="store_true")
group.add_argument("-c", "--chop", type=str,
metavar="slice-file",
help="chop file using given track start file")
args = parser.parse_args()
(rate, data) = sp.read(args.infile, mmap=True)
if args.detect:
starts = process_audio_file(data, rate)
save_song_starts(args.outfile, rate, starts)
elif args.chop is not None:
starts = load_song_starts(args.chop, rate)
chop_audio_file(data, rate, starts, args.outfile)
else:
starts = process_audio_file(data)
chop_audio_file(data, rate, starts, args.outfile)