pycrate/pycrate_media/JPEG.py

327 lines
9.8 KiB
Python

# -*- coding: UTF-8 -*-
#/**
# * Software Name : pycrate
# * Version : 0.4
# *
# * Copyright 2016. Benoit Michau. ANSSI.
# *
# * This library is free software; you can redistribute it and/or
# * modify it under the terms of the GNU Lesser General Public
# * License as published by the Free Software Foundation; either
# * version 2.1 of the License, or (at your option) any later version.
# *
# * This library is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# * Lesser General Public License for more details.
# *
# * You should have received a copy of the GNU Lesser General Public
# * License along with this library; if not, write to the Free Software
# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# * MA 02110-1301 USA
# *
# *--------------------------------------------------------
# * File Name : pycrate_media/JPEG.py
# * Created : 2016-03-09
# * Authors : Benoit Michau
# *--------------------------------------------------------
#*/
from pycrate_core.elt import *
from pycrate_core.base import *
from pycrate_core.repr import *
Buf.REPR_MAXLEN = 256
# Detailed JPEG metadata segments
# Start Of Frame
class SOFComponent(Envelope):
_GEN = (
Uint8('C', desc='Component identifier'),
Uint('H', desc='Horizontal sampling factor', bl=4),
Uint('V', desc='Vertical sampling factor', bl=4),
Uint8('Tq', desc='Quantization table destination selector')
)
class SOF(Envelope):
_GEN = (
Uint8('P', desc='Sample Precision'),
Uint16('Y', desc='Number of lines'),
Uint16('X', desc='Number of sample per line'),
Uint8('Nf', desc='Number of image components in frame'),
Array('SOFComponents', GEN=SOFComponent())
)
def __init__(self, *args, **kwargs):
Envelope.__init__(self, *args, **kwargs)
self[3].set_valauto(self[4].get_num)
self[4].set_numauto(self[3].get_val)
# Start Of Scan
class SOSComponent(Envelope):
_GEN = (
Uint8('Cs', desc='Scan component selector'),
Uint('Td', desc='DC entropy coding table destination selector', bl=4),
Uint('Ta', desc='AC entropy coding table destination selector', bl=4)
)
class SOS(Envelope):
_GEN = (
Uint8('Nf', desc='Number of image components in frame'),
Array('SOSComponents', GEN=SOSComponent()),
Uint8('Ss', desc='Start of spectral or predictor selection'),
Uint8('Se', desc='End of spectral selection'),
Uint('Ah', desc='Successive approximation bit position high', bl=4),
Uint('Al', desc='Successive approximation bit position low', bl=4),
)
def __init__(self, *args, **kwargs):
Envelope.__init__(self, *args, **kwargs)
self[0].set_valauto(self[1].get_num)
self[1].set_numauto(self[0].get_val)
def append_data(self, data=b''):
self.append( Buf('Data', val=data, rep=REPR_HD) )
# Quantization Table
class DQT(Envelope):
_GEN = (
Uint('Pq', ReprName='Quantization table element precision', bl=4),
Uint('Tq', ReprName='Quantization table destination identifier', bl=4),
Array('QT', num=64,
GEN=Uint8('QTe', desc='Quantization table element', val=1))
)
def _from_char(self, char):
self[0:2]._from_char(char)
if self[0].get_val():
self[2].set_bl(16)
self[2]._from_char(char)
# Huffman Table
class DHT(Envelope):
_GEN = (
Uint('Tc', desc='Huffman table class', bl=4),
Uint('Th', desc='Huffman table destination identifier', bl=4) ) + \
tuple([Uint8('HCL{0}'.format(i),
desc='Number of huffman codes of length {0}'.format(i)) \
for i in range(1, 17)]) + \
(Buf('HCV'),
)
def _from_char(self, char):
Envelope._from_char(self, char)
# generate the detailed table of values for all the huffman codes
hcv_gen = []
for hcl in self[2:18]:
for j in range(1, 1+hcl.get_val()):
hcv_gen.append( \
Uint8('{0}_HCV{1}'.format(hcl._name, j),
desc='Value {0} for {1}'.format(j, hcl._name)) )
hcv_env = Envelope('HCV', GEN=tuple(hcv_gen))
# parse it, and substitute if to the buffer one
hcv = self[18].get_val()
hcv_env.from_bytes(hcv)
if hcv_env.get_len() == len(hcv):
self.replace(self[18], hcv_env)
# Arithmetic Table
class DACComponent(Envelope):
_GEN = (
Uint('Tc', desc='Table class', bl=4),
Uint('Tb',
desc='Arithmetic coding conditioning table destination identifier',
bl=4),
Uint8('CS', desc='Conditioning table value')
)
class DAC(Array):
_GEN = DACComponent()
# generic JPEG metadata segment
# Segment containing JPEG meta-data
# Start Of Scan segment contains the image compressed data
Segment_dict = {
# reserved markers
0x01 : 'Temporary private use in arithmetic coding',
# non-differential Huffman coding
0xC0 : 'Start Of Frame (Baseline DCT)',
0xC1 : 'Start Of Frame (Extended Sequential DCT)',
0xC2 : 'Start Of Frame (Progressive DCT)',
0xC3 : 'Start Of Frame (Lossless Sequential)',
# differential Huffman coding
0xC5 : 'Start Of Frame (Differential Sequential DCT)',
0xC6 : 'Start Of Frame (Differential Progressive DCT)',
0xC7 : 'Start Of Frame (Differential Lossless Sequential)',
# non-differential arithmetic coding
0xC8 : 'Start Of Frame (Reserved for JPEG extensions)',
0xC9 : 'Start Of Frame (Extended Sequential DCT)',
0xCA : 'Start Of Frame (Progressive DCT)',
0xCB : 'Start Of Frame (Lossless Sequential)',
# differential arithmetic coding
0xCD : 'Start Of Frame (Differential Sequential DCT)',
0xCE : 'Start Of Frame (Differential Progressive DCT)',
0xCF : 'Start Of Frame (Differential Lossless Sequential)',
# huffman table spec
0xC4 : 'Define Huffman Table(s)',
# arithmetic coding conditioning spec
0xCC : 'Define Arithmetic Coding Conditioning(s)',
# restart interval termination
0xD0 : 'RST0',
0xD1 : 'RST1',
0xD2 : 'RST2',
0xD3 : 'RST3',
0xD4 : 'RST4',
0xD5 : 'RST5',
0xD6 : 'RST6',
0xD7 : 'RST7',
# other markers
0xD8 : 'Start Of Image',
0xD9 : 'End Of Image',
0xDA : 'Start Of Scan',
0xDB : 'Define Quantization Table(s)',
0xDD : 'Define Restart Interval',
0xDE : 'Define Hierarchichal Progression',
0xDF : 'Expand Reference Component(s)',
# reserved for application segments
0xE0 : 'APP0',
0xE1 : 'APP1',
0xE2 : 'APP2',
0xE3 : 'APP3',
0xE4 : 'APP4',
0xE5 : 'APP5',
0xE6 : 'APP6',
0xE7 : 'APP7',
0xE8 : 'APP8',
0xE9 : 'APP9',
0xEA : 'APPA',
0xEB : 'APPB',
0xEC : 'APPC',
0xED : 'APPD',
0xEE : 'APPE',
0xEF : 'APPF',
# reserved for JPEG extensions
0xF0 : 'JPG0',
0xF1 : 'JPG1',
0xF2 : 'JPG2',
0xF3 : 'JPG3',
0xF4 : 'JPG4',
0xF5 : 'JPG5',
0xF6 : 'JPG6',
0xF7 : 'JPG7',
0xF8 : 'JPG8',
0xF9 : 'JPG9',
0xFA : 'JPGA',
0xFB : 'JPGB',
0xFC : 'JPGC',
0xFD : 'JPGD',
# comment
0xFE : 'Comment',
}
class Segment(Envelope):
_GEN = (
Uint8('mark', val=0xFF, rep=REPR_HEX),
Uint8('type', val=0xFE, dic=Segment_dict),
Uint16('len'),
Buf('pay'),
)
# these are segment types without length / payload
_no_pay = (0xD8, 0xD9)
# these are the segments for which there is a detailed structure
_det_struct = {
# Start Of Frame
0xC0 : SOF,
0xC1 : SOF,
0xC2 : SOF,
0xC3 : SOF,
0xC5 : SOF,
0xC6 : SOF,
0xC7 : SOF,
0xC8 : SOF,
0xC9 : SOF,
0xCA : SOF,
0xCB : SOF,
0xCD : SOF,
0xCE : SOF,
0xCF : SOF,
# Start Of Scan
0xDA : SOS,
# Tables
0xDB : DQT,
0xC4 : DHT,
0xCC : DAC
}
def __init__(self, *args, **kwargs):
Envelope.__init__(self, *args, **kwargs)
self[2].set_transauto(self._has_no_pay)
self[2].set_valauto(self._set_len)
self[3].set_transauto(self._has_no_pay)
self[3].set_blauto(self._get_len)
def _has_no_pay(self):
if self[1].get_val() in self._no_pay:
return True
else:
return False
def _set_len(self):
return 2 + self[3].get_len()
def _get_len(self):
return 8 * (self[2].get_val()-2)
def _from_char(self, char):
Envelope._from_char(self, char)
t = self[1].get_val()
p = self[3].get_val()
if t in self._det_struct:
# detailed structure available
ds = self._det_struct[t]()
ds.from_bytes(p)
if ds.get_len() == len(p):
self.replace(self[3], ds)
if t == 0xDA:
# start of scan: compressed data is following
buf = char.to_bytes()
mark = self._scan_marker(buf)
if mark > 0:
self.append( Buf('Data', val=buf[:mark], rep=REPR_HD) )
buf = buf[mark:]
char.forward(8*mark)
def _scan_marker(self, buf):
off = 0
while True:
mark = buf[off:].find(b'\xFF')
if mark >= 0 and buf[off+mark+1:off+mark+2] != b'\x00':
# found a real marker
return off+mark
elif mark == -1:
# no marker found
return -1
else:
# escaped marker found, continue scanning
off += 2+mark
# JPEG structure
class JPEG(Sequence):
_GEN = Segment()