-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmp3slice.py
170 lines (148 loc) · 4.71 KB
/
mp3slice.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
'''
This script cuts an audio file into multiple files when you provide the
timestamps and titles for each.
> mp3slice bigfile.mp3 00:00-01:00 part1.mp3 01:00-02:00 part2.mp3
'''
import argparse
import os
import sys
from voussoirkit import bytestring
def parse_rules(lines):
rules = []
for (times, title) in lines[::-1]:
rule = {'title': title}
(start, end) = hyphen_range(times)
if start is None:
raise ValueError('Null start')
rule['start'] = start
if end is None and len(rules) > 0:
end = rules[-1]['start']
rule['end'] = end
rules.append(rule)
rules.sort(key=lambda x: x.get('start'))
return rules
def read_rulefile(filename):
text = None
for encoding in [None, 'utf-8']:
try:
with open(filename, 'r', encoding=encoding) as handle:
text = handle.read()
break
except UnicodeError:
pass
else:
raise UnicodeError()
lines = [l.strip() for l in text.strip().splitlines()]
lines = [l for l in lines if l]
rules = [l.split(maxsplit=1) for l in lines]
return parse_rules(rules)
def chunk_sequence(sequence, chunk_length, allow_incomplete=True):
'''
Given a sequence, divide it into sequences of length `chunk_length`.
allow_incomplete:
If True, allow the final chunk to be shorter if the
given sequence is not an exact multiple of `chunk_length`.
If False, the incomplete chunk will be discarded.
'''
(complete, leftover) = divmod(len(sequence), chunk_length)
if not allow_incomplete:
leftover = 0
chunk_count = complete + min(leftover, 1)
chunks = []
for x in range(chunk_count):
left = chunk_length * x
right = left + chunk_length
chunks.append(sequence[left:right])
return chunks
def hyphen_range(s):
'''
Given a string like '1-3', return numbers (1, 3) representing lower
and upper bounds.
Supports bytestring.parsebytes and hh:mm:ss format, for example
'1k-2k', '10:00-20:00', '4gib-'
'''
s = s.strip()
s = s.replace(' ', '')
if not s:
return (None, None)
parts = s.split('-')
parts = [part.strip() or None for part in parts]
if len(parts) == 1:
low = parts[0]
high = None
elif len(parts) == 2:
(low, high) = parts
else:
raise ValueError('Too many hyphens')
low = _unitconvert(low)
high = _unitconvert(high)
if low is not None and high is not None and low > high:
raise ValueError(s)
return low, high
def hms_to_seconds(hms):
'''
Convert hh:mm:ss string to an integer seconds.
'''
hms = hms.split(':')
seconds = 0
if len(hms) == 3:
seconds += int(hms[0])*3600
hms.pop(0)
if len(hms) == 2:
seconds += int(hms[0])*60
hms.pop(0)
if len(hms) == 1:
seconds += float(hms[0])
return seconds
def _unitconvert(value):
'''
When parsing hyphenated ranges, this function is used to convert
strings like "1k" to 1024 and "1:00" to 60.
'''
if value is None:
return None
if ':' in value:
return hms_to_seconds(value)
elif all(c in '0123456789.' for c in value):
return float(value)
else:
return bytestring.parsebytes(value)
def mp3slice_argparse(args):
if len(args.rules) == 1 and os.path.isfile(args.rules[0]):
rules = read_rulefile(args.rules[0])
else:
rules = args.rules
rules = chunk_sequence(rules, 2)
if len(rules[-1]) != 2:
raise ValueError('Odd-number parameters')
rules = parse_rules(rules)
extension = os.path.splitext(args.input_filename)[1]
outputters = []
for rule in rules:
outputter = []
if not rule['title'].endswith(extension):
rule['title'] += extension
outputter.append('-ss')
outputter.append(str(rule['start']))
if rule['end'] is not None:
outputter.append('-to')
outputter.append(str(rule['end']))
outputter.append(' -c copy')
outputter.append('"%s"' % rule['title'])
print(outputter)
outputter = ' '.join(outputter)
outputters.append(outputter)
outputters = ' '.join(outputters)
command = 'ffmpeg -i "%s" %s' % (args.input_filename, outputters)
print(command)
os.system(command)
return 0
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument('input_filename')
parser.add_argument('rules', nargs='+', default=None)
parser.set_defaults(func=mp3slice_argparse)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))