-
Notifications
You must be signed in to change notification settings - Fork 1
/
pngs2apng.py
121 lines (115 loc) · 4.38 KB
/
pngs2apng.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pngs2apng is python library/executable used for converting multiple pngs
# into single apng file. It uses only standard python library.
# Copyright (C) 2016 Pavel Holica, Jiří Kortus
#
# 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
import sys
import struct
import zlib
PNG_HEADER = bytes(bytearray([137, 80, 78, 71, 13, 10, 26, 10]))
PNG_HEADER_SIZE = 8
PNG_IHDR_SIZE = 25 # including length and CRC
PNG_IEND = bytes(bytearray([73, 69, 78, 68]))
PNG_IEND_SIZE = 4
def seek_IDAT(fo):
while True:
(size, ) = struct.unpack("!I", fo.read(4))
frame_type = fo.read(4)
if frame_type == b"IDAT":
return size
if frame_type == b"IEND":
return 0
fo.seek(size+4, 1)
def pngs2apng(target, *inpaths):
outfile = open(target, 'wb')
# write PNG HEADER
outfile.write(PNG_HEADER)
# copy PNG IHDR from first file
with open(inpaths[0], 'rb') as first:
first.seek(PNG_HEADER_SIZE)
outfile.write(first.read(PNG_IHDR_SIZE))
# write APNG acTL structure
outfile.write(struct.pack("!I", 8)) # size
chunk = b"acTL"
chunk += struct.pack("!I", len(inpaths))
chunk += struct.pack("!I", 0)
outfile.write(chunk)
# CRC
outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
# write frames
frame_num = 0
for inpath in inpaths:
# write APNG fcTL structure
outfile.write(struct.pack("!I", 26))
chunk = b"fcTL"
chunk += struct.pack("!I", frame_num)
# write frame data from PNG
with open(inpath, 'rb') as infile:
# width, height
infile.seek(PNG_HEADER_SIZE)
ihdr = infile.read(PNG_IHDR_SIZE)
size_start = 8
size_end = 16
chunk += ihdr[size_start:size_end]
# x_offset, y_offset
chunk += struct.pack("!I", 0)
chunk += struct.pack("!I", 0)
# delay_num, delay_den
chunk += struct.pack("!H", 1000)
chunk += struct.pack("!H", 1000)
# dispose_op, blend_op
chunk += struct.pack("!b", 0)
chunk += struct.pack("!b", 0)
outfile.write(chunk)
outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
# sequence num + data
data_len = 0
if frame_num == 0:
chunk = b"IDAT"
while True:
size = seek_IDAT(infile)
if size == 0:
outfile.write(struct.pack("!I", data_len))
outfile.write(chunk)
outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
break
chunk += infile.read(size)
data_len += size
infile.seek(4, 1)
frame_num += 1
else:
chunk = b'fdAT'
chunk += struct.pack("!I", frame_num+1)
while True:
length = seek_IDAT(infile)
if length == 0:
outfile.write(struct.pack("!I", data_len+4))
outfile.write(chunk)
outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
break
chunk += infile.read(length)
data_len += length
infile.seek(4, 1)
frame_num += 2
# write PNG IEND
outfile.write(struct.pack("!I", 0))
chunk = PNG_IEND
outfile.write(chunk)
outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
outfile.close()
if __name__ == "__main__":
sys.exit(pngs2apng(*sys.argv[1:]))