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

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

Added modified SAGE sources

Line 
1#if 0
2# $Id: mpeginfo.py,v 1.33 2005/02/15 18:52:51 dischi Exp $
3# $Log: mpeginfo.py,v $
4# Revision 1.33  2005/02/15 18:52:51  dischi
5# some strange bugfix (what is this doing?)
6#
7# Revision 1.32  2005/01/21 16:37:02  dischi
8# try to find bad timestamps
9#
10# Revision 1.31  2005/01/08 12:06:45  dischi
11# make sure the buffer is big enough
12#
13# Revision 1.30  2005/01/02 14:57:27  dischi
14# detect ac3 in normal mpeg2
15#
16# Revision 1.29  2004/11/27 14:42:12  dischi
17# remove future warning
18#
19# Revision 1.28  2004/11/15 21:43:36  dischi
20# remove bad debugging stuff
21#
22# Revision 1.27  2004/11/12 18:10:45  dischi
23# add ac3 support in mpeg streams
24#
25# Revision 1.26  2004/10/04 18:06:54  dischi
26# test length of remaining buffer
27#
28# Revision 1.25  2004/07/11 19:37:25  dischi
29# o read more bytes on ts scan
30# o support for AC3 in private streams
31#
32# Revision 1.24  2004/07/03 09:01:32  dischi
33# o fix PES start detection inside TS
34# o try to find out if the stream is progressive or interlaced
35#
36# Revision 1.23  2004/06/23 19:44:10  dischi
37# better length detection, big cleanup
38#
39# Revision 1.22  2004/06/22 21:37:34  dischi
40# o PES support
41# o basic length detection for TS and PES
42#
43# Revision 1.21  2004/06/21 20:37:34  dischi
44# basic support for mpeg-ts
45#
46# Revision 1.20  2004/03/13 23:41:59  dischi
47# add AudioInfo to mpeg for all streams
48#
49# Revision 1.19  2004/02/11 20:11:54  dischi
50# Updated length calculation for mpeg files. This may not work for all files.
51#
52#
53# MMPython - Media Metadata for Python
54# Copyright (C) 2003 Thomas Schueppel
55#
56# This program is free software; you can redistribute it and/or modify
57# it under the terms of the GNU General Public License as published by
58# the Free Software Foundation; either version 2 of the License, or
59# (at your option) any later version.
60#
61# This program is distributed in the hope that it will be useful, but
62# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
63# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
64# Public License for more details.
65#
66# You should have received a copy of the GNU General Public License along
67# with this program; if not, write to the Free Software Foundation, Inc.,
68# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
69#
70# -----------------------------------------------------------------------
71#endif
72
73import re
74import os
75import struct
76import string
77import fourcc
78
79from mmpython import mediainfo
80import mmpython
81import stat
82
83##------------------------------------------------------------------------
84## START_CODE
85##
86## Start Codes, with 'slice' occupying 0x01..0xAF
87##------------------------------------------------------------------------
88START_CODE = {
89    0x00 : 'picture_start_code',
90    0xB0 : 'reserved',
91    0xB1 : 'reserved',
92    0xB2 : 'user_data_start_code',
93    0xB3 : 'sequence_header_code',
94    0xB4 : 'sequence_error_code',
95    0xB5 : 'extension_start_code',
96    0xB6 : 'reserved',
97    0xB7 : 'sequence end',
98    0xB8 : 'group of pictures',
99}
100for i in range(0x01,0xAF):
101    START_CODE[i] = 'slice_start_code'
102
103##------------------------------------------------------------------------
104## START CODES
105##------------------------------------------------------------------------
106PICTURE   = 0x00
107USERDATA  = 0xB2
108SEQ_HEAD  = 0xB3
109SEQ_ERR   = 0xB4
110EXT_START = 0xB5
111SEQ_END   = 0xB7
112GOP       = 0xB8
113
114SEQ_START_CODE  = 0xB3
115PACK_PKT        = 0xBA
116SYS_PKT         = 0xBB
117PADDING_PKT     = 0xBE
118AUDIO_PKT       = 0xC0
119VIDEO_PKT       = 0xE0
120PRIVATE_STREAM1 = 0xBD
121PRIVATE_STREAM2 = 0xBf
122
123TS_PACKET_LENGTH = 188
124TS_SYNC          = 0x47
125
126##------------------------------------------------------------------------
127## FRAME_RATE
128##
129## A lookup table of all the standard frame rates.  Some rates adhere to
130## a particular profile that ensures compatibility with VLSI capabilities
131## of the early to mid 1990s.
132##
133## CPB
134##   Constrained Parameters Bitstreams, an MPEG-1 set of sampling and
135##   bitstream parameters designed to normalize decoder computational
136##   complexity, buffer size, and memory bandwidth while still addressing
137##   the widest possible range of applications.
138##
139## Main Level
140##   MPEG-2 Video Main Profile and Main Level is analogous to MPEG-1's
141##   CPB, with sampling limits at CCIR 601 parameters (720x480x30 Hz or
142##   720x576x24 Hz).
143##
144##------------------------------------------------------------------------
145FRAME_RATE = [
146      0,
147      round(24000.0/1001*100)/100, ## 3-2 pulldown NTSC (CPB/Main Level)
148      24,           ## Film (CPB/Main Level)
149      25,           ## PAL/SECAM or 625/60 video
150      round(30000.0/1001*100)/100, ## NTSC (CPB/Main Level)
151      30,           ## drop-frame NTSC or component 525/60  (CPB/Main Level)
152      50,           ## double-rate PAL
153      round(60000.0/1001*100)/100, ## double-rate NTSC
154      60,           ## double-rate, drop-frame NTSC/component 525/60 video
155      ]
156
157##------------------------------------------------------------------------
158## ASPECT_RATIO -- INCOMPLETE?
159##
160## This lookup table maps the header aspect ratio index to a common name.
161## These are just the defined ratios for CPB I believe.  As I understand
162## it, a stream that doesn't adhere to one of these aspect ratios is
163## technically considered non-compliant.
164##------------------------------------------------------------------------
165ASPECT_RATIO = [ 'Forbidden',
166                 '1/1 (VGA)',
167                 '4/3 (TV)',
168                 '16/9 (Large TV)',
169                 '2.21/1 (Cinema)',
170               ]
171 
172
173class MpegInfo(mediainfo.AVInfo):
174    def __init__(self,file):
175        mediainfo.AVInfo.__init__(self)
176        self.context = 'video'
177        self.sequence_header_offset = 0
178
179        # detect TS (fast scan)
180        self.valid = self.isTS(file)
181
182        if not self.valid:
183            # detect system mpeg (many infos)
184            self.valid = self.isMPEG(file)
185
186        if not self.valid:
187            # detect PES
188            self.valid = self.isPES(file)
189           
190        if self.valid:       
191            self.mime = 'video/mpeg'
192            if not self.video:
193                self.video.append(mediainfo.VideoInfo())
194
195            if self.sequence_header_offset <= 0:
196                return
197
198            self.progressive(file)
199           
200            for vi in self.video:
201                vi.width, vi.height = self.dxy(file)
202                vi.fps, vi.aspect = self.framerate_aspect(file)
203                vi.bitrate = self.bitrate(file)
204                if self.length:
205                    vi.length = self.length
206
207            if not self.type:
208                if self.video[0].width == 480:
209                    self.type = 'MPEG2 video' # SVCD spec
210                elif self.video[0].width == 352:
211                    self.type = 'MPEG1 video' # VCD spec
212                else:
213                    self.type = 'MPEG video'
214
215            if mediainfo.DEBUG > 2:
216                self.__scan__()
217
218           
219    def dxy(self,file): 
220        """
221        get width and height of the video
222        """
223        file.seek(self.sequence_header_offset+4,0)
224        v = file.read(4)
225        x = struct.unpack('>H',v[:2])[0] >> 4
226        y = struct.unpack('>H',v[1:3])[0] & 0x0FFF
227        return (x,y)
228
229       
230    def framerate_aspect(self,file):
231        """
232        read framerate and aspect ratio
233        """
234        file.seek(self.sequence_header_offset+7,0)
235        v = struct.unpack( '>B', file.read(1) )[0]
236        try:
237            fps = FRAME_RATE[v&0xf]
238        except IndexError:
239            fps = None
240        try:
241            aspect = ASPECT_RATIO[v>>4]
242        except IndexError:
243            if mediainfo.DEBUG:
244                print 'Index error: %s' % (v>>4)
245            aspect = None
246        return (fps, aspect)
247       
248
249    def progressive(self, file):
250        """
251        Try to find out with brute force if the mpeg is interlaced or not.
252        Search for the Sequence_Extension in the extension header (01B5)
253        """
254        file.seek(0)
255        buffer = ''
256        count  = 0
257        while 1:
258            if len(buffer) < 1000:
259                count += 1
260                if count > 1000:
261                    break
262                buffer += file.read(1024)
263            if len(buffer) < 1000:
264                break
265            pos = buffer.find('\x00\x00\x01\xb5')
266            if pos == -1 or len(buffer) - pos < 5:
267                buffer = buffer[-10:]
268                continue
269            ext = (ord(buffer[pos+4]) >> 4)
270            if ext == 8:
271                pass
272            elif ext == 1:
273                if (ord(buffer[pos+5]) >> 3) & 1:
274                    self.keys.append('progressive')
275                    self.progressive = 1
276                else:
277                    self.keys.append('interlaced')
278                    self.interlaced = 1
279                return True
280            else:
281                print 'ext', ext
282            buffer = buffer[pos+4:]
283        return False
284   
285       
286    ##------------------------------------------------------------------------
287    ## bitrate()
288    ##
289    ## From the MPEG-2.2 spec:
290    ##
291    ##   bit_rate -- This is a 30-bit integer.  The lower 18 bits of the
292    ##   integer are in bit_rate_value and the upper 12 bits are in
293    ##   bit_rate_extension.  The 30-bit integer specifies the bitrate of the
294    ##   bitstream measured in units of 400 bits/second, rounded upwards.
295    ##   The value zero is forbidden.
296    ##
297    ## So ignoring all the variable bitrate stuff for now, this 30 bit integer
298    ## multiplied times 400 bits/sec should give the rate in bits/sec.
299    ## 
300    ## TODO: Variable bitrates?  I need one that implements this.
301    ##
302    ## Continued from the MPEG-2.2 spec:
303    ##
304    ##   If the bitstream is a constant bitrate stream, the bitrate specified
305    ##   is the actual rate of operation of the VBV specified in annex C.  If
306    ##   the bitstream is a variable bitrate stream, the STD specifications in
307    ##   ISO/IEC 13818-1 supersede the VBV, and the bitrate specified here is
308    ##   used to dimension the transport stream STD (2.4.2 in ITU-T Rec. xxx |
309    ##   ISO/IEC 13818-1), or the program stream STD (2.4.5 in ITU-T Rec. xxx |
310    ##   ISO/IEC 13818-1).
311    ##
312    ##   If the bitstream is not a constant rate bitstream the vbv_delay
313    ##   field shall have the value FFFF in hexadecimal.
314    ##
315    ##   Given the value encoded in the bitrate field, the bitstream shall be
316    ##   generated so that the video encoding and the worst case multiplex
317    ##   jitter do not cause STD buffer overflow or underflow.
318    ##
319    ##
320    ##------------------------------------------------------------------------
321
322
323    ## Some parts in the code are based on mpgtx (mpgtx.sf.net)
324   
325    def bitrate(self,file):
326        """
327        read the bitrate (most of the time broken)
328        """
329        file.seek(self.sequence_header_offset+8,0)
330        t,b = struct.unpack( '>HB', file.read(3) )
331        vrate = t << 2 | b >> 6
332        return vrate * 400
333       
334
335    def ReadSCRMpeg2(self, buffer):
336        """
337        read SCR (timestamp) for MPEG2 at the buffer beginning (6 Bytes)
338        """
339        highbit = (ord(buffer[0])&0x20)>>5
340
341        low4Bytes= ((long(ord(buffer[0])) & 0x18) >> 3) << 30
342        low4Bytes |= (ord(buffer[0]) & 0x03) << 28
343        low4Bytes |= ord(buffer[1]) << 20
344        low4Bytes |= (ord(buffer[2]) & 0xF8) << 12
345        low4Bytes |= (ord(buffer[2]) & 0x03) << 13
346        low4Bytes |= ord(buffer[3]) << 5
347        low4Bytes |= (ord(buffer[4])) >> 3
348
349        sys_clock_ref=(ord(buffer[4]) & 0x3) << 7
350        sys_clock_ref|=(ord(buffer[5]) >> 1)
351
352        return (long(highbit * (1<<16) * (1<<16)) + low4Bytes) / 90000
353
354
355    def ReadSCRMpeg1(self, buffer):
356        """
357        read SCR (timestamp) for MPEG1 at the buffer beginning (5 Bytes)
358        """
359        highbit = (ord(buffer[0]) >> 3) & 0x01
360
361        low4Bytes = ((long(ord(buffer[0])) >> 1) & 0x03) << 30
362        low4Bytes |= ord(buffer[1]) << 22;
363        low4Bytes |= (ord(buffer[2]) >> 1) << 15;
364        low4Bytes |= ord(buffer[3]) << 7;
365        low4Bytes |= ord(buffer[4]) >> 1;
366
367        return (long(highbit) * (1<<16) * (1<<16) + low4Bytes) / 90000;
368
369
370    def ReadPTS(self, buffer):
371        """
372        read PTS (PES timestamp) at the buffer beginning (5 Bytes)
373        """
374        high = ((ord(buffer[0]) & 0xF) >> 1)
375        med  = (ord(buffer[1]) << 7) + (ord(buffer[2]) >> 1)
376        low  = (ord(buffer[3]) << 7) + (ord(buffer[4]) >> 1)
377        return ((long(high) << 30 ) + (med << 15) + low) / 90000
378
379
380    def ReadHeader(self, buffer, offset):
381        """
382        Handle MPEG header in buffer on position offset
383        Return -1 on error, new offset or 0 if the new offset can't be scanned
384        """
385        if buffer[offset:offset+3] != '\x00\x00\x01':
386            return -1
387
388        id = ord(buffer[offset+3])
389
390        if id == PADDING_PKT:
391            return offset + (ord(buffer[offset+4]) << 8) + ord(buffer[offset+5]) + 6
392
393        if id == PACK_PKT:
394            if ord(buffer[offset+4]) & 0xF0 == 0x20:
395                self.type     = 'MPEG1 video'
396                self.get_time = self.ReadSCRMpeg1
397                return offset + 12
398            elif (ord(buffer[offset+4]) & 0xC0) == 0x40:
399                self.type     = 'MPEG2 video'
400                self.get_time = self.ReadSCRMpeg2
401                return offset + (ord(buffer[offset+13]) & 0x07) + 14
402            else:
403                # WTF? Very strange
404                return -1
405
406        if 0xC0 <= id <= 0xDF:
407            # code for audio stream
408            for a in self.audio:
409                if a.id == id:
410                    break
411            else:
412                self.audio.append(mediainfo.AudioInfo())
413                self.audio[-1].id = id
414                self.audio[-1].keys.append('id')
415            return 0
416
417        if 0xE0 <= id <= 0xEF:
418            # code for video stream
419            for v in self.video:
420                if v.id == id:
421                    break
422            else:
423                self.video.append(mediainfo.VideoInfo())
424                self.video[-1].id = id
425                self.video[-1].keys.append('id')
426            return 0
427
428        if id == SEQ_HEAD:
429            # sequence header, remember that position for later use
430            self.sequence_header_offset = offset
431            return 0
432
433        if id in (PRIVATE_STREAM1, PRIVATE_STREAM2):
434            # private stream. we don't know, but maybe we can guess later
435            add = ord(buffer[offset+8])
436            # if (ord(buffer[offset+6]) & 4) or 1:
437            # id = ord(buffer[offset+10+add])
438            if buffer[offset+11+add:offset+15+add].find('\x0b\x77') != -1:
439                # AC3 stream
440                for a in self.audio:
441                    if a.id == id:
442                        break
443                else:
444                    self.audio.append(mediainfo.AudioInfo())
445                    self.audio[-1].id = id
446                    self.audio[-1].codec = 'AC3'
447                    self.audio[-1].keys.append('id')
448            return 0
449
450        if id == SYS_PKT:
451            return 0
452       
453        if id == EXT_START:
454            return 0
455       
456        return 0
457
458
459    # Normal MPEG (VCD, SVCD) ========================================
460       
461    def isMPEG(self, file):
462        """
463        This MPEG starts with a sequence of 0x00 followed by a PACK Header
464        http://dvd.sourceforge.net/dvdinfo/packhdr.html
465        """
466        file.seek(0,0)
467        buffer = file.read(10000)
468        offset = 0
469
470        # seek until the 0 byte stop
471        while buffer[offset] == '\0':
472            offset += 1
473        offset -= 2
474
475        # test for mpeg header 0x00 0x00 0x01
476        if not buffer[offset:offset+4] == '\x00\x00\x01%s' % chr(PACK_PKT):
477            return 0
478
479        # scan the 100000 bytes of data
480        buffer += file.read(100000)
481
482        # scan first header, to get basic info about
483        # how to read a timestamp
484        self.ReadHeader(buffer, offset)
485
486        # store first timestamp
487        self.start = self.get_time(buffer[offset+4:])
488        while len(buffer) > offset + 1000 and buffer[offset:offset+3] == '\x00\x00\x01':
489            # read the mpeg header
490            new_offset = self.ReadHeader(buffer, offset)
491
492            # header scanning detected error, this is no mpeg
493            if new_offset == -1:
494                return 0
495
496            if new_offset:
497                # we have a new offset
498                offset = new_offset
499
500                # skip padding 0 before a new header
501                while len(buffer) > offset + 10 and \
502                          not ord(buffer[offset+2]):
503                    offset += 1
504
505            else:
506                # seek to new header by brute force
507                offset += buffer[offset+4:].find('\x00\x00\x01') + 4
508
509        # fill in values for support functions:
510        self.__seek_size__   = 1000000
511        self.__sample_size__ = 10000
512        self.__search__      = self._find_timer_
513        self.filename        = file.name
514
515        # get length of the file
516        self.length = self.get_length()
517        return 1
518
519
520    def _find_timer_(self, buffer):
521        """
522        Return position of timer in buffer or -1 if not found.
523        This function is valid for 'normal' mpeg files
524        """
525        pos = buffer.find('\x00\x00\x01%s' % chr(PACK_PKT))
526        if pos == -1:
527            return -1
528        return pos + 4
529       
530
531
532    # PES ============================================================
533
534
535    def ReadPESHeader(self, offset, buffer, id=0):
536        """
537        Parse a PES header.
538        Since it starts with 0x00 0x00 0x01 like 'normal' mpegs, this
539        function will return (0, -1) when it is no PES header or
540        (packet length, timestamp position (maybe -1))
541       
542        http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
543        """
544        if not buffer[0:3] == '\x00\x00\x01':
545            return 0, -1
546
547        packet_length = (ord(buffer[4]) << 8) + ord(buffer[5]) + 6
548        align         = ord(buffer[6]) & 4
549        header_length = ord(buffer[8])
550
551        # PES ID (starting with 001)
552        if ord(buffer[3]) & 0xE0 == 0xC0:
553            id = id or ord(buffer[3]) & 0x1F
554            for a in self.audio:
555                if a.id == id:
556                    break
557            else:
558                self.audio.append(mediainfo.AudioInfo())
559                self.audio[-1].id = id
560                self.audio[-1].keys.append('id')
561
562        elif ord(buffer[3]) & 0xF0 == 0xE0:
563            id = id or ord(buffer[3]) & 0xF
564            for v in self.video:
565                if v.id == id:
566                    break
567            else:
568                self.video.append(mediainfo.VideoInfo())
569                self.video[-1].id = id
570                self.video[-1].keys.append('id')
571
572            # new mpeg starting
573            if buffer[header_length+9:header_length+13] == \
574                   '\x00\x00\x01\xB3' and not self.sequence_header_offset:
575                # yes, remember offset for later use
576                self.sequence_header_offset = offset + header_length+9
577        elif ord(buffer[3]) == 189 or ord(buffer[3]) == 191:
578            # private stream. we don't know, but maybe we can guess later
579            id = id or ord(buffer[3]) & 0xF
580            if align and buffer[header_length+9:header_length+11] == '\x0b\x77':
581                # AC3 stream
582                for a in self.audio:
583                    if a.id == id:
584                        break
585                else:
586                    self.audio.append(mediainfo.AudioInfo())
587                    self.audio[-1].id = id
588                    self.audio[-1].codec = 'AC3'
589                    self.audio[-1].keys.append('id')
590
591        else:
592            # unknown content
593            pass
594
595        ptsdts = ord(buffer[7]) >> 6
596
597        if ptsdts and ptsdts == ord(buffer[9]) >> 4:
598            if ord(buffer[9]) >> 4 != ptsdts:
599                print 'WARNING: bad PTS/DTS, please contact us'
600                return packet_length, -1
601               
602            # timestamp = self.ReadPTS(buffer[9:14])
603            high = ((ord(buffer[9]) & 0xF) >> 1)
604            med  = (ord(buffer[10]) << 7) + (ord(buffer[11]) >> 1)
605            low  = (ord(buffer[12]) << 7) + (ord(buffer[13]) >> 1)
606            return packet_length, 9
607           
608        return packet_length, -1
609   
610
611
612    def isPES(self, file):
613        if mediainfo.DEBUG:
614            print 'trying mpeg-pes scan'
615        file.seek(0,0)
616        buffer = file.read(3)
617
618        # header (also valid for all mpegs)
619        if not buffer == '\x00\x00\x01':
620            return 0
621
622        self.sequence_header_offset = 0
623        buffer += file.read(10000)
624
625        offset = 0
626        while offset + 1000 < len(buffer):
627            pos, timestamp = self.ReadPESHeader(offset, buffer[offset:])
628            if not pos:
629                return 0
630            if timestamp != -1 and not hasattr(self, 'start'):
631                self.get_time = self.ReadPTS
632                self.start = self.get_time(buffer[offset+timestamp:offset+timestamp+5])
633            if self.sequence_header_offset and hasattr(self, 'start'):
634                # we have all informations we need
635                break
636
637            offset += pos
638            if offset + 1000 < len(buffer) and len(buffer) < 1000000 or 1:
639                # looks like a pes, read more
640                buffer += file.read(10000)
641       
642        if not self.video and not self.audio:
643            # no video and no audio?
644            return 0
645
646        self.type = 'MPEG-PES'
647
648        # fill in values for support functions:
649        self.__seek_size__   = 10000000  # 10 MB
650        self.__sample_size__ = 500000    # 500 k scanning
651        self.__search__      = self._find_timer_PES_
652        self.filename        = file.name
653
654        # get length of the file
655        self.length = self.get_length()
656        return 1
657
658
659    def _find_timer_PES_(self, buffer):
660        """
661        Return position of timer in buffer or -1 if not found.
662        This function is valid for PES files
663        """
664        pos    = buffer.find('\x00\x00\x01')
665        offset = 0
666        if pos == -1 or offset + 1000 >= len(buffer):
667            return -1
668       
669        retpos   = -1
670        ackcount = 0
671        while offset + 1000 < len(buffer):
672            pos, timestamp = self.ReadPESHeader(offset, buffer[offset:])
673            if timestamp != -1 and retpos == -1:
674                retpos = offset + timestamp
675            if pos == 0:
676                # Oops, that was a mpeg header, no PES header
677                offset  += buffer[offset:].find('\x00\x00\x01')
678                retpos   = -1
679                ackcount = 0
680            else:
681                offset   += pos
682                if retpos != -1:
683                    ackcount += 1
684            if ackcount > 10:
685                # looks ok to me
686                return retpos
687        return -1
688           
689
690    # Transport Stream ===============================================
691   
692    def isTS(self, file):
693        file.seek(0,0)
694
695        buffer = file.read(TS_PACKET_LENGTH * 2)
696        c = 0
697
698        while c + TS_PACKET_LENGTH < len(buffer):
699            if ord(buffer[c]) == ord(buffer[c+TS_PACKET_LENGTH]) == TS_SYNC:
700                break
701            c += 1
702        else:
703            return 0
704
705        buffer += file.read(10000)
706        self.type = 'MPEG-TS'
707       
708        while c + TS_PACKET_LENGTH < len(buffer):
709            start = ord(buffer[c+1]) & 0x40
710            # maybe load more into the buffer
711            if c + 2 * TS_PACKET_LENGTH > len(buffer) and c < 500000:
712                buffer += file.read(10000)
713
714            # wait until the ts payload contains a payload header
715            if not start:
716                c += TS_PACKET_LENGTH
717                continue
718
719            tsid = ((ord(buffer[c+1]) & 0x3F) << 8) + ord(buffer[c+2])
720            adapt = (ord(buffer[c+3]) & 0x30) >> 4
721
722            offset = 4
723            if adapt & 0x02:
724                # meta info present, skip it for now
725                adapt_len = ord(buffer[c+offset])
726                offset += adapt_len + 1
727
728            if not ord(buffer[c+1]) & 0x40:
729                # no new pes or psi in stream payload starting
730                pass
731            elif adapt & 0x01:
732                # PES
733                timestamp = self.ReadPESHeader(c+offset, buffer[c+offset:], tsid)[1]
734                if timestamp != -1:
735                    if not hasattr(self, 'start'):
736                        self.get_time = self.ReadPTS
737                        timestamp = c + offset + timestamp
738                        self.start = self.get_time(buffer[timestamp:timestamp+5])
739                    elif not hasattr(self, 'audio_ok'):
740                        timestamp = c + offset + timestamp
741                        start = self.get_time(buffer[timestamp:timestamp+5])
742                        if abs(start - self.start) < 10:
743                            # looks ok
744                            self.audio_ok = True
745                        else:
746                            # timestamp broken
747                            del self.start
748                            if mediainfo.DEBUG:
749                                print 'Timestamp error, correcting'
750                           
751            if hasattr(self, 'start') and self.start and \
752                   self.sequence_header_offset and self.video and self.audio:
753                break
754           
755            c += TS_PACKET_LENGTH
756
757               
758        if not self.sequence_header_offset:
759            return 0
760
761        if hasattr(self, 'start') and self.start:
762            self.keys.append('start')
763           
764        # fill in values for support functions:
765        self.__seek_size__   = 10000000  # 10 MB
766        self.__sample_size__ = 100000    # 100 k scanning
767        self.__search__      = self._find_timer_TS_
768        self.filename        = file.name
769
770        # get length of the file
771        self.length = self.get_length()
772        return 1
773
774
775    def _find_timer_TS_(self, buffer):
776        c = 0
777
778        while c + TS_PACKET_LENGTH < len(buffer):
779            if ord(buffer[c]) == ord(buffer[c+TS_PACKET_LENGTH]) == TS_SYNC:
780                break
781            c += 1
782        else:
783            return -1
784       
785        while c + TS_PACKET_LENGTH < len(buffer):
786            start = ord(buffer[c+1]) & 0x40
787            if not start:
788                c += TS_PACKET_LENGTH
789                continue
790
791            tsid = ((ord(buffer[c+1]) & 0x3F) << 8) + ord(buffer[c+2])
792            adapt = (ord(buffer[c+3]) & 0x30) >> 4
793
794            offset = 4
795            if adapt & 0x02:
796                # meta info present, skip it for now
797                offset += ord(buffer[c+offset]) + 1
798
799            if adapt & 0x01:
800                timestamp = self.ReadPESHeader(c+offset, buffer[c+offset:], tsid)[1]
801                return c + offset + timestamp
802            c += TS_PACKET_LENGTH
803        return -1
804
805
806
807    # Support functions ==============================================
808
809    def get_endpos(self):
810        """
811        get the last timestamp of the mpeg, return -1 if this is not possible
812        """
813        if not hasattr(self, 'filename') or not hasattr(self, 'start'):
814            return -1
815
816        file = open(self.filename)
817        file.seek(os.stat(self.filename)[stat.ST_SIZE]-self.__sample_size__)
818        buffer = file.read(self.__sample_size__)
819
820        end = -1
821        while 1:
822            pos = self.__search__(buffer)
823            if pos == -1:
824                break
825            end    = self.get_time(buffer[pos:])
826            buffer = buffer[pos+100:]
827           
828        file.close()
829        return end
830   
831
832    def get_length(self):
833        """
834        get the length in seconds, return -1 if this is not possible
835        """
836        end = self.get_endpos()
837        if end == -1:
838            return -1
839        if self.start > end:
840            return int(((long(1) << 33) - 1 ) / 90000) - self.start + end
841        return end - self.start
842   
843
844    def seek(self, end_time):
845        """
846        Return the byte position in the file where the time position
847        is 'pos' seconds. Return 0 if this is not possible
848        """
849        if not hasattr(self, 'filename') or not hasattr(self, 'start'):
850            return 0
851
852        file    = open(self.filename)
853        seek_to = 0
854       
855        while 1:
856            file.seek(self.__seek_size__, 1)
857            buffer = file.read(self.__sample_size__)
858            if len(buffer) < 10000:
859                break
860            pos = self.__search__(buffer)
861            if pos != -1:
862                # found something
863                if self.get_time(buffer[pos:]) >= end_time:
864                    # too much, break
865                    break
866            # that wasn't enough
867            seek_to = file.tell()
868
869        file.close()
870        return seek_to
871
872
873    def __scan__(self):
874        """
875        scan file for timestamps (may take a long time)
876        """
877        if not hasattr(self, 'filename') or not hasattr(self, 'start'):
878            return 0
879        file = open(self.filename)
880        print 'scanning file...'
881        while 1:
882            file.seek(self.__seek_size__ * 10, 1)
883            buffer = file.read(self.__sample_size__)
884            if len(buffer) < 10000:
885                break
886            pos = self.__search__(buffer)
887            if pos == -1:
888                continue
889            print self.get_time(buffer[pos:])
890
891        file.close()
892        print 'done'
893        print
894
895   
896   
897mmpython.registertype( 'video/mpeg', ('mpeg','mpg','mp4', 'ts'), mediainfo.TYPE_AV, MpegInfo )
Note: See TracBrowser for help on using the repository browser.