pngs2apng.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # pngs2apng is python library/executable used for converting multiple pngs
  4. # into single apng file. It uses only standard python library.
  5. # Copyright (C) 2016 Pavel Holica, Jiří Kortus
  6. #
  7. # This library is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 2.1 of the License, or (at your option) any later version.
  11. #
  12. # This library is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with this library; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. import sys
  21. import struct
  22. import zlib
  23. PNG_HEADER = bytes(bytearray([137, 80, 78, 71, 13, 10, 26, 10]))
  24. PNG_HEADER_SIZE = 8
  25. PNG_IHDR_SIZE = 25 # including length and CRC
  26. PNG_IEND = bytes(bytearray([73, 69, 78, 68]))
  27. PNG_IEND_SIZE = 4
  28. def seek_IDAT(fo):
  29. while True:
  30. (size, ) = struct.unpack("!I", fo.read(4))
  31. frame_type = fo.read(4)
  32. if frame_type == b"IDAT":
  33. return size
  34. if frame_type == b"IEND":
  35. return 0
  36. fo.seek(size+4, 1)
  37. def pngs2apng(target, *inpaths):
  38. outfile = open(target, 'wb')
  39. # write PNG HEADER
  40. outfile.write(PNG_HEADER)
  41. # copy PNG IHDR from first file
  42. with open(inpaths[0], 'rb') as first:
  43. first.seek(PNG_HEADER_SIZE)
  44. outfile.write(first.read(PNG_IHDR_SIZE))
  45. # write APNG acTL structure
  46. outfile.write(struct.pack("!I", 8)) # size
  47. chunk = b"acTL"
  48. chunk += struct.pack("!I", len(inpaths))
  49. chunk += struct.pack("!I", 0)
  50. outfile.write(chunk)
  51. # CRC
  52. outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
  53. # write frames
  54. frame_num = 0
  55. for inpath in inpaths:
  56. # write APNG fcTL structure
  57. outfile.write(struct.pack("!I", 26))
  58. chunk = b"fcTL"
  59. chunk += struct.pack("!I", frame_num)
  60. # write frame data from PNG
  61. with open(inpath, 'rb') as infile:
  62. # width, height
  63. infile.seek(PNG_HEADER_SIZE)
  64. ihdr = infile.read(PNG_IHDR_SIZE)
  65. size_start = 8
  66. size_end = 16
  67. chunk += ihdr[size_start:size_end]
  68. # x_offset, y_offset
  69. chunk += struct.pack("!I", 0)
  70. chunk += struct.pack("!I", 0)
  71. # delay_num, delay_den
  72. chunk += struct.pack("!H", 1000)
  73. chunk += struct.pack("!H", 1000)
  74. # dispose_op, blend_op
  75. chunk += struct.pack("!b", 0)
  76. chunk += struct.pack("!b", 0)
  77. outfile.write(chunk)
  78. outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
  79. # sequence num + data
  80. data_len = 0
  81. if frame_num == 0:
  82. chunk = b"IDAT"
  83. while True:
  84. size = seek_IDAT(infile)
  85. if size == 0:
  86. outfile.write(struct.pack("!I", data_len))
  87. outfile.write(chunk)
  88. outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
  89. break
  90. chunk += infile.read(size)
  91. data_len += size
  92. infile.seek(4, 1)
  93. frame_num += 1
  94. else:
  95. chunk = b'fdAT'
  96. chunk += struct.pack("!I", frame_num+1)
  97. while True:
  98. length = seek_IDAT(infile)
  99. if length == 0:
  100. outfile.write(struct.pack("!I", data_len+4))
  101. outfile.write(chunk)
  102. outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
  103. break
  104. chunk += infile.read(length)
  105. data_len += length
  106. infile.seek(4, 1)
  107. frame_num += 2
  108. # write PNG IEND
  109. outfile.write(struct.pack("!I", 0))
  110. chunk = PNG_IEND
  111. outfile.write(chunk)
  112. outfile.write(struct.pack("!I", zlib.crc32(chunk) & 0xffffffff))
  113. outfile.close()
  114. if __name__ == "__main__":
  115. sys.exit(pngs2apng(*sys.argv[1:]))