Python 2.7 pyLZMA 工作,Python 3.4 LZMA 模块不工作

标签 python compression 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 可以工作,而 Python 3.4 的 lzma 却不行?

最佳答案

虽然我不知道为什么这两个模块的工作方式不同,但我有一个解决方案。

我无法让非流 LZMA lzma.decompress 工作,因为我对 LZMA/XZ/SWF 规范没有足够的了解,但是我得到了 lzma。 LZMADecompressor 工作。为了完整起见,我相信 SWF LZMA 使用这种 header 格式(未 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 中工作,但是需要更改一些内容,例如 Marcus提到解包(通过使用 buffer[0:1] 而不是 buffer[0] 很容易解决)。也不是真的有必要读取整个文件,一个小块,比如 256 字节应该可以读取整个 SWF header 。 frames 字段也有点古怪,但我相信您所要做的只是一些位移,即:

header['frames'] = read_ui16(buffer[0:2]) >> 8

SWF file format spec

LZMA file format spec

关于Python 2.7 pyLZMA 工作,Python 3.4 LZMA 模块不工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32787778/

相关文章:

Android - 视频压缩

compression - 如何使用 brotli 命令行(版本 0.5.2)压缩文件夹?

c# - 为什么 LZMA SDK (7-zip) 这么慢

c++ - 如何在C++中使用LZMA SDK?

Python 包装亚马逊产品 api

python - 正则表达式:允许逗号分隔的字符串,包括字符和非字符

python - 如何判断文件是否经过 gzip 压缩?

python - 不确定如何从二进制文件中解压缩/读取结果

python - 从另一个数据帧映射后如何在列中查找值的总和?

Python:将变量发送到另一个脚本