Python 2.7 pyLZMA работает, модуль Python 3.4 LZMA не работает
import sys
import os
import zlib
try:
import pylzma as lzma
except ImportError:
import lzma
from io import StringIO
import struct
#-----------------------------------------------------------------------------------------------------------------------
def read_ui8(c):
return struct.unpack('<B', c)[0]
def read_ui16(c):
return struct.unpack('<H', c)[0]
def read_ui32(c):
return struct.unpack('<I', c)[0]
def parse(input):
"""Parses the header information from an SWF file."""
if hasattr(input, 'read'):
input.seek(0)
else:
input = open(input, 'rb')
header = { }
# Read the 3-byte signature field
header['signature'] = signature = b''.join(struct.unpack('<3c', input.read(3))).decode()
# Version
header['version'] = read_ui8(input.read(1))
# File size (stored as a 32-bit integer)
header['size'] = read_ui32(input.read(4))
# Payload
if header['signature'] == 'FWS':
print("The opened file doesn't appear to be compressed")
buffer = input.read(header['size'])
elif header['signature'] == 'CWS':
print("The opened file appears to be compressed with Zlib")
buffer = zlib.decompress(input.read(header['size']))
elif header['signature'] == 'ZWS':
print("The opened file appears to be compressed with Lzma")
# ZWS(LZMA)
# | 4 bytes | 4 bytes | 4 bytes | 5 bytes | n bytes | 6 bytes |
# | 'ZWS'+version | scriptLen | compressedLen | LZMA props | LZMA data | LZMA end marker |
size = read_ui32(input.read(4))
buffer = lzma.decompress(input.read())
# Containing rectangle (struct RECT)
# The number of bits used to store the each of the RECT values are
# stored in first five bits of the first byte.
nbits = read_ui8(buffer[0]) >> 3
current_byte, buffer = read_ui8(buffer[0]), buffer[1:]
bit_cursor = 5
for item in 'xmin', 'xmax', 'ymin', 'ymax':
value = 0
for value_bit in range(nbits-1, -1, -1): # == reversed(range(nbits))
if (current_byte << bit_cursor) & 0x80:
value |= 1 << value_bit
# Advance the bit cursor to the next bit
bit_cursor += 1
if bit_cursor > 7:
# We've exhausted the current byte, consume the next one
# from the buffer.
current_byte, buffer = read_ui8(buffer[0]), buffer[1:]
bit_cursor = 0
# Convert value from TWIPS to a pixel value
header[item] = value / 20
header['width'] = header['xmax'] - header['xmin']
header['height'] = header['ymax'] - header['ymin']
header['frames'] = read_ui16(buffer[0:2])
header['fps'] = read_ui16(buffer[2:4])
input.close()
return header
header = parse(sys.argv[1]);
print('SWF header')
print('----------')
print('Version: %s' % header['version'])
print('Signature: %s' % header['signature'])
print('Dimensions: %s x %s' % (header['width'], header['height']))
print('Bounding box: (%s, %s, %s, %s)' % (header['xmin'], header['xmax'], header['ymin'], header['ymax']))
print('Frames: %s' % header['frames'])
print('FPS: %s' % header['fps'])
У меня сложилось впечатление, что встроенный модуль Python 3.4 LZMA работает так же, как и модуль Python 2.7 pyLZMA. Код, который я предоставил, работает как на 2.7, так и на 3.4, но когда он работает на 3.4 (который не имеет pylzma, поэтому он прибегает к встроенному lzma), я получаю следующую ошибку:
_lzma.LZMAError: Input format not supported by decoder
Почему pylzma работает, а lzma в Python 3.4 - нет?
1 ответ
Хотя у меня нет ответа на вопрос, почему два модуля работают по-разному, у меня есть решение.
Мне не удалось получить LZMA без потока lzma.decompress
работать, так как у меня недостаточно знаний о спецификациях LZMA/XZ/SWF, однако я получил lzma.LZMADecompressor
работать. Для полноты я полагаю, что SWF LZMA использует этот формат заголовка (не подтвержденный на 100%):
Bytes Length Type Endianness Description
0- 2 3 UI8 - SWF Signature: ZWS
3 1 UI8 - SWF Version
4- 7 4 UI32 LE SWF FileLength aka File Size
8-11 4 UI32 LE SWF? Compressed Size (File Size - 17)
12 1 - - LZMA Decoder Properties
13-16 4 UI32 LE LZMA Dictionary Size
17- - - - LZMA Compressed Data (including rest of SWF header)
Однако спецификация формата файла LZMA говорит, что это должно быть:
Bytes Length Type Endianness Description
0 1 - - LZMA Decoder Properties
1- 4 4 UI32 LE LZMA Dictionary Size
5-12 8 UI64 LE LZMA Uncompressed Size
13- - - - LZMA Compressed Data
Я никогда не мог по-настоящему разобраться, что Uncompressed Size
должно быть (если вообще возможно определить для этого формата). pylzma
Кажется, на это наплевать, а на Python 3.3 lzma
делает. Тем не менее, кажется, что явно неизвестный размер работает и может быть указан как UI64
со значением 2^64
например, 8*b'\xff'
или же 8*'\xff'
Таким образом, немного перетаскивая заголовки и вместо использования:
buffer = lzma.decompress(input.read())
Пытаться:
d = lzma.LZMADecompressor(format=lzma.FORMAT_ALONE)
buffer = d.decompress(input.read(5) + 8*b'\xff' + input.read())
Примечание: у меня не было доступного локального интерпретатора python3, поэтому я протестировал его только онлайн с немного измененной процедурой чтения, поэтому он может не работать из коробки.
Изменить: Подтверждено для работы в Python3, но некоторые вещи должны быть изменены, как Маркус упоминал о распаковке (легко решается с помощью buffer[0:1]
вместо buffer[0]
). На самом деле нет необходимости читать весь файл, небольшой фрагмент, скажем, 256 байтов должно быть хорошо для чтения всего заголовка SWF. frames
Поле тоже немного странное, хотя я считаю, что все, что вам нужно сделать, это немного сдвинуть бит, то есть:
header['frames'] = read_ui16(buffer[0:2]) >> 8