#if 0
# $Id: riffinfo.py 401 2005-03-15 17:50:45Z dischi $
# $Log$
# Revision 1.33  2005/03/15 17:50:45  dischi
# check for corrupt avi
#
# Revision 1.32  2005/03/04 17:41:29  dischi
# handle broken avi files
#
# Revision 1.31  2004/12/13 10:19:07  dischi
# more debug, support LIST > 20000 (new max is 80000)
#
# Revision 1.30  2004/08/25 16:18:14  dischi
# detect aspect ratio
#
# Revision 1.29  2004/05/24 16:17:09  dischi
# Small changes for future updates
#
# Revision 1.28  2004/01/31 12:23:46  dischi
# remove bad chars from table (e.g. char 0 is True)
#
# Revision 1.27  2003/10/04 14:30:08  dischi
# add audio delay for avi
#
# Revision 1.26  2003/07/10 11:18:11  the_krow
# few more attributes added
#
# Revision 1.25  2003/07/07 21:36:44  dischi
# make fps a float and round it to two digest after the comma
#
# Revision 1.24  2003/07/05 19:36:37  the_krow
# length fixed
# fps introduced
#
# Revision 1.23  2003/07/02 11:17:30  the_krow
# language is now part of the table key
#
# Revision 1.22  2003/07/01 21:06:50  dischi
# no need to import factory (and when, use "from mmpython import factory"
#
# Revision 1.21  2003/06/30 13:17:20  the_krow
# o Refactored mediainfo into factory, synchronizedobject
# o Parsers now register directly at mmpython not at mmpython.mediainfo
# o use mmpython.Factory() instead of mmpython.mediainfo.get_singleton()
# o Bugfix in PNG parser
# o Renamed disc.AudioInfo into disc.AudioDiscInfo
# o Renamed disc.DataInfo into disc.DataDiscInfo
#
# Revision 1.20  2003/06/23 20:48:11  the_krow
# width + height fixes for OGM files
#
# Revision 1.19  2003/06/23 20:38:04  the_krow
# Support for larger LIST chunks because some files did not work.
#
# Revision 1.18  2003/06/20 19:17:22  dischi
# remove filename again and use file.name
#
# Revision 1.17  2003/06/20 19:05:56  dischi
# scan for subtitles
#
# Revision 1.16  2003/06/20 15:29:42  the_krow
# Metadata Mapping
#
# Revision 1.15  2003/06/20 14:43:57  the_krow
# Putting Metadata into MediaInfo from AVIInfo Table
#
# Revision 1.14  2003/06/09 16:10:52  dischi
# error handling
#
# Revision 1.13  2003/06/08 19:53:21  dischi
# also give the filename to init for additional data tests
#
# Revision 1.12  2003/06/08 13:44:58  dischi
# Changed all imports to use the complete mmpython path for mediainfo
#
# Revision 1.11  2003/06/08 13:11:38  dischi
# removed print at the end and moved it into register
#
# Revision 1.10  2003/06/07 23:10:50  the_krow
# Changed mp3 into new format.
#
# Revision 1.9  2003/06/07 22:30:22  the_krow
# added new avinfo structure
#
# Revision 1.8  2003/06/07 21:48:47  the_krow
# Added Copying info
# started changing riffinfo to new AV stuff
#
# Revision 1.7  2003/05/13 12:31:43  the_krow
# + Copyright Notice
#
#
# MMPython - Media Metadata for Python
# Copyright (C) 2003 Thomas Schueppel, Dirk Meyer
#
# 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
# 
# -----------------------------------------------------------------------
#endif

import re
import struct
import string
import fourcc
# import factory

import mmpython
from mmpython import mediainfo


# List of tags
# http://kibus1.narod.ru/frames_eng.htm?sof/abcavi/infotags.htm
# http://www.divx-digest.com/software/avitags_dll.html
# File Format
# http://www.taenam.co.kr/pds/documents/odmlff2.pdf

_print = mediainfo._debug

AVIINFO_tags = { 'title': 'INAM',
                 'artist': 'IART',
                 'product': 'IPRD',
                 'date': 'ICRD',
                 'comment': 'ICMT',
                 'language': 'ILNG',
                 'keywords': 'IKEY',
                 'trackno': 'IPRT',
                 'trackof': 'IFRM',
                 'producer': 'IPRO',
                 'writer': 'IWRI',
                 'genre': 'IGNR',
                 'copyright': 'ICOP',
                 'trackno': 'IPRT',
                 'trackof': 'IFRM',
                 'comment': 'ICMT',
               }



