#!/usr/bin/env python
# mp3cat.py - tiny mp3 frame editor
# by Yusuke Shinyama  *public domain*
#
#  usage: $ ./mp3cat.py 'f[0:100]+f[1000:2000]' a.mp3 b.mp3 > c.mp3
#

import sys, re, fileinput
from struct import unpack
stderr = sys.stderr

# read MPEG frames
BITRATE1 = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
BITRATE2 = [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
SAMPLERATE1 = [44100, 48000, 32000]
SAMPLERATE2 = [22050, 24000, 16000]
SAMPLERATE25 = [11025, 12000, 8000]
def read_frames(frames, fp, verbose=False):
  while 1:
    x = fp.read(4)
    if not x:
      break
    elif x.startswith('TAG'):
      # TAG - ignored
      data = x[3]+fp.read(128-4)
      if verbose:
        print >>stderr, 'TAG', repr(data)
    elif x.startswith('ID3'):
      # ID3 - ignored
      version = x[3]+fp.read(1)
      flags = ord(fp.read(1))
      s = [ ord(c) & 127 for c in fp.read(4) ]
      size = (s[0]<<21) | (s[1]<<14) | (s[2]<<7) | s[3]
      data = fp.read(size)
      if verbose:
        print >>stderr, 'ID3', repr(data)
    else:
      h = unpack('>L', x)[0]
      assert (h & 0xffe00000) == 0xffe00000, '!Frame Sync: %r' % x
      version = (h & 0x00180000) >> 19
      assert version != 1
      assert (h & 0x00060000) == 0x00020000, '!Layer3'
      protected = not (h & 0x00010000)
      b = (h & 0xf000) >> 12
      assert b != 0 and b != 15, '!Bitrate'
      if version == 3:                      # V1
        bitrate = BITRATE1[b]
      else:                                 # V2 or V2.5
        bitrate = BITRATE2[b]
      s = (h & 0x0c00) >> 10
      assert s != 3, '!SampleRate'
      if version == 3:                      # V1
        samplerate = SAMPLERATE1[s]
      elif version == 2:                    # V2
        samplerate = SAMPLERATE2[s]
      elif version == 0:                    # V2.5
        samplerate = SAMPLERATE25[s]
      nsamples = 1152
      if samplerate <= 24000:
        nsamples = 576
      pad = (h & 0x0200) >> 9
      channel = (h & 0xc0) >> 6
      joint = (h & 0x30) >> 4
      copyright = bool(h & 8)
      original = bool(h & 4)
      emphasis = h & 3
      if version == 3:
        framesize = 144000 * bitrate / samplerate + pad
      else:
        framesize = 72000 * bitrate / samplerate + pad
      data = x+fp.read(framesize-4)
      if verbose:
        print >>stderr, 'Frame%d: bitrate=%dk, samplerate=%d, framesize=%d' % \
              (len(frames), bitrate, samplerate, framesize)
      frames.append(data)
  return


# main
PAT = re.compile(r'^[\[\]:0-9f+]*$')
if __name__ == "__main__":
  import sys, getopt
  def usage():
    print "usage: mp3cat.py [-v] expr [file ...] > new.mp3"
    sys.exit(2)
  try:
    (opts, args) = getopt.getopt(sys.argv[1:], "v")
  except getopt.GetoptError:
    usage()
  verbose = False
  for (k, v) in opts:
    if k == "-v": verbose = True
  if len(args) < 2: usage()
  expr = args[0]
  if not PAT.match(expr):
    print 'illegal expr: %r' % expr
    sys.exit(1)
  #
  allframes = []
  for fname in args[1:]:
    fp = file(fname)
    read_frames(allframes, fp, verbose)
    fp.close()
  if expr:
    for f in eval(expr, {'f':allframes}):
      sys.stdout.write(f)
  else:
    print >>stderr, 'Total Frames:', len(allframes)
