Update of /cvsroot/freevo/kaa/metadata/src/audio
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14035/src/audio

Added Files:
        .cvsignore __init__.py ac3info.py eyed3info.py flacinfo.py 
        id3.py m4ainfo.py ogginfo.py pcminfo.py webradioinfo.py 
Log Message:
move current mmpython cvs to kaa.metadata

--- NEW FILE: .cvsignore ---
*.pyc *.pyo

--- NEW FILE: flacinfo.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# flacinfo.py - flac file parser
# -----------------------------------------------------------------------------
# $Id: flacinfo.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import struct
import re
import logging

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory
import ogginfo

# get logging object
log = logging.getLogger('metadata')

# See: http://flac.sourceforge.net/format.html

class FlacInfo(mediainfo.MusicInfo):
    def __init__(self,file):
        mediainfo.MusicInfo.__init__(self)
        if file.read(4) != 'fLaC':
            raise mediainfo.KaaMetadataParseError()

        while 1:
            (blockheader,) = struct.unpack('>I',file.read(4))
            lastblock = (blockheader >> 31) & 1
            type = (blockheader >> 24) & 0x7F
            numbytes = blockheader & 0xFFFFFF
            log.debug("Last?: %d, NumBytes: %d, Type: %d" % \
                      (lastblock, numbytes, type))
            # Read this blocks the data
            data = file.read(numbytes)
            if type == 0:
                # STREAMINFO
                bits = struct.unpack('>L', data[10:14])[0]
                self.samplerate = (bits >> 12) & 0xFFFFF
                self.channels = ((bits >> 9) & 7) + 1
                self.samplebits = ((bits >> 4) & 0x1F) + 1
                md5 = data[18:34]
            elif type == 1:
                # PADDING
                pass
            elif type == 2:
                # APPLICATION
                pass
            elif type == 3:
                # SEEKTABLE
                pass
            elif type == 4:
                # VORBIS_COMMENT
                skip, self.vendor = self._extractHeaderString(data)
                num, = struct.unpack('<I', data[skip:skip+4])
                start = skip+4
                header = {}
                for i in range(num):
                    (nextlen, s) = self._extractHeaderString(data[start:])
                    start += nextlen
                    a = re.split('=',s)
                    header[(a[0]).upper()]=a[1]
                if header.has_key('TITLE'):
                    self.title = header['TITLE']
                if header.has_key('ALBUM'):
                    self.album = header['ALBUM']
                if header.has_key('ARTIST'):
                    self.artist = header['ARTIST']
                if header.has_key('COMMENT'):
                    self.comment = header['COMMENT']
                if header.has_key('DATE'):
                    self.date = header['DATE']
                if header.has_key('ENCODER'):
                    self.encoder = header['ENCODER']
                if header.has_key('TRACKNUMBER'):
                    self.trackno = header['TRACKNUMBER']

                self.appendtable('VORBISCOMMENT', header)
            elif type == 5:
                # CUESHEET
                pass
            else:
                # UNKNOWN TYPE
                pass
            if lastblock:
                break

    def _extractHeaderString(self,header):
        len = struct.unpack( '<I', header[:4] )[0]
        return (len+4,unicode(header[4:4+len], 'utf-8'))


factory.register( 'application/flac', ('flac',), mediainfo.TYPE_MUSIC,
                       FlacInfo )

--- NEW FILE: ac3info.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# ac3info.py - AC3 file parser
# -----------------------------------------------------------------------------
# $Id: ac3info.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import struct

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

# http://www.atsc.org/standards/a_52a.pdf
# fscod: Sample rate code, 2 bits
#  00  48
#  01  44.1
#  10  32
#  11  reserved

FSCOD = [ 48000, 44100, 32000, 0 ]

# bsmod: Bit stream mode, 3 bits
# bsmod acmod Type of Service
#  000  any main audio service: complete main (CM)
#  001  any main audio service: music and effects (ME)
#  010  any associated service: visually impaired (VI)
#  011  any associated service: hearing impaired (HI)
#  100  any associated service: dialogue (D)
#  101  any associated service: commentary (C)
#  110  any associated service: emergency (E)
#  111  001 associated service: voice over (VO)
#  111  010 - 111  main audio service: karaoke
#
# acmod: Audio coding mode, 3 bits
#  000  1+1 2 Ch1, Ch2
#  001  1/0 1 C
#  010  2/0 2 L, R
#  011  3/0 3 L, C, R
#  100  2/1 3 L, R, S
#  101  3/1 4 L, C, R, S
#  110  2/2 4 L, R, SL, SR
#  111  3/2 5 L, C, R, SL, SR

