1
0
mirror of https://github.com/moparisthebest/SickRage synced 2024-11-16 06:15:09 -05:00
SickRage/lib/hachoir_parser/video/mpeg_video.py
echel0n 0d9fbc1ad7 Welcome to our SickBeard-TVRage Edition ...
This version of SickBeard uses both TVDB and TVRage to search and gather it's series data from allowing you to now have access to and download shows that you couldn't before because of being locked into only what TheTVDB had to offer.

Also this edition is based off the code we used in our XEM editon so it does come with scene numbering support as well as all the other features our XEM edition has to offer.

Please before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present, we HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk!

Enjoy!
2014-03-09 22:39:12 -07:00

577 lines
22 KiB
Python

"""
Moving Picture Experts Group (MPEG) video version 1 and 2 parser.
Information:
- http://www.mpucoder.com/DVD/
- http://dvd.sourceforge.net/dvdinfo/
- http://www.mit.jyu.fi/mweber/leffakone/software/parsempegts/
- http://homepage.mac.com/rnc/EditMpegHeaderIFO.html
- http://standards.iso.org/ittf/PubliclyAvailableStandards/c025029_ISO_IEC_TR_11172-5_1998(E)_Software_Simulation.zip
This is a sample encoder/decoder implementation for MPEG-1.
Author: Victor Stinner
Creation date: 15 september 2006
"""
from lib.hachoir_parser import Parser
from lib.hachoir_parser.audio.mpeg_audio import MpegAudioFile
from lib.hachoir_core.field import (FieldSet,
FieldError, ParserError,
Bit, Bits, Bytes, RawBits, PaddingBits, NullBits,
UInt8, UInt16,
RawBytes, PaddingBytes,
Enum)
from lib.hachoir_core.endian import BIG_ENDIAN
from lib.hachoir_core.stream import StringInputStream
from lib.hachoir_core.text_handler import textHandler, hexadecimal
class FragmentGroup:
def __init__(self, parser):
self.items = []
self.parser = parser
self.args = {}
def add(self, item):
self.items.append(item)
def createInputStream(self):
# FIXME: Use lazy stream creation
data = []
for item in self.items:
if 'rawdata' in item:
data.append( item["rawdata"].value )
data = "".join(data)
# FIXME: Use smarter code to send arguments
tags = {"class": self.parser, "args": self.args}
tags = tags.iteritems()
return StringInputStream(data, "<fragment group>", tags=tags)
class CustomFragment(FieldSet):
def __init__(self, parent, name, size, parser, description=None, group=None):
FieldSet.__init__(self, parent, name, description, size=size)
if not group:
group = FragmentGroup(parser)
self.group = group
self.group.add(self)
def createFields(self):
yield RawBytes(self, "rawdata", self.size//8)
def _createInputStream(self, **args):
return self.group.createInputStream()
class Timestamp(FieldSet):
static_size = 36
def createValue(self):
return (self["c"].value << 30) + (self["b"].value << 15) + self["a"].value
def createFields(self):
yield Bits(self, "c", 3)
yield Bit(self, "sync[]") # =True
yield Bits(self, "b", 15)
yield Bit(self, "sync[]") # =True
yield Bits(self, "a", 15)
yield Bit(self, "sync[]") # =True
class SCR(FieldSet):
static_size = 35
def createFields(self):
yield Bits(self, "scr_a", 3)
yield Bit(self, "sync[]") # =True
yield Bits(self, "scr_b", 15)
yield Bit(self, "sync[]") # =True
yield Bits(self, "scr_c", 15)
class PackHeader(FieldSet):
def createFields(self):
if self.stream.readBits(self.absolute_address, 2, self.endian) == 1:
# MPEG version 2
yield Bits(self, "sync[]", 2)
yield SCR(self, "scr")
yield Bit(self, "sync[]")
yield Bits(self, "scr_ext", 9)
yield Bit(self, "sync[]")
yield Bits(self, "mux_rate", 22)
yield Bits(self, "sync[]", 2)
yield PaddingBits(self, "reserved", 5, pattern=1)
yield Bits(self, "stuffing_length", 3)
count = self["stuffing_length"].value
if count:
yield PaddingBytes(self, "stuffing", count, pattern="\xff")
else:
# MPEG version 1
yield Bits(self, "sync[]", 4)
yield Bits(self, "scr_a", 3)
yield Bit(self, "sync[]")
yield Bits(self, "scr_b", 15)
yield Bit(self, "sync[]")
yield Bits(self, "scr_c", 15)
yield Bits(self, "sync[]", 2)
yield Bits(self, "mux_rate", 22)
yield Bit(self, "sync[]")
def validate(self):
if self["mux_rate"].value == 0:
return "Invalid mux rate"
sync0 = self["sync[0]"]
if (sync0.size == 2 and sync0.value == 1):
# MPEG2
pass
if not self["sync[1]"].value \
or not self["sync[2]"].value \
or self["sync[3]"].value != 3:
return "Invalid synchronisation bits"
elif (sync0.size == 4 and sync0.value == 2):
# MPEG1
if not self["sync[1]"].value \
or not self["sync[2]"].value \
or self["sync[3]"].value != 3 \
or not self["sync[4]"].value:
return "Invalid synchronisation bits"
else:
return "Unknown version"
return True
class SystemHeader(FieldSet):
def createFields(self):
yield Bits(self, "marker[]", 1)
yield Bits(self, "rate_bound", 22)
yield Bits(self, "marker[]", 1)
yield Bits(self, "audio_bound", 6)
yield Bit(self, "fixed_bitrate")
yield Bit(self, "csps", description="Constrained system parameter stream")
yield Bit(self, "audio_lock")
yield Bit(self, "video_lock")
yield Bits(self, "marker[]", 1)
yield Bits(self, "video_bound", 5)
length = self['../length'].value-5
if length:
yield RawBytes(self, "raw[]", length)
class defaultParser(FieldSet):
def createFields(self):
yield RawBytes(self, "data", self["../length"].value)
class Padding(FieldSet):
def createFields(self):
yield PaddingBytes(self, "data", self["../length"].value)
class VideoExtension2(FieldSet):
def createFields(self):
yield Bit(self, "sync[]") # =True
yield Bits(self, "ext_length", 7)
yield NullBits(self, "reserved[]", 8)
size = self["ext_length"].value
if size:
yield RawBytes(self, "ext_bytes", size)
class VideoExtension1(FieldSet):
def createFields(self):
yield Bit(self, "has_private")
yield Bit(self, "has_pack_lgth")
yield Bit(self, "has_pack_seq")
yield Bit(self, "has_pstd_buffer")
yield Bits(self, "sync[]", 3) # =7
yield Bit(self, "has_extension2")
if self["has_private"].value:
yield RawBytes(self, "private", 16)
if self["has_pack_lgth"].value:
yield UInt8(self, "pack_lgth")
if self["has_pack_seq"].value:
yield Bit(self, "sync[]") # =True
yield Bits(self, "pack_seq_counter", 7)
yield Bit(self, "sync[]") # =True
yield Bit(self, "mpeg12_id")
yield Bits(self, "orig_stuffing_length", 6)
if self["has_pstd_buffer"].value:
yield Bits(self, "sync[]", 2) # =1
yield Enum(Bit(self, "pstd_buffer_scale"),
{True: "128 bytes", False: "1024 bytes"})
yield Bits(self, "pstd_size", 13)
class VideoSeqHeader(FieldSet):
ASPECT=["forbidden", "1.0000 (VGA etc.)", "0.6735",
"0.7031 (16:9, 625line)", "0.7615", "0.8055",
"0.8437 (16:9, 525line)", "0.8935",
"0.9157 (CCIR601, 625line)", "0.9815", "1.0255", "1.0695",
"1.0950 (CCIR601, 525line)", "1.1575", "1.2015", "reserved"]
FRAMERATE=["forbidden", "23.976 fps", "24 fps", "25 fps", "29.97 fps",
"30 fps", "50 fps", "59.94 fps", "60 fps"]
def createFields(self):
yield Bits(self, "width", 12)
yield Bits(self, "height", 12)
yield Enum(Bits(self, "aspect", 4), self.ASPECT)
yield Enum(Bits(self, "frame_rate", 4), self.FRAMERATE)
yield Bits(self, "bit_rate", 18, "Bit rate in units of 50 bytes")
yield Bits(self, "sync[]", 1) # =1
yield Bits(self, "vbv_size", 10, "Video buffer verifier size, in units of 16768")
yield Bit(self, "constrained_params_flag")
yield Bit(self, "has_intra_quantizer")
if self["has_intra_quantizer"].value:
for i in range(64):
yield Bits(self, "intra_quantizer[]", 8)
yield Bit(self, "has_non_intra_quantizer")
if self["has_non_intra_quantizer"].value:
for i in range(64):
yield Bits(self, "non_intra_quantizer[]", 8)
class GroupStart(FieldSet):
def createFields(self):
yield Bit(self, "drop_frame")
yield Bits(self, "time_hh", 5)
yield Bits(self, "time_mm", 6)
yield PaddingBits(self, "time_pad[]", 1)
yield Bits(self, "time_ss", 6)
yield Bits(self, "time_ff", 6)
yield Bit(self, "closed_group")
yield Bit(self, "broken_group")
yield PaddingBits(self, "pad[]", 5)
class PacketElement(FieldSet):
def createFields(self):
yield Bits(self, "sync[]", 2) # =2
if self["sync[0]"].value != 2:
raise ParserError("Unknown video elementary data")
yield Bits(self, "is_scrambled", 2)
yield Bits(self, "priority", 1)
yield Bit(self, "alignment")
yield Bit(self, "is_copyrighted")
yield Bit(self, "is_original")
yield Bit(self, "has_pts", "Presentation Time Stamp")
yield Bit(self, "has_dts", "Decode Time Stamp")
yield Bit(self, "has_escr", "Elementary Stream Clock Reference")
yield Bit(self, "has_es_rate", "Elementary Stream rate")
yield Bit(self, "dsm_trick_mode")
yield Bit(self, "has_copy_info")
yield Bit(self, "has_prev_crc", "If True, previous PES packet CRC follows")
yield Bit(self, "has_extension")
yield UInt8(self, "size")
# Time stamps
if self["has_pts"].value:
yield Bits(self, "sync[]", 4) # =2, or 3 if has_dts=True
yield Timestamp(self, "pts")
if self["has_dts"].value:
if not(self["has_pts"].value):
raise ParserError("Invalid PTS/DTS values")
yield Bits(self, "sync[]", 4) # =1
yield Timestamp(self, "dts")
if self["has_escr"].value:
yield Bits(self, "sync[]", 2) # =0
yield SCR(self, "escr")
if self["has_es_rate"].value:
yield Bit(self, "sync[]") # =True
yield Bits(self, "es_rate", 14) # in units of 50 bytes/second
yield Bit(self, "sync[]") # =True
if self["has_copy_info"].value:
yield Bit(self, "sync[]") # =True
yield Bits(self, "copy_info", 7)
if self["has_prev_crc"].value:
yield textHandler(UInt16(self, "prev_crc"), hexadecimal)
# --- Extension ---
if self["has_extension"].value:
yield VideoExtension1(self, "extension")
if self["extension/has_extension2"].value:
yield VideoExtension2(self, "extension2")
class VideoExtension(FieldSet):
EXT_TYPE = {1:'Sequence',2:'Sequence Display',8:'Picture Coding'}
def createFields(self):
yield Enum(Bits(self, "ext_type", 4), self.EXT_TYPE)
ext_type=self['ext_type'].value
if ext_type==1:
# Sequence extension
yield Bits(self, 'profile_and_level', 8)
yield Bit(self, 'progressive_sequence')
yield Bits(self, 'chroma_format', 2)
yield Bits(self, 'horiz_size_ext', 2)
yield Bits(self, 'vert_size_ext', 2)
yield Bits(self, 'bit_rate_ext', 12)
yield Bits(self, 'pad[]', 1)
yield Bits(self, 'vbv_buffer_size_ext', 8)
yield Bit(self, 'low_delay')
yield Bits(self, 'frame_rate_ext_n', 2)
yield Bits(self, 'frame_rate_ext_d', 5)
elif ext_type==2:
# Sequence Display extension
yield Bits(self, 'video_format', 3)
yield Bit(self, 'color_desc_present')
if self['color_desc_present'].value:
yield UInt8(self, 'color_primaries')
yield UInt8(self, 'transfer_characteristics')
yield UInt8(self, 'matrix_coeffs')
yield Bits(self, 'display_horiz_size', 14)
yield Bits(self, 'pad[]', 1)
yield Bits(self, 'display_vert_size', 14)
yield NullBits(self, 'pad[]', 3)
elif ext_type==8:
yield Bits(self, 'f_code[0][0]', 4, description="forward horizontal")
yield Bits(self, 'f_code[0][1]', 4, description="forward vertical")
yield Bits(self, 'f_code[1][0]', 4, description="backward horizontal")
yield Bits(self, 'f_code[1][1]', 4, description="backward vertical")
yield Bits(self, 'intra_dc_precision', 2)
yield Bits(self, 'picture_structure', 2)
yield Bit(self, 'top_field_first')
yield Bit(self, 'frame_pred_frame_dct')
yield Bit(self, 'concealment_motion_vectors')
yield Bit(self, 'q_scale_type')
yield Bit(self, 'intra_vlc_format')
yield Bit(self, 'alternate_scan')
yield Bit(self, 'repeat_first_field')
yield Bit(self, 'chroma_420_type')
yield Bit(self, 'progressive_frame')
yield Bit(self, 'composite_display')
if self['composite_display'].value:
yield Bit(self, 'v_axis')
yield Bits(self, 'field_sequence', 3)
yield Bit(self, 'sub_carrier')
yield Bits(self, 'burst_amplitude', 7)
yield Bits(self, 'sub_carrier_phase', 8)
yield NullBits(self, 'pad[]', 2)
else:
yield NullBits(self, 'pad[]', 6)
else:
yield RawBits(self, "raw[]", 4)
class VideoPicture(FieldSet):
CODING_TYPE = ["forbidden","intra-coded (I)",
"predictive-coded (P)",
"bidirectionally-predictive-coded (B)",
"dc intra-coded (D)", "reserved",
"reserved", "reserved"]
def createFields(self):
yield Bits(self, "temporal_ref", 10)
yield Enum(Bits(self, "coding_type", 3), self.CODING_TYPE)
yield Bits(self, "vbv_delay", 16)
if self['coding_type'].value in (2,3):
# predictive coding
yield Bit(self, 'full_pel_fwd_vector')
yield Bits(self, 'forward_f_code', 3)
if self['coding_type'].value == 3:
# bidi predictive coding
yield Bit(self, 'full_pel_back_vector')
yield Bits(self, 'backward_f_code', 3)
yield Bits(self, "padding", 8-(self.current_size % 8))
class VideoSlice(FieldSet):
def createFields(self):
yield Bits(self, "quantizer_scale", 5)
start=self.absolute_address+self.current_size+3
pos=self.stream.searchBytes('\0\0\1',start,start+1024*1024*8) # seek forward by at most 1MB
if pos is None: pos=self.root.size
yield RawBits(self, "data", pos-start+3)
class VideoChunk(FieldSet):
tag_info = {
0x00: ("pict_start[]", VideoPicture, "Picture start"),
0xB2: ("data_start[]", None, "Data start"),
0xB3: ("seq_hdr[]", VideoSeqHeader,"Sequence header"),
0xB4: ("seq_err[]", None, "Sequence error"),
0xB5: ("ext_start[]", VideoExtension,"Extension start"),
0xB7: ("seq_end[]", None, "Sequence end"),
0xB8: ("group_start[]", GroupStart, "Group start"),
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
tag = self["tag"].value
if tag in self.tag_info:
self._name, self.parser, self._description = self.tag_info[tag]
if not self.parser:
self.parser = defaultParser
elif 0x01 <= tag <= 0xaf:
self._name, self.parser, self._description = ('slice[]', VideoSlice, 'Picture slice')
else:
self.parser = defaultParser
def createFields(self):
yield Bytes(self, "sync", 3)
yield textHandler(UInt8(self, "tag"), hexadecimal)
if self.parser and self['tag'].value != 0xb7:
yield self.parser(self, "content")
class VideoStream(Parser):
endian = BIG_ENDIAN
def createFields(self):
while self.current_size < self.size:
pos=self.stream.searchBytes('\0\0\1',self.current_size,self.current_size+1024*1024*8) # seek forward by at most 1MB
if pos is not None:
padsize = pos-self.current_size
if padsize:
yield PaddingBytes(self, "pad[]", padsize//8)
yield VideoChunk(self, "chunk[]")
class Stream(FieldSet):
def createFields(self):
padding=0
position=0
while True:
next=ord(self.parent.stream.readBytes(self.absolute_address+self.current_size+position, 1))
if next == 0xff:
padding+=1
position+=8
elif padding:
yield PaddingBytes(self, "pad[]", padding)
padding=None
position=0
elif 0x40 <= next <= 0x7f:
yield Bits(self, "scale_marker", 2) # 1
yield Bit(self, "scale")
scale=self['scale'].value
if scale:
scaleval=1024
else:
scaleval=128
yield textHandler(Bits(self, "size", 13), lambda field:str(field.value*scaleval))
elif 0x00 <= next <= 0x3f:
yield Bits(self, "ts_marker", 2) # 0
yield Bit(self, "has_pts")
yield Bit(self, "has_dts")
if self['has_pts'].value:
yield Timestamp(self, "pts")
if self['has_dts'].value:
yield PaddingBits(self, "pad[]", 4)
yield Timestamp(self, "dts")
if self.current_size % 8 == 4:
yield PaddingBits(self, "pad[]", 4)
break
elif 0x80 <= next <= 0xbf:
# MPEG-2 extension
yield PacketElement(self, "pkt")
break
else:
# 0xc0 - 0xfe: unknown
break
length = self["../length"].value - self.current_size//8
if length:
tag=self['../tag'].value
group=self.root.streamgroups[tag]
parname=self.parent._name
if parname.startswith('audio'):
frag = CustomFragment(self, "data", length*8, MpegAudioFile, group=group)
elif parname.startswith('video'):
frag = CustomFragment(self, "data", length*8, VideoStream, group=group)
else:
frag = CustomFragment(self, "data", length*8, None, group=group)
self.root.streamgroups[tag]=frag.group
yield frag
class Chunk(FieldSet):
ISO_END_CODE = 0xB9
tag_info = {
0xB9: ("end", None, "End"),
0xBA: ("pack_start[]", PackHeader, "Pack start"),
0xBB: ("system_start[]", SystemHeader, "System start"),
# streams
0xBD: ("private[]", Stream, "Private elementary"),
0xBE: ("padding[]", Stream, "Padding"),
# 0xC0 to 0xFE handled specially
0xFF: ("directory[]", Stream, "Program Stream Directory"),
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
if not hasattr(self.root,'streamgroups'):
self.root.streamgroups={}
for tag in range(0xBC, 0x100):
self.root.streamgroups[tag]=None
tag = self["tag"].value
if tag in self.tag_info:
self._name, self.parser, self._description = self.tag_info[tag]
elif 0xBC <= tag <= 0xFF:
if 0xC0 <= tag < 0xE0:
# audio
streamid = tag-0xC0
self._name, self.parser, self._description = ("audio[%i][]"%streamid, Stream, "Audio Stream %i Packet"%streamid)
elif 0xE0 <= tag < 0xF0:
# video
streamid = tag-0xE0
self._name, self.parser, self._description = ("video[%i][]"%streamid, Stream, "Video Stream %i Packet"%streamid)
else:
self._name, self.parser, self._description = ("stream[]", Stream, "Data Stream Packet")
else:
self.parser = defaultParser
if not self.parser:
self.parser = defaultParser
elif self.parser != PackHeader and "length" in self:
self._size = (6 + self["length"].value) * 8
def createFields(self):
yield Bytes(self, "sync", 3)
yield textHandler(UInt8(self, "tag"), hexadecimal)
if self.parser:
if self.parser != PackHeader:
yield UInt16(self, "length")
if not self["length"].value:
return
yield self.parser(self, "content")
def createDescription(self):
return "Chunk: tag %s" % self["tag"].display
class MPEGVideoFile(Parser):
PARSER_TAGS = {
"id": "mpeg_video",
"category": "video",
"file_ext": ("mpeg", "mpg", "mpe", "vob"),
"mime": (u"video/mpeg", u"video/mp2p"),
"min_size": 12*8,
#TODO: "magic": xxx,
"description": "MPEG video, version 1 or 2"
}
endian = BIG_ENDIAN
version = None
def createFields(self):
while self.current_size < self.size:
pos=self.stream.searchBytes('\0\0\1',self.current_size,self.current_size+1024*1024*8) # seek forward by at most 1MB
if pos is not None:
padsize = pos-self.current_size
if padsize:
yield PaddingBytes(self, "pad[]", padsize//8)
chunk=Chunk(self, "chunk[]")
try:
# force chunk to be processed, so that CustomFragments are complete
chunk['content/data']
except: pass
yield chunk
def validate(self):
try:
pack = self[0]
except FieldError:
return "Unable to create first chunk"
if pack.name != "pack_start[0]":
return "Invalid first chunk"
if pack["sync"].value != "\0\0\1":
return "Invalid synchronisation"
return pack["content"].validate()
def getVersion(self):
if not self.version:
if self["pack_start[0]/content/sync[0]"].size == 2:
self.version = 2
else:
self.version = 1
return self.version
def createDescription(self):
if self.getVersion() == 2:
return "MPEG-2 video"
else:
return "MPEG-1 video"