ewa.mp3

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