ACMOD = [ ( '1+1', 2, 'Ch1, Ch2' ),
          ( '1/0', 1, 'C' ),
          ( '2/0', 2, 'L, R' ),
          ( '3/0', 3, 'L, C, R' ),
          ( '2/1', 3, 'L, R, S' ),
          ( '3/1', 4, 'L, C, R, S' ),
          ( '2/2', 4, 'L, R, SL, SR' ),
          ( '3/2', 5, 'L, C, R, SL, SR' ) ]


# dsurmod: Dolby surround mode, 2 bits
#  00  not indicated
#  01  Not Dolby Surround encoded
#  10  Dolby Surround encoded
#  11  reserved
#
# lfeon: Low frequency effects channel on, 1 bit
# This bit has a value of 1 if the lfe (sub woofer) channel is on, and a
# value of 0 if the lfe channel is off.
#
# frmsizcod:
#  byte&0x3e = 0x00        \b, 32 kbit/s
#  byte&0x3e = 0x02        \b, 40 kbit/s
#  byte&0x3e = 0x04        \b, 48 kbit/s
#  byte&0x3e = 0x06        \b, 56 kbit/s
#  byte&0x3e = 0x08        \b, 64 kbit/s
#  byte&0x3e = 0x0a        \b, 80 kbit/s
#  byte&0x3e = 0x0c        \b, 96 kbit/s
#  byte&0x3e = 0x0e        \b, 112 kbit/s
#  byte&0x3e = 0x10        \b, 128 kbit/s
#  byte&0x3e = 0x12        \b, 160 kbit/s
#  byte&0x3e = 0x14        \b, 192 kbit/s
#  byte&0x3e = 0x16        \b, 224 kbit/s
#  byte&0x3e = 0x18        \b, 256 kbit/s
#  byte&0x3e = 0x1a        \b, 320 kbit/s
#  byte&0x3e = 0x1c        \b, 384 kbit/s
#  byte&0x3e = 0x1e        \b, 448 kbit/s
#  byte&0x3e = 0x20        \b, 512 kbit/s
#  byte&0x3e = 0x22        \b, 576 kbit/s
#  byte&0x3e = 0x24        \b, 640 kbit/s

FRMSIZCOD = [ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192,
              224, 256, 320, 384, 448, 512, 576, 640 ]

class AC3Info(mediainfo.MusicInfo):
    def __init__(self,file):
        mediainfo.MusicInfo.__init__(self)
        if file.name.endswith('.ac3'):
            # when the ending is ac3, force the detection. It may not be
            # necessary the the header is at the beginning but in the first
            # 2000 bytes
            check_length = 1000
        else:
            check_length = 1
        for i in range(check_length):
            if file.read(2) == '\x0b\x77':
                break
        else:
            raise mediainfo.KaaMetadataParseError()

        info = struct.unpack('<HBBBB',file.read(6))
        self.samplerate = FSCOD[info[1] >> 6]
        self.bitrate = FRMSIZCOD[(info[1] & 0x3F) >> 1] * 1000
        bsmod = info[2] & 0x7
        channels = ACMOD[info[3] >> 5]
        acmod = info[3] >> 5
        self.channels = ACMOD[acmod][1]
        bits = 0
        if acmod & 0x01 and not acmod == 0x01:
            bits += 2
        if acmod & 0x04:
            bits += 2
        if acmod == 0x02:
            bits += 2

        # info is now 5 bits of info[3] and all bits of info[4] ( == 13 bits)
        # 'bits' bits (0-6) bits are information we don't need, after that,
        # the bit we need is lfeon.
        info = (((info[3] & 0x1F) << 8) + info[4])

        # now we create the mask we need (based on 'bits')
        # the bit number 13 - 'bits' is what we want to read
        for i in range(13 - bits - 1):
            info = info >> 1
        if info & 1:
            # subwover
            self.channels += 1
        self.codec = 'AC3'
        self.mime = 'audio/ac3'

factory.register( 'audio/ac3', ('ac3',), mediainfo.TYPE_MUSIC, AC3Info )

--- NEW FILE: eyed3info.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# eyed3info.py - mp3 file parser using eyeD3
# -----------------------------------------------------------------------------
# $Id: eyed3info.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import os
import re
import logging
import struct

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

