1 import os
2 from struct import unpack
3 import subprocess
4
5 import eyeD3
6
7 from ewa.logutil import debug
8 from ewa.frameinfo import get_frame
9 from ewa.buffutil import buff_chunk_string, buff_chunk_file
10
11
12 -def _sox_splicer(files,
13 buffsize,
14 sox_path='/usr/bin/sox'):
15 pipe=subprocess.Popen([sox_path, ]+ files + ['-t', 'mp3', '-'],
16 stdout=subprocess.PIPE)
17 while 1:
18 stuff=pipe.stdout.read(buffsize)
19 if stuff=='':
20 break
21 yield stuff
22
23 pipe.wait()
24
25
29
30 """
31 splicing engine that uses Tom Clegg's mp3cat.
32 """
33
34 p1=subprocess.Popen(["cat"]+ files,
35 stdout=subprocess.PIPE)
36 p2=subprocess.Popen([mp3cat_path, "-", "-"],
37 stdin=p1.stdout,
38 stdout=subprocess.PIPE)
39 while 1:
40 stuff=p2.stdout.read(buffsize)
41 if stuff=='':
42 break
43 yield stuff
44
45 p1.wait()
46 p2.wait()
47
57
58
60 """
61 if all files are mp3 files and
62 are of the same bitrate, samplerate, and mode,
63 do nothing; otherwise raise an exception
64 """
65 if not files:
66 return
67 res=[]
68 for f in files:
69 res.append(get_vbr_bitrate_samplerate_mode(f))
70 template=res[0]
71 fields=['vbr', 'bitrate', 'samplerate', 'mode']
72 for r in res[1:]:
73 diffs=[r[x]==template[x] for x in range(4)]
74 if False in diffs:
75 msg="%s does not match; expected %s, got %s"
76 idx=diffs.index(False)
77 raise ValueError, msg % (fields[idx],
78 template[idx],
79 r[idx])
80
81
87 """ Returns an iterator that supplies the spliced data from the files listed
88 in chunks not larger than buffsize. ID3 v2 and v1 tags are supplied from
89 the tagfile if provided.
90 """
91
92 if tagfile:
93 fp=open(tagfile, 'rb')
94 try:
95 data=get_id3v2_tags(fp)
96 finally:
97 fp.close()
98
99 if data:
100 for chunk in buff_chunk_string(data, buffsize):
101 yield chunk
102
103 for chunk in splicer(files, buffsize, **splicerKwargs):
104 yield chunk
105
106 if tagfile:
107 endoffset, tag=get_id3v1_offset_and_tag(tagfile)
108 if tag:
109 yield tag
110
112 """
113 returns a 4-tuple: whether the file is VBR,
114 the bitrate, the samplerate, and the mode.
115 """
116 af = eyeD3.Mp3AudioFile(path)
117 return af.getBitRate() + (af.getSampleFreq(), af.header.mode[0].lower())
118
120 """
121 precondition: header is a valid ID3v2 header.
122
123 """
124 id3, vmaj, vrev, flags, size = unpack('>3sBBB4s', header)
125 s = [ord(c) & 127 for c in header[6:10]]
126 size = (s[0] << 21) | (s[1] << 14) | (s[2] << 7) | s[3]
127
128
129 if flags & 8:
130 size += 10
131 else:
132 debug("no footer found, flags is %s", flags)
133
134 return size
135
162
164 size=os.path.getsize(filename)
165 tagidx=size-128
166 fp=open(filename, 'rb')
167 try:
168 fp.seek(tagidx)
169 tag=fp.read(128)
170 if tag[:3]=='TAG':
171 if correct_offset:
172 return _check_last_sync(fp, tagidx), tag
173 else:
174 return tagidx, tag
175 if correct_offset:
176 return _check_last_sync(fp, size), ''
177 else:
178 return size, ''
179 finally:
180 fp.close()
181
182
183 BUFFMAX=8192
184
186 """
187 search back to no more than BUFFMAX
188 to find a valid sync frame, and return the
189 end index of the valid part of the file
190 """
191 where=fp.tell()
192 newidx=max(0, idx-BUFFMAX)
193 buffsize=(idx-newidx)
194 fp.seek(newidx)
195 stuff=fp.read(buffsize)
196 fp.seek(where)
197 prevend=end=len(stuff)
198 while end >= 0:
199 end=stuff.rfind('\xff', 0, end)
200 frlen, frver, frlayer=get_frame(stuff[end:])
201 if frlen==0:
202
203 continue
204 else:
205
206
207 if frlen == prevend-end:
208
209 newidx=idx-(buffsize-prevend)
210
211 return newidx
212 else:
213
214
215
216 prevend=end
217 continue
218
219
220 debug(
221 ('no valid sync frame found in %d bytes '
222 'at end of file before any id3v1 tag; '
223 'no cleanup attempted'),
224 BUFFMAX)
225
226 return idx
227