~ubuntu-branches/debian/jessie/subdownloader/jessie

« back to all changes in this revision

Viewing changes to modules/mmpython/video/mpeginfo.py

  • Committer: Package Import Robot
  • Author(s): Emilien Klein, Jakub Wilk, Emilien Klein
  • Date: 2014-10-22 00:09:01 UTC
  • mfrom: (1.2.2)
  • Revision ID: package-import@ubuntu.com-20141022000901-968rxi9a15icm032
Tags: 2.0.18-1
* New upstream release (Closes: #687126, #751920).

[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.
* Fix watch file.

[ Emilien Klein ]
* Transition from python_support to dh_python2. Affects files:
  - d/control
  - d/rules
  - d/subdownloader.install
  - d/subdownloader.links
* d/control:
  - Bump Standards-Version from 3.9.1 to 3.9.6 (one change performed to
    d/rules recommended target)
  - Replace Marco Rodrigues by myself in Uploaders
* d/rules: Fix debian-rules-missing-recommended-target Lintian warning
* d/patches/fix-non-ascii-download-path.patch: deleted as applied upstream
* d/patches/desktop-file-remove-path.patch: make the desktop file usable
* d/patches/no-mime-type-in-desktop-file.patch: Mime Type is unused, as
    the .desktop file doesn't provide an Exec code.
* d/patches/resize-icon.patch: Resize icon from 64x64 to 32x32

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#if 0
 
2
# $Id: mpeginfo.py 398 2005-02-15 18:52:51Z dischi $
 
3
# $Log$
 
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 3 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
 
 
73
import re
 
74
import os
 
75
import struct
 
76
import string
 
77
import fourcc
 
78
 
 
79
from modules.mmpython import mediainfo
 
80
#import mmpython
 
81
import stat
 
82
 
 
83
#------------------------------------------------------------------------
 
84
# START_CODE
 
85
#
 
86
# Start Codes, with 'slice' occupying 0x01..0xAF
 
87
#------------------------------------------------------------------------
 
88
START_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
}
 
100
for i in range(0x01,0xAF): 
 
101
    START_CODE[i] = 'slice_start_code'
 
102
 
 
103
#------------------------------------------------------------------------
 
104
# START CODES
 
105
#------------------------------------------------------------------------
 
106
PICTURE   = 0x00
 
107
USERDATA  = 0xB2
 
108
SEQ_HEAD  = 0xB3
 
109
SEQ_ERR   = 0xB4
 
110
EXT_START = 0xB5
 
111
SEQ_END   = 0xB7
 
112
GOP       = 0xB8
 
113
 
 
114
SEQ_START_CODE  = 0xB3
 
115
PACK_PKT        = 0xBA
 
116
SYS_PKT         = 0xBB
 
117
PADDING_PKT     = 0xBE
 
118
AUDIO_PKT       = 0xC0
 
119
VIDEO_PKT       = 0xE0
 
120
PRIVATE_STREAM1 = 0xBD
 
121
PRIVATE_STREAM2 = 0xBf
 
122
 
 
123
TS_PACKET_LENGTH = 188
 
124
TS_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
#------------------------------------------------------------------------ 
 
145
FRAME_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
#------------------------------------------------------------------------ 
 
165
ASPECT_RATIO = [ 'Forbidden',
 
166
                 '1/1 (VGA)',
 
167
                 '4/3 (TV)',
 
168
                 '16/9 (Large TV)',
 
169
                 '2.21/1 (Cinema)',
 
170
               ]
 
171
 
 
172
 
 
173
class 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
    
 
897
#mmpython.registertype( 'video/mpeg', ('mpeg','mpg','mp4', 'ts'), mediainfo.TYPE_AV, MpegInfo )