# eyeD3 imports
from eyeD3 import tag as eyeD3_tag
from eyeD3 import frames as eyeD3_frames

import id3 as id3info

# get logging object
log = logging.getLogger('metadata')

# http://www.omniscia.org/~vivake/python/MP3Info.py

MP3_INFO_TABLE = { "APIC": "picture",
                   "LINK": "link",
                   "TALB": "album",
                   "TCOM": "composer",
                   "TCOP": "copyright",
                   "TDOR": "release",
                   "TYER": "date",
                   "TEXT": "text",
                   "TIT2": "title",
                   "TLAN": "language",
                   "TLEN": "length",
                   "TMED": "media_type",
                   "TPE1": "artist",
                   "TPE2": "artist",
                   "TRCK": "trackno",
                   "TPOS": "discs"}

_bitrates = [
   [ # MPEG-2 & 2.5
   [0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,None], # Layer 1
   [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None], # Layer 2
   [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,None]  # Layer 3
   ],

   [ # MPEG-1
   [0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,None], # Layer 1
   [0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,None], # Layer 2
   [0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,None]  # Layer 3
   ]
   ]

_samplerates = [
   [ 11025, 12000,  8000, None], # MPEG-2.5
   [  None,  None,  None, None], # reserved
   [ 22050, 24000, 16000, None], # MPEG-2
   [ 44100, 48000, 32000, None], # MPEG-1
   ]

_modes = [ "stereo", "joint stereo", "dual channel", "mono" ]

_MP3_HEADER_SEEK_LIMIT = 4096

