【问题标题】:How to modify a compressed itxt record of an existing file in python?如何在python中修改现有文件的压缩itxt记录?
【发布时间】:2016-09-01 07:01:59
【问题描述】:

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

保存后,itxt 应再次压缩。

【问题讨论】:

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


    【解决方案1】:

    这并不像你目睹的那么简单。如果是这样,您可能会发现没有直接的解决方案。

    让我们从基础开始。

    PyPNG 可以读取所有块吗?

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

    PNG:逐块逐块

    辅助块

    .. iTXt
    阅读时忽略。未生成。

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

    但是在那页下面,救命!

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

    所以我所要做的就是编写这个“用户代码”,PyPNG 可以完成剩下的工作。 (哎呀)

    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 提供了一个 iterator,因此确实检查 PNG 是否包含 iTXt 块很容易:

    chunks()
    返回一个迭代器,它将每个块生成为 (chunktype, content) 对。

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

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

    >>> 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]
    

    这个快速而简单的函数返回一个由 [before, after] 对组成的数组,并丢弃零本身。

    为了尽可能透明地处理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 编码。

    【讨论】:

    • 我的帖子没有谈到 pypng 而是 python-pillow,它对 ᴘɴɢ 元数据有一些支持,但似乎太有用了……
    • @user2284570:我没有注意到,它只是在标签中。而且你非常缺乏细节......由于Python和PyPNG对我来说是新的,所以我小心地记录了我是如何继续写这篇文章的;希望有帮助!
    • 正确,不要将 iTXt 放在 IHDR 之前或 IEND 之后。如果有多个 IDAT 块,IDAT 块中也不允许这样做。否则,任何地方都可以。
    • 谢谢,@GlennRanders-Pehrson,我错过了。奇怪的是,我在寻找检查调整后的 PNG 的方法时遇到了一些严重的问题! pngcheck 可以列出tEXt 块的内容,但不能列出iTXt(它只显示关键字、压缩和语言标签);原本完美无瑕的 ImageMagic 的 identify 根本没有看到任何东西。这肯定似乎是一个很大程度上未使用的标签。
    • @Rad Lexus:我使用“pngcrush -n -v file.png”列出块。这将展开并打印(除其他外)iTXt 块内容。
    猜你喜欢
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    • 2018-01-03
    • 2015-11-26
    • 2022-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多