def process_video_track(self, elements): track = core.VideoStream() # Defaults track.codec = u'Unknown' track.fps = 0 self.set_track_defaults(track) for elem in elements: elem_id = elem.get_id() if elem_id == MATROSKA_CODEC_ID: track.codec = elem.get_str() elif elem_id == MATROSKA_FRAME_DURATION_ID: try: track.fps = 1 / (pow(10, -9) * (elem.get_value())) except ZeroDivisionError: pass elif elem_id == MATROSKA_VIDEO_SETTINGS_ID: d_width = d_height = None for settings_elem in self.process_one_level(elem): settings_elem_id = settings_elem.get_id() if settings_elem_id == MATROSKA_VIDEO_WIDTH_ID: track.width = settings_elem.get_value() elif settings_elem_id == MATROSKA_VIDEO_HEIGHT_ID: track.height = settings_elem.get_value() elif settings_elem_id == MATROSKA_VIDEO_DISPLAY_WIDTH_ID: d_width = settings_elem.get_value() elif settings_elem_id == MATROSKA_VIDEO_DISPLAY_HEIGHT_ID: d_height = settings_elem.get_value() elif settings_elem_id == MATROSKA_VIDEO_INTERLACED_ID: value = int(settings_elem.get_value()) self._set('interlaced', value) if None not in [d_width, d_height]: track.aspect = float(d_width) / d_height else: self.process_track_common(elem, track) # convert codec information # http://haali.cs.msu.ru/mkv/codecs.pdf if track.codec in FOURCCMap: track.codec = FOURCCMap[track.codec] elif '/' in track.codec and track.codec.split( '/')[0] + '/' in FOURCCMap: track.codec = FOURCCMap[track.codec.split('/')[0] + '/'] elif track.codec.endswith('FOURCC') and len(track.codec_private or '') == 40: track.codec = track.codec_private[16:20] elif track.codec.startswith('V_REAL/'): track.codec = track.codec[7:] elif track.codec.startswith('V_'): # FIXME: add more video codecs here track.codec = track.codec[2:] track.id = len(self.video) self.video.append(track) return track
def parseDisc(self, device): type = None if self.is_disc(device) != 2: raise core.ParseError() f = open(device) try: # read CD-XA001 at byte 1024 in sector 16 f.seek(2048 * 16 + 1024, 0) if f.read(8) != 'CD-XA001': raise core.ParseError() # read VIDEO_CD at sector 150 f.seek(2048 * 150, 0) typebuffer = f.read(8) if typebuffer != 'VIDEO_CD' and typebuffer != 'SUPERVCD': raise core.ParseError() # Read some bytes of the ISO9660 part to better guess VCD or SVCD. # Maybe this code is not needed and VIDEO_CD and SUPERVCD are enough. f.seek(2048 * 16, 0) iso9660 = f.read(2048 * 16) if typebuffer == 'VIDEO_CD' and iso9660.find('MPEGAV') > 0: type = 'VCD' elif typebuffer == 'SUPERVCD' and \ (iso9660.find('SVCD') > 0 or iso9660.find('MPEG2') > 0): type = 'SVCD' else: raise core.ParseError() finally: f.close() # read the tracks to generate the title list device = open(device) (first, last) = cdrom.audiocd_toc_header(device) lmin = 0 lsec = 0 num = 0 for i in range(first, last + 2): if i == last + 1: min, sec, frames = cdrom.audiocd_leadout(device) else: min, sec, frames = cdrom.audiocd_toc_entry(device, i) if num: vi = core.VideoStream() # XXX add more static information here, it's also possible # XXX to scan for more informations like fps # XXX Settings to MPEG1/2 is a wild guess, maybe the track # XXX isn't playable at all (e.g. the menu) if type == 'VCD': vi.codec = 'MPEG1' else: vi.codec = 'MPEG2' vi.length = (min - lmin) * 60 + (sec - lsec) self.tracks.append(vi) num += 1 lmin, lsec = min, sec device.close()
def __init__(self, file): core.AVContainer.__init__(self) self.sequence_header_offset = 0 self.mpeg_version = 2 # detect TS (fast scan) if not self.isTS(file): # detect system mpeg (many infos) if not self.isMPEG(file): # detect PES if not self.isPES(file): # Maybe it's MPEG-ES if self.isES(file): # If isES() succeeds, we needn't do anything further. return if file.name.lower().endswith('mpeg') or \ file.name.lower().endswith('mpg'): # This has to be an mpeg file. It could be a bad # recording from an ivtv based hardware encoder with # same bytes missing at the beginning. # Do some more digging... if not self.isMPEG(file, force=True) or \ not self.video or not self.audio: # does not look like an mpeg at all raise ParseError() else: # no mpeg at all raise ParseError() self.mime = 'video/mpeg' if not self.video: self.video.append(core.VideoStream()) if self.sequence_header_offset <= 0: return self.progressive(file) for vi in self.video: vi.width, vi.height = self.dxy(file) vi.fps, vi.aspect = self.framerate_aspect(file) vi.bitrate = self.bitrate(file) if self.length: vi.length = self.length if not self.type: self.type = 'MPEG Video' # set fourcc codec for video and audio vc, ac = 'MP2V', 'MP2A' if self.mpeg_version == 1: vc, ac = 'MPEG', 0x0050 for v in self.video: v.codec = vc for a in self.audio: if not a.codec: a.codec = ac
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 = core.AudioStream() ai.samplerate = retval['nSamplesPerSec'] ai.channels = retval['nChannels'] # FIXME: Bitrate calculation is completely wrong. #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'] ai.codec = retval['wFormatTag'] 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 v = struct.unpack('IIIII', t[20:40]) ( retval['biSizeImage'], retval['biXPelsPerMeter'], retval['biYPelsPerMeter'], retval['biClrUsed'], retval['biClrImportant'], ) = v vi = core.VideoStream() vi.codec = t[16:20] vi.width = retval['biWidth'] vi.height = retval['biHeight'] # FIXME: Bitrate calculation is completely wrong. #vi.bitrate = strh['dwRate'] vi.fps = float(strh['dwRate']) / strh['dwScale'] vi.length = strh['dwLength'] / vi.fps self.video.append(vi) return retval
def _read_header(self, object_id, s): if object_id == 'PROP': prop = struct.unpack('>9IHH', s) log.debug('PROP: %r' % prop) if object_id == 'MDPR': mdpr = struct.unpack('>H7I', s[:30]) log.debug('MDPR: %r' % mdpr) self.length = mdpr[7] / 1000.0 (stream_name_size, ) = struct.unpack('>B', s[30:31]) stream_name = s[31:31 + stream_name_size] pos = 31 + stream_name_size (mime_type_size, ) = struct.unpack('>B', s[pos:pos + 1]) mime = s[pos + 1:pos + 1 + mime_type_size] pos += mime_type_size + 1 (type_specific_len, ) = struct.unpack('>I', s[pos:pos + 4]) type_specific = s[pos + 4:pos + 4 + type_specific_len] pos += 4 + type_specific_len if mime[:5] == 'audio': ai = core.AudioStream() ai.id = mdpr[0] ai.bitrate = mdpr[2] self.audio.append(ai) elif mime[:5] == 'video': vi = core.VideoStream() vi.id = mdpr[0] vi.bitrate = mdpr[2] self.video.append(vi) else: log.debug('Unknown: %r' % mime) if object_id == 'CONT': pos = 0 (title_len, ) = struct.unpack('>H', s[pos:pos + 2]) self.title = s[2:title_len + 2] pos += title_len + 2 (author_len, ) = struct.unpack('>H', s[pos:pos + 2]) self.artist = s[pos + 2:pos + author_len + 2] pos += author_len + 2 (copyright_len, ) = struct.unpack('>H', s[pos:pos + 2]) self.copyright = s[pos + 2:pos + copyright_len + 2] pos += copyright_len + 2 (comment_len, ) = struct.unpack('>H', s[pos:pos + 2]) self.comment = s[pos + 2:pos + comment_len + 2]
def isES(self, file): file.seek(0, 0) try: header = struct.unpack('>LL', file.read(8)) except (struct.error, IOError): return False if header[0] != 0x1B3: return False # Is an mpeg video elementary stream self.mime = 'video/mpeg' video = core.VideoStream() video.width = header[1] >> 20 video.height = (header[1] >> 8) & 0xfff if header[1] & 0xf < len(FRAME_RATE): video.fps = FRAME_RATE[header[1] & 0xf] if (header[1] >> 4) & 0xf < len(ASPECT_RATIO): # FIXME: Empirically the aspect looks like PAR rather than DAR video.aspect = ASPECT_RATIO[(header[1] >> 4) & 0xf] self.video.append(video) return True
class MPEG4(core.AVContainer): """ Parser for the MP4 container format. This format is mostly identical to Apple Quicktime and 3GP files. It maps to mp4, mov, qt and some other extensions. """ table_mapping = {'QTUDTA': QTUDTA} def __init__(self, file): core.AVContainer.__init__(self) self._references = [] self.mime = 'video/quicktime' self.type = 'Quicktime Video' h = file.read(8) try: (size, type) = struct.unpack('>I4s', h) except struct.error: # EOF. raise ParseError() if type == 'ftyp': # file type information if size >= 12: # this should always happen if file.read(4) != 'qt ': # not a quicktime movie, it is a mpeg4 container self.mime = 'video/mp4' self.type = 'MPEG-4 Video' size -= 4 file.seek(size - 8, 1) h = file.read(8) (size, type) = struct.unpack('>I4s', h) while type in ['mdat', 'skip']: # movie data at the beginning, skip file.seek(size - 8, 1) h = file.read(8) (size, type) = struct.unpack('>I4s', h) if not type in ['moov', 'wide', 'free']: log.debug(u'invalid header: %r' % type) raise ParseError() # Extended size if size == 1: size = struct.unpack('>Q', file.read(8)) # Back over the atom header we just read, since _readatom expects the # file position to be at the start of an atom. file.seek(-8, 1) while self._readatom(file): pass if self._references: self._set('references', self._references) def _readatom(self, file): s = file.read(8) if len(s) < 8: return 0 atomsize, atomtype = struct.unpack('>I4s', s) if not str(atomtype).decode('latin1').isalnum(): # stop at nonsense data return 0 log.debug(u'%r [%X]' % (atomtype, atomsize)) if atomtype == 'udta': # Userdata (Metadata) pos = 0 tabl = {} i18ntabl = {} atomdata = file.read(atomsize - 8) while pos < atomsize - 12: (datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos + 8]) if ord(datatype[0]) == 169: # i18n Metadata... mypos = 8 + pos while mypos + 4 < datasize + pos: # first 4 Bytes are i18n header (tlen, lang) = struct.unpack('>HH', atomdata[mypos:mypos + 4]) i18ntabl[lang] = i18ntabl.get(lang, {}) l = atomdata[mypos + 4:mypos + tlen + 4] i18ntabl[lang][datatype[1:]] = l mypos += tlen + 4 elif datatype == 'WLOC': # Drop Window Location pass else: if ord(atomdata[pos + 8:pos + datasize][0]) > 1: tabl[datatype] = atomdata[pos + 8:pos + datasize] pos += datasize if len(i18ntabl.keys()) > 0: for k in i18ntabl.keys(): if QTLANGUAGES.has_key(k) and QTLANGUAGES[k] == 'en': self._appendtable('QTUDTA', i18ntabl[k]) self._appendtable('QTUDTA', tabl) else: log.debug(u'NO i18') self._appendtable('QTUDTA', tabl) elif atomtype == 'trak': atomdata = file.read(atomsize - 8) pos = 0 trackinfo = {} tracktype = None while pos < atomsize - 8: (datasize, datatype) = struct.unpack('>I4s', atomdata[pos:pos + 8]) if datatype == 'tkhd': tkhd = struct.unpack('>6I8x4H36xII', atomdata[pos + 8:pos + datasize]) trackinfo['width'] = tkhd[10] >> 16 trackinfo['height'] = tkhd[11] >> 16 trackinfo['id'] = tkhd[3] try: # XXX Timestamp of Seconds is since January 1st 1904! # XXX 2082844800 is the difference between Unix and # XXX Apple time. FIXME to work on Apple, too self.timestamp = int(tkhd[1]) - 2082844800 except Exception, e: log.exception(u'There was trouble extracting timestamp') elif datatype == 'mdia': pos += 8 datasize -= 8 log.debug(u'--> mdia information') while datasize: mdia = struct.unpack('>I4s', atomdata[pos:pos + 8]) if mdia[1] == 'mdhd': # Parse based on version of mdhd header. See # http://wiki.multimedia.cx/index.php?title=QuickTime_container#mdhd ver = ord(atomdata[pos + 8]) if ver == 0: mdhd = struct.unpack('>IIIIIhh', atomdata[pos + 8:pos + 8 + 24]) elif ver == 1: mdhd = struct.unpack('>IQQIQhh', atomdata[pos + 8:pos + 8 + 36]) else: mdhd = None if mdhd: # duration / time scale trackinfo['length'] = mdhd[4] / mdhd[3] if mdhd[5] in QTLANGUAGES: trackinfo['language'] = QTLANGUAGES[mdhd[5]] # mdhd[6] == quality self.length = max(self.length, mdhd[4] / mdhd[3]) elif mdia[1] == 'minf': # minf has only atoms inside pos -= (mdia[0] - 8) datasize += (mdia[0] - 8) elif mdia[1] == 'stbl': # stbl has only atoms inside pos -= (mdia[0] - 8) datasize += (mdia[0] - 8) elif mdia[1] == 'hdlr': hdlr = struct.unpack('>I4s4s', atomdata[pos + 8:pos + 8 + 12]) if hdlr[1] == 'mhlr': if hdlr[2] == 'vide': tracktype = 'video' if hdlr[2] == 'soun': tracktype = 'audio' elif mdia[1] == 'stsd': stsd = struct.unpack('>2I', atomdata[pos + 8:pos + 8 + 8]) if stsd[1] > 0: codec = atomdata[pos + 16:pos + 16 + 8] codec = struct.unpack('>I4s', codec) trackinfo['codec'] = codec[1] if codec[1] == 'jpeg': tracktype = 'image' elif mdia[1] == 'dinf': dref = struct.unpack('>I4s', atomdata[pos + 8:pos + 8 + 8]) log.debug(u' --> %r, %r (useless)' % mdia) if dref[1] == 'dref': num = struct.unpack('>I', atomdata[pos + 20:pos + 20 + 4])[0] rpos = pos + 20 + 4 for ref in range(num): # FIXME: do somthing if this references ref = struct.unpack('>I3s', atomdata[rpos:rpos + 7]) data = atomdata[rpos + 7:rpos + ref[0]] rpos += ref[0] else: if mdia[1].startswith('st'): log.debug(u' --> %r, %r (sample)' % mdia) elif mdia[1] == 'vmhd' and not tracktype: # indicates that this track is video tracktype = 'video' elif mdia[1] in ['vmhd', 'smhd'] and not tracktype: # indicates that this track is audio tracktype = 'audio' else: log.debug(u' --> %r, %r (unknown)' % mdia) pos += mdia[0] datasize -= mdia[0] elif datatype == 'udta': log.debug(u'udta: %r' % struct.unpack('>I4s', atomdata[:8])) else: if datatype == 'edts': log.debug(u'--> %r [%d] (edit list)' % \ (datatype, datasize)) else: log.debug(u'--> %r [%d] (unknown)' % \ (datatype, datasize)) pos += datasize info = None if tracktype == 'video': info = core.VideoStream() self.video.append(info) if tracktype == 'audio': info = core.AudioStream() self.audio.append(info) if info: for key, value in trackinfo.items(): setattr(info, key, value)
def _parseHeader(self, header, granule): headerlen = len(header) flags = ord(header[0]) if headerlen >= 30 and header[1:7] == 'vorbis': ai = core.AudioStream() ai.version, ai.channels, ai.samplerate, bitrate_max, ai.bitrate, \ bitrate_min, blocksize, framing = \ struct.unpack('<IBIiiiBB', header[7:7 + 23]) ai.codec = 'Vorbis' #ai.granule = granule #ai.length = granule / ai.samplerate self.audio.append(ai) self.all_streams.append(ai) elif headerlen >= 7 and header[1:7] == 'theora': # Theora Header # XXX Finish Me vi = core.VideoStream() vi.codec = 'theora' self.video.append(vi) self.all_streams.append(vi) elif headerlen >= 142 and \ header[1:36] == 'Direct Show Samples embedded in Ogg': # Old Directshow format # XXX Finish Me vi = core.VideoStream() vi.codec = 'dshow' self.video.append(vi) self.all_streams.append(vi) elif flags & PACKET_TYPE_BITS == PACKET_TYPE_HEADER and \ headerlen >= struct.calcsize(STREAM_HEADER_VIDEO) + 1: # New Directshow Format htype = header[1:9] if htype[:5] == 'video': sh = header[9:struct.calcsize(STREAM_HEADER_VIDEO) + 9] streamheader = struct.unpack(STREAM_HEADER_VIDEO, sh) vi = core.VideoStream() (type, ssize, timeunit, samplerate, vi.length, buffersize, \ vi.bitrate, vi.width, vi.height) = streamheader vi.width /= 65536 vi.height /= 65536 # XXX length, bitrate are very wrong vi.codec = type vi.fps = 10000000 / timeunit self.video.append(vi) self.all_streams.append(vi) elif htype[:5] == 'audio': sha = header[9:struct.calcsize(STREAM_HEADER_AUDIO) + 9] streamheader = struct.unpack(STREAM_HEADER_AUDIO, sha) ai = core.AudioStream() (type, ssize, timeunit, ai.samplerate, ai.length, buffersize, \ ai.bitrate, ai.channels, bloc, ai.bitrate) = streamheader self.samplerate = ai.samplerate log.debug(u'Samplerate %d' % self.samplerate) self.audio.append(ai) self.all_streams.append(ai) elif htype[:4] == 'text': subtitle = core.Subtitle() # FIXME: add more info self.subtitles.append(subtitle) self.all_streams.append(subtitle) else: log.debug(u'Unknown Header')
def __init__(self, file): core.AVContainer.__init__(self) self.mime = 'video/flv' self.type = 'Flash Video' data = file.read(13) if len(data) < 13 or struct.unpack('>3sBBII', data)[0] != 'FLV': raise ParseError() for _ in range(10): if self.audio and self.video: break data = file.read(11) if len(data) < 11: break chunk = struct.unpack('>BH4BI', data) size = (chunk[1] << 8) + chunk[2] if chunk[0] == FLV_TAG_TYPE_AUDIO: flags = ord(file.read(1)) if not self.audio: a = core.AudioStream() a.channels = (flags & FLV_AUDIO_CHANNEL_MASK) + 1 srate = (flags & FLV_AUDIO_SAMPLERATE_MASK) a.samplerate = ( 44100 << (srate >> FLV_AUDIO_SAMPLERATE_OFFSET) >> 3) codec = (flags & FLV_AUDIO_CODECID_MASK ) >> FLV_AUDIO_CODECID_OFFSET if codec < len(FLV_AUDIO_CODECID): a.codec = FLV_AUDIO_CODECID[codec] self.audio.append(a) file.seek(size - 1, 1) elif chunk[0] == FLV_TAG_TYPE_VIDEO: flags = ord(file.read(1)) if not self.video: v = core.VideoStream() codec = (flags & FLV_VIDEO_CODECID_MASK) - 2 if codec < len(FLV_VIDEO_CODECID): v.codec = FLV_VIDEO_CODECID[codec] # width and height are in the meta packet, but I have # no file with such a packet inside. So maybe we have # to decode some parts of the video. self.video.append(v) file.seek(size - 1, 1) elif chunk[0] == FLV_TAG_TYPE_META: log.info('metadata %r', str(chunk)) metadata = file.read(size) try: while metadata: length, value = self._parse_value(metadata) if isinstance(value, dict): log.info('metadata: %r', value) if value.get('creator'): self.copyright = value.get('creator') if value.get('width'): self.width = value.get('width') if value.get('height'): self.height = value.get('height') if value.get('duration'): self.length = value.get('duration') self._appendtable('FLVINFO', value) if not length: # parse error break metadata = metadata[length:] except (IndexError, struct.error, TypeError): pass else: log.info('unkown %r', str(chunk)) file.seek(size, 1) file.seek(4, 1)
def ReadPESHeader(self, offset, buffer, id=0): """ Parse a PES header. Since it starts with 0x00 0x00 0x01 like 'normal' mpegs, this function will return (0, None) when it is no PES header or (packet length, timestamp position (maybe None)) http://dvd.sourceforge.net/dvdinfo/pes-hdr.html """ if not buffer[0:3] == '\x00\x00\x01': return 0, None packet_length = (ord(buffer[4]) << 8) + ord(buffer[5]) + 6 align = ord(buffer[6]) & 4 header_length = ord(buffer[8]) # PES ID (starting with 001) if ord(buffer[3]) & 0xE0 == 0xC0: id = id or ord(buffer[3]) & 0x1F for a in self.audio: if a.id == id: break else: self.audio.append(core.AudioStream()) self.audio[-1]._set('id', id) elif ord(buffer[3]) & 0xF0 == 0xE0: id = id or ord(buffer[3]) & 0xF for v in self.video: if v.id == id: break else: self.video.append(core.VideoStream()) self.video[-1]._set('id', id) # new mpeg starting if buffer[header_length + 9:header_length + 13] == \ '\x00\x00\x01\xB3' and not self.sequence_header_offset: # yes, remember offset for later use self.sequence_header_offset = offset + header_length + 9 elif ord(buffer[3]) == 189 or ord(buffer[3]) == 191: # private stream. we don't know, but maybe we can guess later id = id or ord(buffer[3]) & 0xF if align and \ buffer[header_length + 9:header_length + 11] == '\x0b\x77': # AC3 stream for a in self.audio: if a.id == id: break else: self.audio.append(core.AudioStream()) self.audio[-1]._set('id', id) self.audio[-1].codec = 0x2000 # AC3 else: # unknown content pass ptsdts = ord(buffer[7]) >> 6 if ptsdts and ptsdts == ord(buffer[9]) >> 4: if ord(buffer[9]) >> 4 != ptsdts: log.warning(u'WARNING: bad PTS/DTS, please contact us') return packet_length, None # timestamp = self.ReadPTS(buffer[9:14]) high = ((ord(buffer[9]) & 0xF) >> 1) med = (ord(buffer[10]) << 7) + (ord(buffer[11]) >> 1) low = (ord(buffer[12]) << 7) + (ord(buffer[13]) >> 1) return packet_length, 9 return packet_length, None
def ReadHeader(self, buffer, offset): """ Handle MPEG header in buffer on position offset Return None on error, new offset or 0 if the new offset can't be scanned """ if buffer[offset:offset + 3] != '\x00\x00\x01': return None id = ord(buffer[offset + 3]) if id == PADDING_PKT: return offset + (ord(buffer[offset + 4]) << 8) + \ ord(buffer[offset + 5]) + 6 if id == PACK_PKT: if ord(buffer[offset + 4]) & 0xF0 == 0x20: self.type = 'MPEG-1 Video' self.get_time = self.ReadSCRMpeg1 self.mpeg_version = 1 return offset + 12 elif (ord(buffer[offset + 4]) & 0xC0) == 0x40: self.type = 'MPEG-2 Video' self.get_time = self.ReadSCRMpeg2 return offset + (ord(buffer[offset + 13]) & 0x07) + 14 else: # I have no idea what just happened, but for some DVB # recordings done with mencoder this points to a # PACK_PKT describing something odd. Returning 0 here # (let's hope there are no extensions in the header) # fixes it. return 0 if 0xC0 <= id <= 0xDF: # code for audio stream for a in self.audio: if a.id == id: break else: self.audio.append(core.AudioStream()) self.audio[-1]._set('id', id) return 0 if 0xE0 <= id <= 0xEF: # code for video stream for v in self.video: if v.id == id: break else: self.video.append(core.VideoStream()) self.video[-1]._set('id', id) return 0 if id == SEQ_HEAD: # sequence header, remember that position for later use self.sequence_header_offset = offset return 0 if id in [PRIVATE_STREAM1, PRIVATE_STREAM2]: # private stream. we don't know, but maybe we can guess later add = ord(buffer[offset + 8]) # if (ord(buffer[offset+6]) & 4) or 1: # id = ord(buffer[offset+10+add]) if buffer[offset + 11 + add:offset + 15 + add].find('\x0b\x77') != -1: # AC3 stream for a in self.audio: if a.id == id: break else: self.audio.append(core.AudioStream()) self.audio[-1]._set('id', id) self.audio[-1].codec = 0x2000 # AC3 return 0 if id == SYS_PKT: return 0 if id == EXT_START: return 0 return 0
def _getnextheader(self, s): r = struct.unpack('<16sQ', s[:24]) (guidstr, objsize) = r guid = self._parseguid(guidstr) if guid == GUIDS['ASF_File_Properties_Object']: log.debug('File Properties Object') val = struct.unpack('<16s6Q4I', s[24:24 + 80]) (fileid, size, date, packetcount, duration, senddur, preroll, flags, minpack, maxpack, maxbr) = val # FIXME: parse date to timestamp self.length = duration / 10000000.0 elif guid == GUIDS['ASF_Stream_Properties_Object']: log.debug('Stream Properties Object [%d]' % objsize) streamtype = self._parseguid(s[24:40]) errortype = self._parseguid(s[40:56]) offset, typelen, errorlen, flags = struct.unpack('<QIIH', s[56:74]) strno = flags & 0x7f encrypted = flags >> 15 if encrypted: self._set('encrypted', True) if streamtype == GUIDS['ASF_Video_Media']: vi = core.VideoStream() vi.width, vi.height, depth, codec, = struct.unpack('<4xII2xH4s', s[89:89 + 20]) vi.codec = codec vi.id = strno self.video.append(vi) elif streamtype == GUIDS['ASF_Audio_Media']: ai = core.AudioStream() twocc, ai.channels, ai.samplerate, bitrate, block, \ ai.samplebits, = struct.unpack('<HHIIHH', s[78:78 + 16]) ai.bitrate = 8 * bitrate ai.codec = twocc ai.id = strno self.audio.append(ai) self._apply_extinfo(strno) elif guid == GUIDS['ASF_Extended_Stream_Properties_Object']: streamid, langid, frametime = struct.unpack('<HHQ', s[72:84]) (bitrate,) = struct.unpack('<I', s[40:40 + 4]) if streamid not in self._extinfo: self._extinfo[streamid] = [None, None, None, {}] if frametime == 0: # Problaby VFR, report as 1000fps (which is what MPlayer does) frametime = 10000.0 self._extinfo[streamid][:3] = [bitrate, 10000000.0 / frametime, langid] self._apply_extinfo(streamid) elif guid == GUIDS['ASF_Header_Extension_Object']: log.debug('ASF_Header_Extension_Object %d' % objsize) size = struct.unpack('<I', s[42:46])[0] data = s[46:46 + size] while len(data): log.debug('Sub:') h = self._getnextheader(data) data = data[h[1]:] elif guid == GUIDS['ASF_Codec_List_Object']: log.debug('List Object') pass elif guid == GUIDS['ASF_Error_Correction_Object']: log.debug('Error Correction') pass elif guid == GUIDS['ASF_Content_Description_Object']: log.debug('Content Description Object') val = struct.unpack('<5H', s[24:24 + 10]) pos = 34 strings = [] for i in val: ss = s[pos:pos + i].replace('\0', '').lstrip().rstrip() strings.append(ss) pos += i # Set empty strings to None strings = [x or None for x in strings] self.title, self.artist, self.copyright, self.caption, rating = strings elif guid == GUIDS['ASF_Extended_Content_Description_Object']: (count,) = struct.unpack('<H', s[24:26]) pos = 26 descriptor = {} for i in range(0, count): # Read additional content descriptors d = self._parsekv(s[pos:]) pos += d[0] descriptor[d[1]] = d[2] self._appendtable('ASFDESCRIPTOR', descriptor) elif guid == GUIDS['ASF_Metadata_Object']: (count,) = struct.unpack('<H', s[24:26]) pos = 26 streams = {} for i in range(0, count): # Read additional content descriptors size, key, value, strno = self._parsekv2(s[pos:]) if strno not in streams: streams[strno] = {} streams[strno][key] = value pos += size for strno, metadata in streams.items(): if strno not in self._extinfo: self._extinfo[strno] = [None, None, None, {}] self._extinfo[strno][3].update(metadata) self._apply_extinfo(strno) elif guid == GUIDS['ASF_Language_List_Object']: count = struct.unpack('<H', s[24:26])[0] pos = 26 for i in range(0, count): idlen = struct.unpack('<B', s[pos:pos + 1])[0] idstring = s[pos + 1:pos + 1 + idlen] idstring = unicode(idstring, 'utf-16').replace('\0', '') log.debug('Language: %d/%d: %r' % (i + 1, count, idstring)) self._languages.append(idstring) pos += 1 + idlen elif guid == GUIDS['ASF_Stream_Bitrate_Properties_Object']: # This record contains stream bitrate with payload overhead. For # audio streams, we should have the average bitrate from # ASF_Stream_Properties_Object. For video streams, we get it from # ASF_Extended_Stream_Properties_Object. So this record is not # used. pass elif guid == GUIDS['ASF_Content_Encryption_Object'] or \ guid == GUIDS['ASF_Extended_Content_Encryption_Object']: self._set('encrypted', True) else: # Just print the type: for h in GUIDS.keys(): if GUIDS[h] == guid: log.debug('Unparsed %r [%d]' % (h, objsize)) break else: u = "%.8X-%.4X-%.4X-%.2X%.2X-%s" % guid log.debug('unknown: len=%d [%d]' % (len(u), objsize)) return r