class eyeD3Info(mediainfo.MusicInfo):

   fileName       = str();
   fileSize       = int();

   def __init__(self, file, tagVersion = eyeD3_tag.ID3_ANY_VERSION):
      mediainfo.MusicInfo.__init__(self)
      self.fileName = file.name;
      self.mime = 'audio/mp3'

      if not eyeD3_tag.isMp3File(file.name):
         raise mediainfo.KaaMetadataParseError()

      id3 = None
      try:
         id3 = eyeD3_tag.Mp3AudioFile(file.name)
      except eyeD3_tag.TagException:
         try:
            id3 = eyeD3_tag.Mp3AudioFile(file.name)
         except eyeD3_tag.InvalidAudioFormatException:
            # File is not an MP3
            raise mediainfo.KaaMetadataParseError()
         except:
            # The MP3 tag decoder crashed, assume the file is still
            # MP3 and try to play it anyway
            if log.level < 30:
               log.exception('mp3 tag parsing %s failed!' % file.name)
      except:
         # The MP3 tag decoder crashed, assume the file is still
         # MP3 and try to play it anyway
         if log.level < 30:
            log.exception('mp3 tag parsing %s failed!' % file.name)

      if not id3:
         # let's take a look at the header
         s = file.read(4096)
         if not s[:3] == 'ID3':
            # no id3 tag header, not good
            if not re.compile(r'0*\xFF\xFB\xB0\x04$').search(s):
               # again, not good
               if not re.compile(r'0*\xFF\xFA\xB0\x04$').search(s):
                  # that's it, it is no mp3 at all
                  raise mediainfo.KaaMetadataParseError()
                  
      try:
         if id3 and id3.tag:
            log.debug(id3.tag.frames)
            for k, var in MP3_INFO_TABLE.items():
               if id3.tag.frames[k]:
                  if k == 'APIC':
                     setattr(self, var, id3.tag.frames[k][0])
                  else:
                     setattr(self, var, id3.tag.frames[k][0].text)
            if id3.tag.getYear():
               self.date = id3.tag.getYear()
            tab = {}
            for f in id3.tag.frames:
               if f.__class__ is eyeD3_frames.TextFrame:
                  tab[f.header.id] = f.text
               elif f.__class__ is eyeD3_frames.UserTextFrame:
                  tab[f.header.id] = f.text
               elif f.__class__ is eyeD3_frames.DateFrame:
                  tab[f.header.id] = f.date_str
               elif f.__class__ is eyeD3_frames.CommentFrame:
                  tab[f.header.id] = f.comment
               elif f.__class__ is eyeD3_frames.URLFrame:
                  tab[f.header.id] = f.url
               elif f.__class__ is eyeD3_frames.UserURLFrame:
                  tab[f.header.id] = f.url
               elif f.__class__ is eyeD3_frames.ImageFrame:
                  tab[f.header.id] = f
               else:
                  log.debug(f.__class__)
            self.appendtable('id3v2', tab, 'en')

            if id3.tag.frames['TCON']:
               genre = None
               tcon = id3.tag.frames['TCON'][0].text
               try:
                  genre = int(tcon)
               except:
                  try:
                      genre = int(tcon[1:tcon.find(')')])
                  except ValueError:
                     genre = tcon
               if genre is not None:
                  try:
                     self.genre = id3info.GENRE_LIST[genre]
                  except:
                     self.genre = genre
            # and some tools store it as trackno/trackof in TRCK
            if not self['trackof'] and self['trackno'] and \
                   self['trackno'].find('/') > 0:
               self['trackof'] = self['trackno'][self['trackno'].find('/')+1:]
               self['trackno'] = self['trackno'][:self['trackno'].find('/')]
         if id3:
            self.length = id3.getPlayTime()
      except:
         if log.level < 30:
            log.exception('parse error')

      offset, header = self._find_header(file)
      if offset == -1 or header is None:
         return

      self._parse_header(header)


   def _find_header(self, file):
      file.seek(0, 0)
      amount_read = 0

      # see if we get lucky with the first four bytes
      amt = 4

      while amount_read < _MP3_HEADER_SEEK_LIMIT:
         header = file.read(amt)
         if len(header) < amt:
            # awfully short file. just give up.
            return -1, None

         amount_read = amount_read + len(header)

         # on the next read, grab a lot more
         amt = 500

         # look for the sync byte
         offset = header.find(chr(255))
         if offset == -1:
            continue

         # looks good, make sure we have the next 3 bytes after this
         # because the header is 4 bytes including sync
         if offset + 4 > len(header):
            more = file.read(4)
            if len(more) < 4:
               # end of file. can't find a header
               return -1, None
            amount_read = amount_read + 4
            header = header + more

         # the sync flag is also in the next byte, the first 3 bits
         # must also be set
         if ord(header[offset+1]) >> 5 != 7:
            continue

         # ok, that's it, looks like we have the header
         return amount_read - len(header) + offset, header[offset:offset+4]

      # couldn't find the header
      return -1, None


   def _parse_header(self, header):
      # http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html
      bytes = struct.unpack('>i', header)[0]
      mpeg_version =    (bytes >> 19) & 3
      layer =           (bytes >> 17) & 3
      bitrate =         (bytes >> 12) & 15
      samplerate =      (bytes >> 10) & 3
      mode =            (bytes >> 6)  & 3

      if mpeg_version == 0:
         self.version = 2.5
      elif mpeg_version == 2:
         self.version = 2
      elif mpeg_version == 3:
         self.version = 1
      else:
         return

      if layer > 0:
         layer = 4 - layer
      else:
         return

      self.bitrate = _bitrates[mpeg_version & 1][layer - 1][bitrate]
      self.samplerate = _samplerates[mpeg_version][samplerate]

      if self.bitrate is None or self.samplerate is None:
         return

      self.mode = _modes[mode]
      self.keys.append('mode')


factory.register( 'audio/mp3', ('mp3',), mediainfo.TYPE_MUSIC, eyeD3Info )

--- NEW FILE: m4ainfo.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# m4ainfo.py - m4a file parser
# -----------------------------------------------------------------------------
# $Id: m4ainfo.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Aubin Paul <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# Based on a sample implementation posted to daap-dev mailing list by
# Bob Ippolito <[EMAIL PROTECTED]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import struct
import logging

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

# get logging object
log = logging.getLogger('metadata')

