解压并重新打包.docx和.pptx文件(以及图片压缩)

This article is categorized as "Garbage" . It should NEVER be appeared in your search engine's results.

用途


作用1:压缩本地docx/pptx文件体积,尤其是那种粘贴了一大堆原图的PPT

作用2:Google docs限制单个文件大小50M,如果一个google docs文件因为大尺寸图片过多的原因超过了50M,可以通过 下载docx-优化docx-重新上传-重新转换为google docs 的方法给文件续命。

其实,online word(也就是office 365 sharepoint)也限制了单个文件大小为100M,如果真的有这个需求(即:docx大小超过了100M,但无法使用/不想使用word桌面版去编辑这个docx文件),也可以通过 下载docx-优化docx-重新上传 的方法给文件续命。


docx/pptx的解压和重新压缩

解压比较随意,随便找个什么keka这样的解压软件就能完成任务。下面主要讨论压缩。

关键步骤:🔗 [macos - re-zipped docx files don't open in Word - Super User] https://superuser.com/questions/411911/re-zipped-docx-files-dont-open-in-word

意思是,要从那一堆文件的根目录(而不是包含了这些文件的上层文件夹)开始打包

比如:

Archive.zip.docx就是重新打包的docx文件

注意,压缩并制作完成的Archive.zip.docx首次被word打开的时候可能仍然会报错/警告(比如因为zip文件夹里包含了.DS_Store文件),这种错误word是能修复的(修复文件并另存为另一个.docx即可),但如果打包的基本结构有问题(比如没有从根目录开始打包),word是无法修复的。

虽然上面的这个Word found unreadable content警告可以被word修复,但强迫症显然忍不了这一点,一开始我还以为是keka压缩参数的问题,所以改用了/usr/bin/zip命令:

  1. 用macOS自带的zip命令/usr/bin/zip
  2. 去掉.DS_Store和__MACOSX文件夹

综合起来就是(在.docx根目录下):

$ zip -r opt.docx * -x "*.DS_Store" "__MACOSX*"

或者

$ zip -r opt.pptx * -x "*.DS_Store" "__MACOSX*"

这样可以规避.DS_Store带来的问题,但后来我发现Word found unreadable content警告仍然有几率出现,只是word都能修复它。最后我发现是word设置的问题,比如:由于种种原因,你的docx文件设置了“嵌入字体”,但实际保存的时候word并没有往docx压缩包里面添加正确的字体文件,而是添加了0字节大小的 font*.odttf . (这种字体策略不一致的问题可能来自不同版本/不同平台的word,或者来自Google Docs/Libreoffice这些非word程序).

实际上word压缩docx的压缩率仍然比macOS自带的/usr/bin/zip(默认参数)要低,比如你用/usr/bin/zip重新打包以后得到的docx是10M,用word打开然后修改一点点(加个空格什么的)然后再保存它,这个docx可能就变成了12M. 又或者你遇到了上面提到的“0字节大小的font*.odttf“问题(word再次保存的时候又会把>0M的字体文件给它加上),这些繁琐的word设置都可能让你新保存的.docx文件变得更大。但这些都属于无关紧要的问题,因为关键的问题都已经解决了(图片文件大幅度优化,且重新制作的docx文件没有出现严重错误)。


图片优化的思路(2024草稿)

接下来是jpg/png的优化问题。jpg还好说,全部丢给mozjpeg就可以。png稍微麻烦点,目前是2个思路:

思路1:png就用其他的png优化库(这样可以防止文档中出现的透明png样式被搞乱)

思路2:png也喂给mozjpeg(要确定这个文档里没有透明png的需求),但要同时改变各种xml文档中的字符串(从.png变成.jpg)

暂时实现的是思路2.

遗留问题:如果一个docx被反复优化并反复添加新图片,那么留在docx里面的“老图”质量会越来越低。思路:可能要用exif进行标记。

遗留问题:pdf缓存问题:用脚本优化并重新打包docx以后,用word再次打开(优化后的文件)并另存为pdf,仍有可能导出一个巨大的pdf,这可能来自于word.app的缓存问题(内存缓存或者硬盘缓存)。解决方法:待续(重命名?)

遗留问题:png优化(目前没有实现这个功能,而是把所有png变成了jpeg,主要原因还是没有透明png这个刚需,不想去花时间挑选以及测试那些png优化库)


2024-11-22更新:

完全一样的程序还可以作用于.pptx文件!很多时候pptx更需要优化,因为图多

但有些pptx会用到透明png的特性(比如logo展示),所以要注意png的处理是否恰当


图片优化(成品python代码)

2025-06-18,我终于想起了这个项目还没收尾。事实上过去一年我一直在用上面的思路制作的半成品(只能处理jpeg的python程序)来压缩docx/pptx,效果很好。对于极少数透明png,我的做法是在程序压缩完毕以后直接手动把这些透明png添加回去。

现在终于加入了pngquant把程序收尾了。总体思路如下:

  1. pngquant可选。如果文档里没有透明png的需求:所有jpeg/png都会用mozjpeg变成压缩过的jpeg;如果文档里有透明png的需求:jpeg使用mozjpeg压缩成jpeg,png使用pngquant压缩成png(保留png的透明特性)。
  2. mozjpeg可选压缩比,一般我习惯用85;pngquant暂时没有引入额外参数
  3. 使用exiftool对优化过的图片进行exif标记:优化后的图片会带有 {"Keywords": ["docx_opt"]} ,同时程序会自动跳过带有这种标记的图片(防止一张图片被反复压缩)

代码如下(部分代码是gpt给的,很多代码写的很烂比如检查 [Content_Types].xml 的代码,但是懒得改了):

注意:需要exiftool, mozjpeg, pngquant的运行程序以及python-exiftool.

import os
import sys
import codecs
import argparse

import exiftool


def set_exif_tag(file_path):
    with exiftool.ExifToolHelper() as et:
        et.set_tags(
            [file_path],
            tags={"Keywords": ["docx_opt"]}
        )
    os.remove(file_path + '_original')


def check_exif_tag(file_path):
    with exiftool.ExifToolHelper() as et:
        for d in et.get_metadata(file_path):
            for k, v in d.items():
                if k == 'IPTC:Keywords' and 'docx_opt' in v:
                    return True
    return False


def try_utf8(filename):
    # https://stackoverflow.com/questions/3269293/how-to-write-a-check-in-python-to-see-if-file-is-valid-utf-8
    try:
        file_str = codecs.open(filename, encoding='utf-8', errors='strict').read()
        return file_str
    except UnicodeDecodeError:
        return None


def list_files(path):
    files_path = []
    for root, dirs, files in os.walk(path):
        for name in files:
            files_path.append(os.path.join(root, name))
    return files_path


def mozcjpeg_in_place(img_path, quality):
    os.system('/PATH/cjpeg-static -quality %d %s > %s' % (quality, img_path, img_path + '.tmp'))
    os.rename(img_path + '.tmp', img_path)
    print('optimize: %s' % img_path)


def mozcjpeg_png(img_path, quality):
    os.system('/PATH/cjpeg-static -quality %d %s > %s' % (quality, img_path, img_path.replace('.png', '.jpg')))
    os.remove(img_path)
    print('%s -> %s' % (img_path, img_path.replace('.png', '.jpg')))


def run_pngquant(img_path):
    # currently just use default options in pngquant
    os.system(f'/PATH/pngquant {img_path} --output {img_path + '.opt'}')
    os.rename(img_path + '.opt', img_path)
    print(f'pngquant optimize {img_path}')


def replace_xml_file(files_path, original_str, new_str):
    # https://stackoverflow.com/questions/17140886/how-to-search-and-replace-text-in-a-file

    for file_path in files_path:
        # Read in the file
        if '/media/' not in file_path:
            with open(file_path, 'r') as file:
                file_str = try_utf8(file_path)
            if file_str:
                # Replace the target string
                if original_str in file_str:
                    file_str = file_str.replace(original_str, new_str)
                    # Write the file out again
                    with open(file_path, 'w') as file:
                        file.write(file_str)
                    print('change file: %s' % (file_path))


if __name__ == '__main__':
    # 检查参数
    parser = argparse.ArgumentParser()
    # Positional argument: quality (must be int between 1 and 99)
    parser.add_argument(
        'quality',
        type=int,
        help='Quality value between 1 and 99'
    )
    # Optional flag: --pngquant
    parser.add_argument('--pngquant', action='store_true', help='Enable pngquant compression')
    args = parser.parse_args()
    # Validate quality range
    if not (0 < args.quality < 100):
        print('error: input parameter invalid (must be between 1 and 99)')
        exit(1)
    print('quality: %d' % args.quality)
    quality = args.quality

    # Handle pngquant flag
    if args.pngquant:
        print('--------------------------All png files will be compressed by pngquant--------------------------')
        pngquant = True
    else:
        print('--------------------------All png files will be converted to jpeg--------------------------')
        pngquant = False

    # 检查当前运行环境
    files_path = list_files('.')
    check_dir = False
    for path in files_path:
        if './[Content_Types].xml' == path:
            check_dir = True
            break
    if not check_dir:
        print('error: this python script must run inside docx directory which includes [Content_Types].xml')
        exit(1)

    # 检查必要工具
    if not os.path.isfile('/PATH/cjpeg-static'):
        print('error: mozjpeg not found: /PATH/cjpeg-static')
        exit(1)
    if not os.path.isfile('/PATH/exiftool'):
        print('error: exiftool not found: /PATH/exiftool')
        exit(1)
    # 现在也检查pngquant
    if not os.path.isfile('/PATH/pngquant'):
        print('error: pngquant not found: /PATH/pngquant')
        exit(1)

    # 开始优化
    print()
    for file_path in files_path:
        if '/media/' in file_path:
            if file_path.lower().endswith('.jpg') or file_path.lower().endswith('jpeg'):
                if not check_exif_tag(file_path):
                    mozcjpeg_in_place(file_path, quality)
                    set_exif_tag(file_path)
                else:
                    print('Already optimized: skip %s' % file_path)
                print()
            if file_path.lower().endswith('.png'):
                head, filename = os.path.split(file_path)
                if not check_exif_tag(file_path):
                    if not pngquant:
                        mozcjpeg_png(file_path, quality)
                        replace_xml_file(files_path, filename, filename.replace('.png', '.jpg'))
                        set_exif_tag(file_path.replace('.png', '.jpg'))
                    else:
                        run_pngquant(file_path)
                        set_exif_tag(file_path)
                else:
                    print('Already optimized: skip %s' % file_path)
                print()

用法示例:

先解压一个docx/pptx,然后进入解压后的目录(目录下一定有一个 [Content_Types].xml 文件),运行:

# 优化docx,jpeg压缩比80,选择pngquant压缩png
# 其他用法同理

$ cp /PATH/TO/docx_opt.py ./docx_opt.py; python3 docx_opt.py 80 --pngquant; zip -r opt.docx * -x "*.DS_Store" "__MACOSX*" "docx_opt.py"

然后用word/powerpoint程序打开 opt.docx 并检查文件是否正常。如果报了小错就修复文件(如之前的内容所示)。把优化后的docx/pptx拷贝出来存着,然后把剩下的那堆垃圾删掉即可。


Leave a Comment Anonymous comment is allowed / 允许匿名评论