Python Gif Processing

天气站点需要往微博推送气象信息,原始的基本反射率数据都是单帧的,为了好一点的效果计划自己合成GIF,但找了一下发现Python下没有现成好的GIF处理库,PIL能支持读存操作但不支持合并,没办法只好找些资料自己写。下面列些相关资料的链接和一个GIF合并处理的例子,基本上就是按照标准对数据封装,其它格式对GIF的转换可以同理处理。

Standards and References

Application Extension Spec: NETSCAPE2.0

byte   1       : 33 (hex 0x21) GIF Extension code
byte   2       : 255 (hex 0xFF) Application Extension Label
byte   3       : 11 (hex 0x0B) Length of Application Block
                 (eleven bytes of data to follow)
bytes  4 to 11 : "NETSCAPE"
bytes 12 to 14 : "2.0"
byte  15       : 3 (hex 0x03) Length of Data Sub-Block
                 (three bytes of data to follow)
byte  16       : 1 (hex 0x01)
bytes 17 to 18 : 0 to 65535, an unsigned integer in
                 lo-hi byte format. This indicate the
                 number of iterations the loop should
                 be executed.
byte  19       : 0 (hex 0x00) a Data Sub-Block Terminator.
from PIL import Image


def o16(i):
    return chr(i&255) + chr(i>>8&255)


def merge(fp, images, durations, loops=0):
    _im = images[0]
    _durations = [durations for im in images] if isinstance(durations, int) else durations

    _bits = 8
    _size = _im.size
    try:
        assert len(_durations) == len(images)
        for im in images:
            im.load()
            assert _size == im.size
    except AssertionError:
        raise

    # Header
    _h = [
        'GIF89a',
        o16(_size[0]),              # width
        o16(_size[1]),              # height
        chr(128 + (_bits - 1)),     # flags: palette + bits
        chr(0),                     # background
        chr(0),                     # reserved/aspect
    ]
    fp.write(''.join(_h))

    # Palette
    _mode = 'P'     # palette/grayscale (P/L)
    _colors = 256
    _palette = _im.im.getpalette('RGB')[:_colors * 3]
    fp.write(_palette)

    # Ext: application extension
    _loop = loops
    _ext = [
        '\x21\xFF\x0B',
        'NETSCAPE2.0',
        '\x03\x01',
        o16(_loop),
        '\x00',
    ]
    fp.write(''.join(_ext))

    # Images
    for idx, im in enumerate(images):
        # Ext: graphics control extension
        _cext = [
            '\x21\xF9\x04',
            '\x08',                 # flag: transparency: \x08 | \x09
            o16(_durations[idx]),   # druation
            '\x00',                 # transparency
            '\x00',
        ]
        fp.write(''.join(_cext))

        # Image
        _h = [
            '\x2C',                             # ',' sperator
            o16(0), o16(0),                     # offset
            o16(im.size[0]), o16(im.size[1]),   # size
            chr(0),                             # flag: no local palette
            chr(_bits),                         # color bits
        ]
        fp.write(''.join(_h))

        _encoder = Image._getencoder(im.mode, "gif", im.mode)
        _encoder.setimage(im.im, (0, 0) + im.size)
        _bufsize = 4096
        while True:
            l, s, d = _encoder.encode(_bufsize)
            fp.write(d)
            if s: break
        if s < 0:
            raise IOError("encoder error %d when writing image file" % s)
        fp.write('\x00')

    # End
    fp.write('\x3b')    # ';' trailer
    fp.flush()