class Mpeg4(mediainfo.MusicInfo):
    def __init__(self, file):
        self.containerTags = ('moov', 'udta', 'trak', 'mdia', 'minf', 'dinf',
                              'stbl', 'meta', 'ilst', '----')
        self.skipTags = {'meta':4 }

        mediainfo.MusicInfo.__init__(self)
        self.valid = 0
        returnval = 0
        while returnval == 0:
            try:
                self.readNextTag(file)
            except ValueError:
                returnval = 1
        if not self.valid:
            raise mediainfo.KaaMetadataParseError()


    def readNextTag(self, file):
        length, name = self.readInt(file), self.read(4, file)
        length -= 8
        if length < 0 or length > 1000:
            raise ValueError, "Oops?"

        if name in self.containerTags:
            self.read(self.skipTags.get(name, 0), file)
            data = '[container tag]'
        else:
            data = self.read(length, file)
        if name == '\xa9nam':
            self.title = data[8:]
            self.valid = 1
        if name == '\xa9ART':
            self.artist = data[8:]
            self.valid = 1
        if name == '\xa9alb':
            self.album = data[8:]
            self.valid = 1
        if name == 'trkn':
            # Fix this
            self.trackno = data
            self.valid = 1
        if name == '\xa9day':
            self.year = data[8:]
            self.valid = 1
        if name == '\xa9too':
            self.encoder = data[8:]
            self.valid = 1
        return 0

    def read(self, b, file):
        data = file.read(b)
        if len(data) < b:
            raise ValueError, "EOF"
        return data

    def readInt(self, file):
        return struct.unpack('>I', self.read(4, file))[0]


factory.register( 'application/m4a', ('m4a',), mediainfo.TYPE_MUSIC,
                       Mpeg4 )


--- NEW FILE: ogginfo.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# ogginfo.py - ogg file parser
# -----------------------------------------------------------------------------
# $Id: ogginfo.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import re
import os
import stat
import struct
import logging

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

# get logging object
log = logging.getLogger('metadata')

VORBIS_PACKET_INFO = '\01vorbis'
VORBIS_PACKET_HEADER = '\03vorbis'
VORBIS_PACKET_SETUP = '\05vorbis'

class OggInfo(mediainfo.MusicInfo):
    def __init__(self,file):
        mediainfo.MusicInfo.__init__(self)
        h = file.read(4+1+1+20+1)
        if h[:5] != "OggS\00":
            log.info("Invalid header")
            raise mediainfo.KaaMetadataParseError()
        if ord(h[5]) != 2:
            log.info("Invalid header type flag (trying to go ahead anyway)")
        self.pageSegCount = ord(h[-1])
        # Skip the PageSegCount
        file.seek(self.pageSegCount,1)
        h = file.read(7)
        if h != VORBIS_PACKET_INFO:
            log.info("Wrong vorbis header type, giving up.")
            raise mediainfo.KaaMetadataParseError()

        self.mime = 'application/ogg'
        header = {}
        info = file.read(23)
        self.version, self.channels, self.samplerate, bitrate_max, \
                      self.bitrate, bitrate_min, blocksize, \
                      framing = struct.unpack('<IBIiiiBB',info[:23])
        # INFO Header, read Oggs and skip 10 bytes
        h = file.read(4+10+13)
        if h[:4] == 'OggS':
            (serial, pagesequence, checksum, numEntries) = \
                     struct.unpack( '<14xIIIB', h )
            # skip past numEntries
            file.seek(numEntries,1)
            h = file.read(7)
            if h != VORBIS_PACKET_HEADER:
                # Not a corrent info header
                return
            self.encoder = self._extractHeaderString(file)
            numItems = struct.unpack('<I',file.read(4))[0]
            for i in range(numItems):
                s = self._extractHeaderString(file)
                a = re.split('=',s)
                header[(a[0]).upper()]=a[1]
            # Put Header fields into info fields
            if header.has_key('TITLE'):
                self.title = header['TITLE']
            if header.has_key('ALBUM'):
                self.album = header['ALBUM']
            if header.has_key('ARTIST'):
                self.artist = header['ARTIST']
            if header.has_key('COMMENT'):
                self.comment = header['COMMENT']
            if header.has_key('DATE'):
                self.date = header['DATE']
            if header.has_key('ENCODER'):
                self.encoder = header['ENCODER']
            if header.has_key('TRACKNUMBER'):
                self.trackno = header['TRACKNUMBER']
            self.type = 'OGG Vorbis'
            self.subtype = ''
            self.length = self._calculateTrackLength(file)
            self.appendtable('VORBISCOMMENT',header)


    def _extractHeaderString(self,f):
        len = struct.unpack( '<I', f.read(4) )[0]
        return unicode(f.read(len), 'utf-8')


    def _calculateTrackLength(self,f):
        # seek to the end of the stream, to avoid scanning the whole file
        if (os.stat(f.name)[stat.ST_SIZE] > 20000):
            f.seek(os.stat(f.name)[stat.ST_SIZE]-10000)

        # read the rest of the file into a buffer
        h = f.read()
        granule_position = 0
        # search for each 'OggS' in h
        if len(h):
            idx = h.rfind('OggS')
            if idx < 0:
                return 0
            pageSize = 0
            h = h[idx+4:]
            (check, type, granule_position, absPos, serial, pageN, crc, \
             segs) = struct.unpack( '<BBIIIIIB', h[:23] )
            if check != 0:
                log.debug(h[:10])
                return
            log.debug("granule = %d / %d" % (granule_position, absPos))
        # the last one is the one we are interested in
        return (granule_position / self.samplerate)