class RiffInfo(mediainfo.AVInfo):
    def __init__(self,file):
        mediainfo.AVInfo.__init__(self)
        # read the header
        h = file.read(12)
        if h[:4] != "RIFF" and h[:4] != 'SDSS':
            self.valid = 0
            return
        self.valid = 1
        self.mime = 'application/x-wave'
        self.has_idx = False
        self.header = {}
        self.junkStart = None
        self.infoStart = None
        self.type = h[8:12]
        self.tag_map = { ('AVIINFO', 'en') : AVIINFO_tags }
        if self.type == 'AVI ':
            self.mime = 'video/avi'
        elif self.type == 'WAVE':
            self.mime = 'application/x-wave'
        try:
            while self.parseRIFFChunk(file):
                pass
        except IOError:
            if mediainfo.DEBUG:
                print 'error in file, stop parsing'

        self.find_subtitles(file.name)
        
        # 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())
        if not self.has_idx:
            _print('WARNING: avi has no index')
            self.corrupt = 1
            self.keys.append('corrupt')
            
        
    def _extractHeaderString(self,h,offset,len):
        return h[offset:offset+len]

    def parseAVIH(self,t):
        retval = {}
        v = struct.unpack('<IIIIIIIIIIIIII',t[0:56])
        ( retval['dwMicroSecPerFrame'],
          retval['dwMaxBytesPerSec'],           
          retval['dwPaddingGranularity'], 
          retval['dwFlags'], 
          retval['dwTotalFrames'],
          retval['dwInitialFrames'],
          retval['dwStreams'],
          retval['dwSuggestedBufferSize'],
          retval['dwWidth'],
          retval['dwHeight'],
          retval['dwScale'],
          retval['dwRate'],
          retval['dwStart'],
          retval['dwLength'] ) = v
        if retval['dwMicroSecPerFrame'] == 0:
            _print("ERROR: Corrupt AVI")
            self.valid = 0
            return {}
        return retval
        
    def parseSTRH(self,t):
        retval = {}
        retval['fccType'] = t[0:4]
        _print("parseSTRH(%s) : %d bytes" % ( retval['fccType'], len(t)))
        if retval['fccType'] != 'auds':
            retval['fccHandler'] = t[4:8]
            v = struct.unpack('<IHHIIIIIIIII',t[8:52])
            ( retval['dwFlags'],
              retval['wPriority'],
              retval['wLanguage'],
              retval['dwInitialFrames'],
              retval['dwScale'],
              retval['dwRate'],
              retval['dwStart'],
              retval['dwLength'],
              retval['dwSuggestedBufferSize'],
              retval['dwQuality'],
              retval['dwSampleSize'],
              retval['rcFrame'], ) = v
        else:
            try:
                v = struct.unpack('<IHHIIIIIIIII',t[8:52])
                ( retval['dwFlags'],
                  retval['wPriority'],
                  retval['wLanguage'],
                  retval['dwInitialFrames'],
                  retval['dwScale'],
                  retval['dwRate'],
                  retval['dwStart'],
                  retval['dwLength'],
                  retval['dwSuggestedBufferSize'],
                  retval['dwQuality'],
                  retval['dwSampleSize'],
                  retval['rcFrame'], ) = v
                self.delay = float(retval['dwStart']) / \
                             (float(retval['dwRate']) / retval['dwScale'])
            except:
                pass
            
        return retval

    def parseSTRF(self,t,strh):
        fccType = strh['fccType']
        retval = {}
        if fccType == 'auds':
            ( retval['wFormatTag'],
              retval['nChannels'],
              retval['nSamplesPerSec'],
              retval['nAvgBytesPerSec'],
              retval['nBlockAlign'],
              retval['nBitsPerSample'],
            ) = struct.unpack('<HHHHHH',t[0:12])
            ai = mediainfo.AudioInfo()
            ai.samplerate = retval['nSamplesPerSec']
            ai.channels = retval['nChannels']
            ai.samplebits = retval['nBitsPerSample']
            ai.bitrate = retval['nAvgBytesPerSec'] * 8
            # TODO: set code if possible
            # http://www.stats.uwa.edu.au/Internal/Specs/DXALL/FileSpec/Languages
            # ai.language = strh['wLanguage']
            try:
                ai.codec = fourcc.RIFFWAVE[retval['wFormatTag']]
            except:
                ai.codec = "Unknown"            
            self.audio.append(ai)  
        elif fccType == 'vids':
            v = struct.unpack('<IIIHH',t[0:16])
            ( retval['biSize'],
              retval['biWidth'],
              retval['biHeight'],
              retval['biPlanes'],
              retval['biBitCount'], ) = v
            retval['fourcc'] = t[16:20]            
            v = struct.unpack('IIIII',t[20:40])
            ( retval['biSizeImage'],
              retval['biXPelsPerMeter'],
              retval['biYPelsPerMeter'],
              retval['biClrUsed'],
              retval['biClrImportant'], ) = v
            vi = mediainfo.VideoInfo()
            try:
                vi.codec = fourcc.RIFFCODEC[t[16:20]]
            except:
                vi.codec = "Unknown"
            vi.width = retval['biWidth']
            vi.height = retval['biHeight']            
            vi.bitrate = strh['dwRate']
            vi.fps = round(float(strh['dwRate'] * 100) / strh['dwScale']) / 100
            vi.length = strh['dwLength'] / vi.fps 
            self.video.append(vi)  
        return retval
        

    def parseSTRL(self,t):
        retval = {}
        size = len(t)
        i = 0
        key = t[i:i+4]
        sz = struct.unpack('<I',t[i+4:i+8])[0]
        i+=8
        value = t[i:]

        if key == 'strh':
            retval[key] = self.parseSTRH(value)
            i += sz
        else:
            _print("parseSTRL: Error")
        key = t[i:i+4]
        sz = struct.unpack('<I',t[i+4:i+8])[0]
        i+=8
        value = t[i:]

        if key == 'strf':
            retval[key] = self.parseSTRF(value, retval['strh'])
            i += sz
        return ( retval, i )

            
    def parseODML(self,t):
        retval = {}
        size = len(t)
        i = 0
        key = t[i:i+4]
        sz = struct.unpack('<I',t[i+4:i+8])[0]
        i += 8
        value = t[i:]
        if key == 'dmlh':
            pass
        else:
            _print("parseODML: Error")

        i += sz - 8
        return ( retval, i )

            
    def parseVPRP(self,t):
        retval = {}
        v = struct.unpack('<IIIIIIIIII',t[:4*10])
        
        ( retval['VideoFormat'],
          retval['VideoStandard'],
          retval['RefreshRate'],
          retval['HTotalIn'],
          retval['VTotalIn'],
          retval['FrameAspectRatio'],
          retval['wPixel'],
          retval['hPixel'] ) = v[1:-1]

        # I need an avi with more informations
        # enum {FORMAT_UNKNOWN, FORMAT_PAL_SQUARE, FORMAT_PAL_CCIR_601,
        #    FORMAT_NTSC_SQUARE, FORMAT_NTSC_CCIR_601,...} VIDEO_FORMAT; 
        # enum {STANDARD_UNKNOWN, STANDARD_PAL, STANDARD_NTSC, STANDARD_SECAM}
        #    VIDEO_STANDARD; 
        #
        r = retval['FrameAspectRatio']
        r = float(r >> 16) / (r & 0xFFFF)
        retval['FrameAspectRatio'] = r
        if self.video:
            map(lambda v: setattr(v, 'aspect', r), self.video)
        return ( retval, v[0] )

            
    def parseLIST(self,t):
        retval = {}
        i = 0
        size = len(t)

        while i < size-8:
            # skip zero
            if ord(t[i]) == 0: i += 1
            key = t[i:i+4]
            sz = 0

            if key == 'LIST':
                sz = struct.unpack('<I',t[i+4:i+8])[0]
                _print("-> SUBLIST: len: %d, %d" % ( sz, i+4 ))
                i+=8
                key = "LIST:"+t[i:i+4]
                value = self.parseLIST(t[i:i+sz])
                _print("<-")
                if key == 'strl':
                    for k in value.keys():
                        retval[k] = value[k]
                else:
                    retval[key] = value
                i+=sz
            elif key == 'avih':
                _print("SUBAVIH")
                sz = struct.unpack('<I',t[i+4:i+8])[0]
                i += 8
                value = self.parseAVIH(t[i:i+sz])
                i += sz
                retval[key] = value
            elif key == 'strl':
                i += 4
                (value, sz) = self.parseSTRL(t[i:])
                _print("SUBSTRL: len: %d" % sz)
                key = value['strh']['fccType']
                i += sz
                retval[key] = value
            elif key == 'odml':
                i += 4
                (value, sz) = self.parseODML(t[i:])
                _print("ODML: len: %d" % sz)
                i += sz
            elif key == 'vprp':
                i += 4
                (value, sz) = self.parseVPRP(t[i:])
                _print("VPRP: len: %d" % sz)
                retval[key] = value
                i += sz
            elif key == 'JUNK':
                sz = struct.unpack('<I',t[i+4:i+8])[0]
                i += sz + 8
                _print("Skipping %d bytes of Junk" % sz)
            else:
                sz = struct.unpack('<I',t[i+4:i+8])[0]
                _print("Unknown Key: %s, len: %d" % (key,sz))
                i+=8
                value = self._extractHeaderString(t,i,sz)
                value = value.replace('\0', '').lstrip().rstrip()
                if value:
                    retval[key] = value
                i+=sz
        return retval
        

    def parseRIFFChunk(self,file):
        h = file.read(8)
        if len(h) < 4:
            return False
        name = h[:4]
        size = struct.unpack('<I',h[4:8])[0]        

        if name == 'LIST' and size < 80000:
            pos = file.tell() - 8
            t = file.read(size)
            key = t[:4]
            _print('parse RIFF LIST: %d bytes' % (size))
            value = self.parseLIST(t[4:])
            self.header[key] = value
            if key == 'INFO':
                self.infoStart = pos
                self.appendtable( 'AVIINFO', value )
            elif key == 'MID ':
                self.appendtable( 'AVIMID', value )
            elif key in ('hdrl', ):
                # no need to add this info to a table
                pass
            else:
                _print('Skipping table info %s' % key)

        elif name == 'JUNK':
            self.junkStart = file.tell() - 8
            self.junkSize  = size
            file.seek(size, 1)
        elif name == 'idx1':
            self.has_idx = True
            _print('idx1: %s bytes' % size)
            # no need to parse this
            t = file.seek(size,1)
        elif name == 'LIST':
            _print('RIFF LIST to long to parse: %s bytes' % size)
            # no need to parse this
            t = file.seek(size,1)
        elif name == 'RIFF':
            _print("New RIFF chunk, extended avi [%i]" % size)
            type = file.read(4)
            if type != 'AVIX':
                _print("Second RIFF chunk is %s, not AVIX, skipping", type)
                file.seek(size-4, 1)
            # that's it, no new informations should be in AVIX
            return False
        elif not name.strip(string.printable + string.whitespace):
            # check if name is something usefull at all, maybe it is no
            # avi or broken
            t = file.seek(size,1)
            _print("Skipping %s [%i]" % (name,size))
        else:
            # bad avi
            _print("Bad or broken avi")
            return False
        return True

    def buildTag(self,key,value):
        text = value + '\0'
        l = len(text)
        return struct.pack('<4sI%ds'%l, key[:4], l, text[:l])


    def setInfo(self,file,hash):
        if self.junkStart == None:
            raise "junkstart missing"
        tags = []
        size = 4 # Length of 'INFO'
        # Build String List and compute req. size
        for key in hash.keys():
            tag = self.buildTag( key, hash[key] )
            if (len(tag))%2 == 1: tag += '\0'
            tags.append(tag)
            size += len(tag)
            _print("Tag [%i]: %s" % (len(tag),tag))
        if self.infoStart != None:
            _print("Infostart found. %i" % (self.infoStart))
            # Read current info size
            file.seek(self.infoStart,0)
            s = file.read(12)
            (list, oldsize, info) = struct.unpack('<4sI4s',s)
            self.junkSize += oldsize + 8
        else:
            self.infoStart = self.junkStart
            _print("Infostart computed. %i" % (self.infoStart))
        file.seek(self.infoStart,0)
        if ( size > self.junkSize - 8 ):
            raise "Too large"
        file.write( "LIST" + struct.pack('<I',size) + "INFO" )
        for tag in tags:
            file.write( tag )
        _print("Junksize %i" % (self.junkSize-size-8))
        file.write( "JUNK" + struct.pack('<I',self.junkSize-size-8) )
        


mmpython.registertype( 'video/avi', ('avi',), mediainfo.TYPE_AV, RiffInfo )
