如何在python中修改现有文件的压缩itxt记录?

时间:2016-05-06 09:06:43

标签: python windows python-3.x png python-imaging-library

我知道这看起来太简单但我找不到直接的解决方案。

保存后,应再次压缩itxt。

1 个答案:

答案 0 :(得分:2)

这并不像你用眼球那么简单。如果是,您可能已经发现没有直接的解决方案。

让我们从基础开始。

PyPNG可以读取所有块吗?

一个重要的问题,因为修改现有的PNG文件是一项艰巨的任务。阅读它的文档,它并不是很好:

  

PNG:Chunk by Chunk

     

辅助大块

     

..    iTXt
  阅读时忽略。没有生成。

https://pythonhosted.org/pypng/chunk.html

但在那页上降低了,救恩!

  

非标准块
  通常,不可能生成具有任何其他块类型的PNG图像。在读取PNG图像时,使用块接口png.Reader.chunks处理它将允许处理任何块(通过用户代码)。

所以我要做的就是写下'用户代码',PyPNG可以完成其余的工作。失步(OOF)

iTXt块怎么样?

让我们来看看您感兴趣的内容。

  

4.2.3.3。 iTXt国际文本数据

     

..文本数据采用Unicode字符集的UTF-8编码,而不是Latin-1。这个块包含:

Keyword:             1-79 bytes (character string)
Null separator:      1 byte
Compression flag:    1 byte
Compression method:  1 byte
Language tag:        0 or more bytes (character string)
Null separator:      1 byte
Translated keyword:  0 or more bytes
Null separator:      1 byte
Text:                0 or more bytes

http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt

对我来说很清楚。可选的压缩应该不是问题,因为

  

.. [t]他目前为压缩方法字节定义的值只有0,意思是zlib ..

我非常有信心Python可以为我做这件事。

然后回到PyPNG的块处理。

我们可以看到块数据吗?

PyPNG提供了一个迭代器,所以确实检查PNG是否包含iTXt块很容易:

  

chunks()
  返回一个迭代器,它将每个块作为(块类型,内容)对生成。

https://pythonhosted.org/pypng/png.html?#png.Reader.chunks

所以让我们以交互模式编写一些代码并检查。我从http://pmt.sourceforge.net/itxt/获得了一个样本图像,为方便起见,这里重复了一遍。 (如果此处未保存iTXt数据,请下载并使用原始数据。)

itxt sample image

>>> import png
>>> imageFile = png.Reader("itxt.png")
>>> print imageFile
<png.Reader instance at 0x10ae1cfc8>
>>> for c in imageFile.chunks():
...   print c[0],len(c[1])
... 
IHDR 13
gAMA 4
sBIT 4
pCAL 44
tIME 7
bKGD 6
pHYs 9
tEXt 9
iTXt 39
IDAT 4000
IDAT 831
zTXt 202
iTXt 111
IEND 0

成功!

写回来怎么样?好吧,PyPNG通常用于创建完整的图像,但幸运的是它还提供了一种从自定义块中显式创建图像的方法:

  

png.write_chunks(out,chunks)
  通过写出块来创建PNG文件。

所以我们可以遍历块,更改你想要的那个,然后回写修改后的PNG。

解包并打包iTXt数据

这本身就是一项任务。数据格式描述得很好,但不适合Python的原生unpackpack方法。所以我们必须自己创造一些东西。

文本字符串以ASCIIZ格式存储:以零字节结尾的字符串。我们需要一个小函数来分割第一个0

def cutASCIIZ(str):
   end = str.find(chr(0))
   if end >= 0:
      result = str[:end]
      return [str[:end],str[end+1:]]
   return ['',str]

这个快速和脏的函数返回一个[之前之后]对的数组,并丢弃零本身。

为了尽可能透明地处理iTXt数据,我把它变成了一个类:

class Chunk_iTXt:
  def __init__(self, chunk_data):
    tmp = cutASCIIZ(chunk_data)
    self.keyword = tmp[0]
    if len(tmp[1]):
      self.compressed = ord(tmp[1][0])
    else:
      self.compressed = 0
    if len(tmp[1]) > 1:
      self.compressionMethod = ord(tmp[1][1])
    else:
      self.compressionMethod = 0
    tmp = tmp[1][2:]
    tmp = cutASCIIZ(tmp)
    self.languageTag = tmp[0]
    tmp = tmp[1]
    tmp = cutASCIIZ(tmp)
    self.languageTagTrans = tmp[0]
    if self.compressed:
      if self.compressionMethod != 0:
        raise TypeError("Unknown compression method")
      self.text = zlib.decompress(tmp[1])
    else:
      self.text = tmp[1]

  def pack (self):
    result = self.keyword+chr(0)
    result += chr(self.compressed)
    result += chr(self.compressionMethod)
    result += self.languageTag+chr(0)
    result += self.languageTagTrans+chr(0)
    if self.compressed:
      if self.compressionMethod != 0:
        raise TypeError("Unknown compression method")
      result += zlib.compress(self.text)
    else:
      result += self.text
    return result

  def show (self):
    print 'iTXt chunk contents:'
    print '  keyword: "'+self.keyword+'"'
    print '  compressed: '+str(self.compressed)
    print '  compression method: '+str(self.compressionMethod)
    print '  language: "'+self.languageTag+'"'
    print '  tag translation: "'+self.languageTagTrans+'"'
    print '  text: "'+self.text+'"'

由于这会使用zlib,因此您需要在程序顶部使用import zlib

类构造函数接受“太短”的字符串,在这种情况下,它将使用默认值来定义未定义的内容。

show方法列出了用于调试目的的数据。

使用我的自定义类

通过所有这些,现在检查,修改和添加iTXt块最后 是直截了当的:

import png
import zlib

# insert helper and class here

sourceImage = png.Reader("itxt.png")
chunkList = []
for chunk in sourceImage.chunks():
  if chunk[0] == 'iTXt':
    itxt = Chunk_iTXt(chunk[1])
    itxt.show()
    # modify existing data
    if itxt.keyword == 'Author':
      itxt.text = 'Rad Lexus'
      itxt.compressed = 1
    chunk = [chunk[0], itxt.pack()]
  chunkList.append (chunk)

# append new data
newData = Chunk_iTXt('')
newData.keyword = 'Custom'
newData.languageTag = 'nl'
newData.languageTagTrans = 'Aangepast'
newData.text = 'Dat was leuk.'
chunkList.insert (-1, ['iTXt', newData.pack()])

with open("foo.png", "wb") as file:
  png.write_chunks(file, chunkList)

添加一个全新的块时,请注意append它,因为它会在所需的最后IEND块之后出现 ,这是一个错误。我没有尝试,但你也可能不应该在所需的第一个IHDR块之前插入它(或者由Glenn Randers-Pehrson评论)在连续的IDAT块之间插入它。

请注意,根据规范,iTXt中的所有文本都应采用UTF8编码。