factory.register( 'application/ogg', ('ogg',), mediainfo.TYPE_MUSIC,
                       OggInfo )

--- NEW FILE: id3.py ---
GENRE_LIST = {
       0 : 'Blues',
       1 : 'Classic Rock',
       2 : 'Country',
       3 : 'Dance',
       4 : 'Disco',
       5 : 'Funk',
       6 : 'Grunge',
       7 : 'Hip-Hop',
       8 : 'Jazz',
       9 : 'Metal',
      10 : 'New Age',
      11 : 'Oldies',
      12 : 'Other',
      13 : 'Pop',
      14 : 'R&B',
      15 : 'Rap',
      16 : 'Reggae',
      17 : 'Rock',
      18 : 'Techno',
      19 : 'Industrial',
      20 : 'Alternative',
      21 : 'Ska',
      22 : 'Death Metal',
      23 : 'Pranks',
      24 : 'Soundtrack',
      25 : 'Euro-Techno',
      26 : 'Ambient',
      27 : 'Trip-Hop',
      28 : 'Vocal',
      29 : 'Jazz+Funk',
      30 : 'Fusion',
      31 : 'Trance',
      32 : 'Classical',
      33 : 'Instrumental',
      34 : 'Acid',
      35 : 'House',
      36 : 'Game',
      37 : 'Sound Clip',
      38 : 'Gospel',
      39 : 'Noise',
      40 : 'AlternRock',
      41 : 'Bass',
      42 : 'Soul',
      43 : 'Punk',
      44 : 'Space',
      45 : 'Meditative',
      46 : 'Instrumental Pop',
      47 : 'Instrumental Rock',
      48 : 'Ethnic',
      49 : 'Gothic',
      50 : 'Darkwave',
      51 : 'Techno-Industrial',
      52 : 'Electronic',
      53 : 'Pop-Folk',
      54 : 'Eurodance',
      55 : 'Dream',
      56 : 'Southern Rock',
      57 : 'Comedy',
      58 : 'Cult',
      59 : 'Gangsta',
      60 : 'Top 40',
      61 : 'Christian Rap',
      62 : 'Pop/Funk',
      63 : 'Jungle',
      64 : 'Native American',
      65 : 'Cabaret',
      66 : 'New Wave',
      67 : 'Psychadelic',
      68 : 'Rave',
      69 : 'Showtunes',
      70 : 'Trailer',
      71 : 'Lo-Fi',
      72 : 'Tribal',
      73 : 'Acid Punk',
      74 : 'Acid Jazz',
      75 : 'Polka',
      76 : 'Retro',
      77 : 'Musical',
      78 : 'Rock & Roll',
      79 : 'Hard Rock',

      
    # The following genres are Winamp extensions

      
      80 : 'Folk',
      81 : 'Folk-Rock',
      82 : 'National Folk',
      83 : 'Swing',
      84 : 'Fast Fusion',
      85 : 'Bebob',
      86 : 'Latin',
      87 : 'Revival',
      88 : 'Celtic',
      89 : 'Bluegrass',
      90 : 'Avantgarde',
      91 : 'Gothic Rock',
      92 : 'Progressive Rock',
      93 : 'Psychedelic Rock',
      94 : 'Symphonic Rock',
      95 : 'Slow Rock',
      96 : 'Big Band',
      97 : 'Chorus',
      98 : 'Easy Listening',
      99 : 'Acoustic',
     100 : 'Humour',
     101 : 'Speech',
     102 : 'Chanson',
     103 : 'Opera',
     104 : 'Chamber Music',
     105 : 'Sonata',
     106 : 'Symphony',
     107 : 'Booty Bass',
     108 : 'Primus',
     109 : 'Porn Groove',
     110 : 'Satire',
     111 : 'Slow Jam',
     112 : 'Club',
     113 : 'Tango',
     114 : 'Samba',
     115 : 'Folklore',
     116 : 'Ballad',
     117 : 'Power Ballad',
     118 : 'Rhythmic Soul',
     119 : 'Freestyle',
     120 : 'Duet',
     121 : 'Punk Rock',
     122 : 'Drum Solo',
     123 : 'A capella',
     124 : 'Euro-House',
     125 : 'Dance Hall'
}

