Package ewa :: Module mp3
[hide private]
[frames] | no frames]

Source Code for Module ewa.mp3

  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 # and we mean wait 23 pipe.wait()
24 25
26 -def _mp3cat_splicer(files, 27 buffsize, 28 mp3cat_path="/usr/bin/mp3cat"):
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
48 -def _default_splicer(files, buffsize):
49 for filename in files: 50 fp=open(filename, 'rb') 51 # discard tag 52 tag=get_id3v2_tags(fp) 53 endoffset, endtag=get_id3v1_offset_and_tag(filename, True) 54 for chunk in buff_chunk_file(fp, endoffset, buffsize): 55 yield chunk 56 fp.close()
57 58
59 -def mp3_sanity_check(files):
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
82 -def splice(files, 83 tagfile=None, 84 buffsize=2**20, 85 splicer=_default_splicer, 86 **splicerKwargs):
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
111 -def get_vbr_bitrate_samplerate_mode(path):
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
119 -def calculate_id3v2_size(header):
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 # we are expanding the size to 10 bytes 128 # if there is a footer. 129 if flags & 8: 130 size += 10 131 else: 132 debug("no footer found, flags is %s", flags) 133 134 return size
135
136 -def get_id3v2_tags(fp):
137 """ 138 returns the id3v2 tag as a string. fp is an open file; if there 139 is no id3v2 tag the file is left at the same position as when it 140 was found, otherwise it is left at the end of the tag. 141 """ 142 idx=fp.tell() 143 header=fp.read(10) 144 if not header[:3]=='ID3': 145 fp.seek(idx) 146 return '' 147 else: 148 size=calculate_id3v2_size(header) 149 tags=header + fp.read(size) 150 # WORKAROUND for when the footer is not correctly detected 151 idx=fp.tell() 152 buff=fp.read(8192) 153 fp.seek(idx) 154 fr=get_frame(buff) 155 nxfr=get_frame(buff[10:]) 156 157 if fr[0]==0 and nxfr[0]!=0: 158 # we'll conclude that there was a footer 159 debug("inferring a footer") 160 tags+=fp.read(10) 161 return tags
162
163 -def get_id3v1_offset_and_tag(filename, correct_offset=False):
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
185 -def _check_last_sync(fp, idx):
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 # invalid, not a sync at all 203 continue 204 else: 205 # real sync 206 # do we have a full frame? 207 if frlen == prevend-end: 208 # perfect 209 newidx=idx-(buffsize-prevend) 210 #debug("valid frame ends at %d", newidx) 211 return newidx 212 else: 213 #debug("frlen: %d, prevend-end: %d", frlen, prevend-end) 214 # fragmentary frame, delete it 215 #debug("invalid frame at %d", idx-(buffsize-end)) 216 prevend=end 217 continue 218 219 # if we get here, no sync frame was found 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