source: trunk/src/testing/bin/fileServer/misc/mmpython/video/riffinfo.py @ 4

Revision 4, 17.5 KB checked in by ajaworski, 13 years ago (diff)

Added modified SAGE sources

Line 
1#if 0
2# $Id: riffinfo.py,v 1.33 2005/03/15 17:50:45 dischi Exp $
3# $Log: riffinfo.py,v $
4# Revision 1.33  2005/03/15 17:50:45  dischi
5# check for corrupt avi
6#
7# Revision 1.32  2005/03/04 17:41:29  dischi
8# handle broken avi files
9#
10# Revision 1.31  2004/12/13 10:19:07  dischi
11# more debug, support LIST > 20000 (new max is 80000)
12#
13# Revision 1.30  2004/08/25 16:18:14  dischi
14# detect aspect ratio
15#
16# Revision 1.29  2004/05/24 16:17:09  dischi
17# Small changes for future updates
18#
19# Revision 1.28  2004/01/31 12:23:46  dischi
20# remove bad chars from table (e.g. char 0 is True)
21#
22# Revision 1.27  2003/10/04 14:30:08  dischi
23# add audio delay for avi
24#
25# Revision 1.26  2003/07/10 11:18:11  the_krow
26# few more attributes added
27#
28# Revision 1.25  2003/07/07 21:36:44  dischi
29# make fps a float and round it to two digest after the comma
30#
31# Revision 1.24  2003/07/05 19:36:37  the_krow
32# length fixed
33# fps introduced
34#
35# Revision 1.23  2003/07/02 11:17:30  the_krow
36# language is now part of the table key
37#
38# Revision 1.22  2003/07/01 21:06:50  dischi
39# no need to import factory (and when, use "from mmpython import factory"
40#
41# Revision 1.21  2003/06/30 13:17:20  the_krow
42# o Refactored mediainfo into factory, synchronizedobject
43# o Parsers now register directly at mmpython not at mmpython.mediainfo
44# o use mmpython.Factory() instead of mmpython.mediainfo.get_singleton()
45# o Bugfix in PNG parser
46# o Renamed disc.AudioInfo into disc.AudioDiscInfo
47# o Renamed disc.DataInfo into disc.DataDiscInfo
48#
49# Revision 1.20  2003/06/23 20:48:11  the_krow
50# width + height fixes for OGM files
51#
52# Revision 1.19  2003/06/23 20:38:04  the_krow
53# Support for larger LIST chunks because some files did not work.
54#
55# Revision 1.18  2003/06/20 19:17:22  dischi
56# remove filename again and use file.name
57#
58# Revision 1.17  2003/06/20 19:05:56  dischi
59# scan for subtitles
60#
61# Revision 1.16  2003/06/20 15:29:42  the_krow
62# Metadata Mapping
63#
64# Revision 1.15  2003/06/20 14:43:57  the_krow
65# Putting Metadata into MediaInfo from AVIInfo Table
66#
67# Revision 1.14  2003/06/09 16:10:52  dischi
68# error handling
69#
70# Revision 1.13  2003/06/08 19:53:21  dischi
71# also give the filename to init for additional data tests
72#
73# Revision 1.12  2003/06/08 13:44:58  dischi
74# Changed all imports to use the complete mmpython path for mediainfo
75#
76# Revision 1.11  2003/06/08 13:11:38  dischi
77# removed print at the end and moved it into register
78#
79# Revision 1.10  2003/06/07 23:10:50  the_krow
80# Changed mp3 into new format.
81#
82# Revision 1.9  2003/06/07 22:30:22  the_krow
83# added new avinfo structure
84#
85# Revision 1.8  2003/06/07 21:48:47  the_krow
86# Added Copying info
87# started changing riffinfo to new AV stuff
88#
89# Revision 1.7  2003/05/13 12:31:43  the_krow
90# + Copyright Notice
91#
92#
93# MMPython - Media Metadata for Python
94# Copyright (C) 2003 Thomas Schueppel, Dirk Meyer
95#
96# This program is free software; you can redistribute it and/or modify
97# it under the terms of the GNU General Public License as published by
98# the Free Software Foundation; either version 2 of the License, or
99# (at your option) any later version.
100#
101# This program is distributed in the hope that it will be useful, but
102# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
103# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
104# Public License for more details.
105#
106# You should have received a copy of the GNU General Public License along
107# with this program; if not, write to the Free Software Foundation, Inc.,
108# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
109#
110# -----------------------------------------------------------------------
111#endif
112
113import re
114import struct
115import string
116import fourcc
117# import factory
118
119import mmpython
120from mmpython import mediainfo
121
122
123# List of tags
124# http://kibus1.narod.ru/frames_eng.htm?sof/abcavi/infotags.htm
125# http://www.divx-digest.com/software/avitags_dll.html
126# File Format
127# http://www.taenam.co.kr/pds/documents/odmlff2.pdf
128
129_print = mediainfo._debug
130
131AVIINFO_tags = { 'title': 'INAM',
132                 'artist': 'IART',
133                 'product': 'IPRD',
134                 'date': 'ICRD',
135                 'comment': 'ICMT',
136                 'language': 'ILNG',
137                 'keywords': 'IKEY',
138                 'trackno': 'IPRT',
139                 'trackof': 'IFRM',
140                 'producer': 'IPRO',
141                 'writer': 'IWRI',
142                 'genre': 'IGNR',
143                 'copyright': 'ICOP',
144                 'trackno': 'IPRT',
145                 'trackof': 'IFRM',
146                 'comment': 'ICMT',
147               }
148
149
150
151class RiffInfo(mediainfo.AVInfo):
152    def __init__(self,file):
153        mediainfo.AVInfo.__init__(self)
154        # read the header
155        h = file.read(12)
156        if h[:4] != "RIFF" and h[:4] != 'SDSS':
157            self.valid = 0
158            return
159        self.valid = 1
160        self.mime = 'application/x-wave'
161        self.has_idx = False
162        self.header = {}
163        self.junkStart = None
164        self.infoStart = None
165        self.type = h[8:12]
166        self.tag_map = { ('AVIINFO', 'en') : AVIINFO_tags }
167        if self.type == 'AVI ':
168            self.mime = 'video/avi'
169        elif self.type == 'WAVE':
170            self.mime = 'application/x-wave'
171        try:
172            while self.parseRIFFChunk(file):
173                pass
174        except IOError:
175            if mediainfo.DEBUG:
176                print 'error in file, stop parsing'
177
178        self.find_subtitles(file.name)
179       
180        # Copy Metadata from tables into the main set of attributes       
181        for k in self.tag_map.keys():
182            map(lambda x:self.setitem(x,self.gettable(k[0],k[1]),self.tag_map[k][x]),
183                self.tag_map[k].keys())
184        if not self.has_idx:
185            _print('WARNING: avi has no index')
186            self.corrupt = 1
187            self.keys.append('corrupt')
188           
189       
190    def _extractHeaderString(self,h,offset,len):
191        return h[offset:offset+len]
192
193    def parseAVIH(self,t):
194        retval = {}
195        v = struct.unpack('<IIIIIIIIIIIIII',t[0:56])
196        ( retval['dwMicroSecPerFrame'],
197          retval['dwMaxBytesPerSec'],           
198          retval['dwPaddingGranularity'],
199          retval['dwFlags'],
200          retval['dwTotalFrames'],
201          retval['dwInitialFrames'],
202          retval['dwStreams'],
203          retval['dwSuggestedBufferSize'],
204          retval['dwWidth'],
205          retval['dwHeight'],
206          retval['dwScale'],
207          retval['dwRate'],
208          retval['dwStart'],
209          retval['dwLength'] ) = v
210        if retval['dwMicroSecPerFrame'] == 0:
211            _print("ERROR: Corrupt AVI")
212            self.valid = 0
213            return {}
214        return retval
215       
216    def parseSTRH(self,t):
217        retval = {}
218        retval['fccType'] = t[0:4]
219        _print("parseSTRH(%s) : %d bytes" % ( retval['fccType'], len(t)))
220        if retval['fccType'] != 'auds':
221            retval['fccHandler'] = t[4:8]
222            v = struct.unpack('<IHHIIIIIIIII',t[8:52])
223            ( retval['dwFlags'],
224              retval['wPriority'],
225              retval['wLanguage'],
226              retval['dwInitialFrames'],
227              retval['dwScale'],
228              retval['dwRate'],
229              retval['dwStart'],
230              retval['dwLength'],
231              retval['dwSuggestedBufferSize'],
232              retval['dwQuality'],
233              retval['dwSampleSize'],
234              retval['rcFrame'], ) = v
235        else:
236            try:
237                v = struct.unpack('<IHHIIIIIIIII',t[8:52])
238                ( retval['dwFlags'],
239                  retval['wPriority'],
240                  retval['wLanguage'],
241                  retval['dwInitialFrames'],
242                  retval['dwScale'],
243                  retval['dwRate'],
244                  retval['dwStart'],
245                  retval['dwLength'],
246                  retval['dwSuggestedBufferSize'],
247                  retval['dwQuality'],
248                  retval['dwSampleSize'],
249                  retval['rcFrame'], ) = v
250                self.delay = float(retval['dwStart']) / \
251                             (float(retval['dwRate']) / retval['dwScale'])
252            except:
253                pass
254           
255        return retval
256
257    def parseSTRF(self,t,strh):
258        fccType = strh['fccType']
259        retval = {}
260        if fccType == 'auds':
261            ( retval['wFormatTag'],
262              retval['nChannels'],
263              retval['nSamplesPerSec'],
264              retval['nAvgBytesPerSec'],
265              retval['nBlockAlign'],
266              retval['nBitsPerSample'],
267            ) = struct.unpack('<HHHHHH',t[0:12])
268            ai = mediainfo.AudioInfo()
269            ai.samplerate = retval['nSamplesPerSec']
270            ai.channels = retval['nChannels']
271            ai.samplebits = retval['nBitsPerSample']
272            ai.bitrate = retval['nAvgBytesPerSec'] * 8
273            # TODO: set code if possible
274            # http://www.stats.uwa.edu.au/Internal/Specs/DXALL/FileSpec/Languages
275            # ai.language = strh['wLanguage']
276            try:
277                ai.codec = fourcc.RIFFWAVE[retval['wFormatTag']]
278            except:
279                ai.codec = "Unknown"           
280            self.audio.append(ai) 
281        elif fccType == 'vids':
282            v = struct.unpack('<IIIHH',t[0:16])
283            ( retval['biSize'],
284              retval['biWidth'],
285              retval['biHeight'],
286              retval['biPlanes'],
287              retval['biBitCount'], ) = v
288            retval['fourcc'] = t[16:20]           
289            v = struct.unpack('IIIII',t[20:40])
290            ( retval['biSizeImage'],
291              retval['biXPelsPerMeter'],
292              retval['biYPelsPerMeter'],
293              retval['biClrUsed'],
294              retval['biClrImportant'], ) = v
295            vi = mediainfo.VideoInfo()
296            try:
297                vi.codec = fourcc.RIFFCODEC[t[16:20]]
298            except:
299                vi.codec = "Unknown"
300            vi.width = retval['biWidth']
301            vi.height = retval['biHeight']           
302            vi.bitrate = strh['dwRate']
303            vi.fps = round(float(strh['dwRate'] * 100) / strh['dwScale']) / 100
304            vi.length = strh['dwLength'] / vi.fps
305            self.video.append(vi) 
306        return retval
307       
308
309    def parseSTRL(self,t):
310        retval = {}
311        size = len(t)
312        i = 0
313        key = t[i:i+4]
314        sz = struct.unpack('<I',t[i+4:i+8])[0]
315        i+=8
316        value = t[i:]
317
318        if key == 'strh':
319            retval[key] = self.parseSTRH(value)
320            i += sz
321        else:
322            _print("parseSTRL: Error")
323        key = t[i:i+4]
324        sz = struct.unpack('<I',t[i+4:i+8])[0]
325        i+=8
326        value = t[i:]
327
328        if key == 'strf':
329            retval[key] = self.parseSTRF(value, retval['strh'])
330            i += sz
331        return ( retval, i )
332
333           
334    def parseODML(self,t):
335        retval = {}
336        size = len(t)
337        i = 0
338        key = t[i:i+4]
339        sz = struct.unpack('<I',t[i+4:i+8])[0]
340        i += 8
341        value = t[i:]
342        if key == 'dmlh':
343            pass
344        else:
345            _print("parseODML: Error")
346
347        i += sz - 8
348        return ( retval, i )
349
350           
351    def parseVPRP(self,t):
352        retval = {}
353        v = struct.unpack('<IIIIIIIIII',t[:4*10])
354       
355        ( retval['VideoFormat'],
356          retval['VideoStandard'],
357          retval['RefreshRate'],
358          retval['HTotalIn'],
359          retval['VTotalIn'],
360          retval['FrameAspectRatio'],
361          retval['wPixel'],
362          retval['hPixel'] ) = v[1:-1]
363
364        # I need an avi with more informations
365        # enum {FORMAT_UNKNOWN, FORMAT_PAL_SQUARE, FORMAT_PAL_CCIR_601,
366        #    FORMAT_NTSC_SQUARE, FORMAT_NTSC_CCIR_601,...} VIDEO_FORMAT;
367        # enum {STANDARD_UNKNOWN, STANDARD_PAL, STANDARD_NTSC, STANDARD_SECAM}
368        #    VIDEO_STANDARD;
369        #
370        r = retval['FrameAspectRatio']
371        r = float(r >> 16) / (r & 0xFFFF)
372        retval['FrameAspectRatio'] = r
373        if self.video:
374            map(lambda v: setattr(v, 'aspect', r), self.video)
375        return ( retval, v[0] )
376
377           
378    def parseLIST(self,t):
379        retval = {}
380        i = 0
381        size = len(t)
382
383        while i < size-8:
384            # skip zero
385            if ord(t[i]) == 0: i += 1
386            key = t[i:i+4]
387            sz = 0
388
389            if key == 'LIST':
390                sz = struct.unpack('<I',t[i+4:i+8])[0]
391                _print("-> SUBLIST: len: %d, %d" % ( sz, i+4 ))
392                i+=8
393                key = "LIST:"+t[i:i+4]
394                value = self.parseLIST(t[i:i+sz])
395                _print("<-")
396                if key == 'strl':
397                    for k in value.keys():
398                        retval[k] = value[k]
399                else:
400                    retval[key] = value
401                i+=sz
402            elif key == 'avih':
403                _print("SUBAVIH")
404                sz = struct.unpack('<I',t[i+4:i+8])[0]
405                i += 8
406                value = self.parseAVIH(t[i:i+sz])
407                i += sz
408                retval[key] = value
409            elif key == 'strl':
410                i += 4
411                (value, sz) = self.parseSTRL(t[i:])
412                _print("SUBSTRL: len: %d" % sz)
413                key = value['strh']['fccType']
414                i += sz
415                retval[key] = value
416            elif key == 'odml':
417                i += 4
418                (value, sz) = self.parseODML(t[i:])
419                _print("ODML: len: %d" % sz)
420                i += sz
421            elif key == 'vprp':
422                i += 4
423                (value, sz) = self.parseVPRP(t[i:])
424                _print("VPRP: len: %d" % sz)
425                retval[key] = value
426                i += sz
427            elif key == 'JUNK':
428                sz = struct.unpack('<I',t[i+4:i+8])[0]
429                i += sz + 8
430                _print("Skipping %d bytes of Junk" % sz)
431            else:
432                sz = struct.unpack('<I',t[i+4:i+8])[0]
433                _print("Unknown Key: %s, len: %d" % (key,sz))
434                i+=8
435                value = self._extractHeaderString(t,i,sz)
436                value = value.replace('\0', '').lstrip().rstrip()
437                if value:
438                    retval[key] = value
439                i+=sz
440        return retval
441       
442
443    def parseRIFFChunk(self,file):
444        h = file.read(8)
445        if len(h) < 4:
446            return False
447        name = h[:4]
448        size = struct.unpack('<I',h[4:8])[0]       
449
450        if name == 'LIST' and size < 80000:
451            pos = file.tell() - 8
452            t = file.read(size)
453            key = t[:4]
454            _print('parse RIFF LIST: %d bytes' % (size))
455            value = self.parseLIST(t[4:])
456            self.header[key] = value
457            if key == 'INFO':
458                self.infoStart = pos
459                self.appendtable( 'AVIINFO', value )
460            elif key == 'MID ':
461                self.appendtable( 'AVIMID', value )
462            elif key in ('hdrl', ):
463                # no need to add this info to a table
464                pass
465            else:
466                _print('Skipping table info %s' % key)
467
468        elif name == 'JUNK':
469            self.junkStart = file.tell() - 8
470            self.junkSize  = size
471            file.seek(size, 1)
472        elif name == 'idx1':
473            self.has_idx = True
474            _print('idx1: %s bytes' % size)
475            # no need to parse this
476            t = file.seek(size,1)
477        elif name == 'LIST':
478            _print('RIFF LIST to long to parse: %s bytes' % size)
479            # no need to parse this
480            t = file.seek(size,1)
481        elif name == 'RIFF':
482            _print("New RIFF chunk, extended avi [%i]" % size)
483            type = file.read(4)
484            if type != 'AVIX':
485                _print("Second RIFF chunk is %s, not AVIX, skipping", type)
486                file.seek(size-4, 1)
487            # that's it, no new informations should be in AVIX
488            return False
489        elif not name.strip(string.printable + string.whitespace):
490            # check if name is something usefull at all, maybe it is no
491            # avi or broken
492            t = file.seek(size,1)
493            _print("Skipping %s [%i]" % (name,size))
494        else:
495            # bad avi
496            _print("Bad or broken avi")
497            return False
498        return True
499
500    def buildTag(self,key,value):
501        text = value + '\0'
502        l = len(text)
503        return struct.pack('<4sI%ds'%l, key[:4], l, text[:l])
504
505
506    def setInfo(self,file,hash):
507        if self.junkStart == None:
508            raise "junkstart missing"
509        tags = []
510        size = 4 # Length of 'INFO'
511        # Build String List and compute req. size
512        for key in hash.keys():
513            tag = self.buildTag( key, hash[key] )
514            if (len(tag))%2 == 1: tag += '\0'
515            tags.append(tag)
516            size += len(tag)
517            _print("Tag [%i]: %s" % (len(tag),tag))
518        if self.infoStart != None:
519            _print("Infostart found. %i" % (self.infoStart))
520            # Read current info size
521            file.seek(self.infoStart,0)
522            s = file.read(12)
523            (list, oldsize, info) = struct.unpack('<4sI4s',s)
524            self.junkSize += oldsize + 8
525        else:
526            self.infoStart = self.junkStart
527            _print("Infostart computed. %i" % (self.infoStart))
528        file.seek(self.infoStart,0)
529        if ( size > self.junkSize - 8 ):
530            raise "Too large"
531        file.write( "LIST" + struct.pack('<I',size) + "INFO" )
532        for tag in tags:
533            file.write( tag )
534        _print("Junksize %i" % (self.junkSize-size-8))
535        file.write( "JUNK" + struct.pack('<I',self.junkSize-size-8) )
536       
537
538
539mmpython.registertype( 'video/avi', ('avi',), mediainfo.TYPE_AV, RiffInfo )
Note: See TracBrowser for help on using the repository browser.