--- NEW FILE: webradioinfo.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# webradioinfo.py - read webradio attributes
# -----------------------------------------------------------------------------
# $Id: webradioinfo.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import urlparse
import string
import urllib

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

# http://205.188.209.193:80/stream/1006

ICY_tags = { 'title': 'icy-name',
             'genre': 'icy-genre',
             'bitrate': 'icy-br',
             'caption': 'icy-url',
           }

class WebRadioInfo(mediainfo.MusicInfo):
    def __init__(self, url):
        mediainfo.MusicInfo.__init__(self)
        tup = urlparse.urlsplit(url)
        scheme, location, path, query, fragment = tup
        if scheme != 'http':
            raise mediainfo.KaaMetadataParseError()

        # Open an URL Connection
        fi = urllib.urlopen(url)        

        # grab the statusline
        self.statusline = fi.readline()
        try:
            statuslist = string.split(self.statusline)
        except ValueError:
            # assume it is okay since so many servers are badly configured
            statuslist = ["ICY", "200"]
                    
        if statuslist[1] != "200":
            if fi:
                fi.close()
            raise mediainfo.KaaMetadataParseError()

        self.type = 'audio'
        self.subtype = 'mp3'
        # grab any headers for a max of 10 lines
        linecnt = 0
        tab = {}
        lines = fi.readlines(512)        
        for linecnt in range(0,11):
            icyline = lines[linecnt]
            icyline = icyline.rstrip('\r\n')
            if len(icyline) < 4:
                break
            cidx = icyline.find(':')
            if cidx != -1:
                # break on short line (ie. really should be a blank line)
                # strip leading and trailing whitespace                
                tab[icyline[:cidx].strip()] = icyline[cidx+2:].strip()
        if fi:
            fi.close()
        self.appendtable('ICY', tab)
        self.tag_map = { ('ICY', 'en') : ICY_tags }
        # Copy Metadata from tables into the main set of attributes        
        for k in self.tag_map.keys():
            map(lambda x:self.setitem(x,self.gettable(k[0],k[1]),
                                      self.tag_map[k][x]),
                self.tag_map[k].keys())
        self.bitrate = string.atoi(self.bitrate)*1000        


factory.register( 'text/plain', mediainfo.EXTENSION_STREAM,
                       mediainfo.TYPE_MUSIC, WebRadioInfo )


--- NEW FILE: __init__.py ---

--- NEW FILE: pcminfo.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# pcminfo.py - pcm file parser
# -----------------------------------------------------------------------------
# $Id: pcminfo.py,v 1.1 2005/07/02 16:33:10 dischi Exp $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2005 Thomas Schueppel, Dirk Meyer
#
# First Edition: Thomas Schueppel <[EMAIL PROTECTED]>
# Maintainer:    Dirk Meyer <[EMAIL PROTECTED]>
#
# Please see the file doc/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

# python imports
import sndhdr

# kaa imports
from kaa.metadata import mediainfo
from kaa.metadata import factory

class PCMInfo(mediainfo.AudioInfo):
    def __init__(self,file):
       mediainfo.AudioInfo.__init__(self)
       t = self._what(file)
       if not t:
           raise mediainfo.KaaMetadataParseError()
       (self.type, self.samplerate, self.channels, self.bitrate, \
        self.samplebits) = t
       if self.bitrate == -1:
           # doesn't look right
           raise mediainfo.KaaMetadataParseError()
       self.mime = "audio/%s" % self.type
       
    def _what(self,f):
        """Recognize sound headers"""
        h = f.read(512)
        for tf in sndhdr.tests:
            res = tf(h, f)
            if res:
                return res
        return None


factory.register( 'application/pcm', ('wav','aif','voc','au'),
                       mediainfo.TYPE_AUDIO, PCMInfo )



-------------------------------------------------------
SF.Net email is sponsored by: Discover Easy Linux Migration Strategies
from IBM. Find simple to follow Roadmaps, straightforward articles,
informative Webcasts and more! Get everything you need to get up to
speed, fast. http://ads.osdn.com/?ad_id=7477&alloc_id=16492&op=click
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog

Reply via email to