)
文章目录gc.py 功能介绍PDF 对象流还原工具用于 pdfium 测试核心定位工作原理对象流压缩 vs 交叉引用表格式转换流程使用方法1. 配置参数2. 运行脚本3. 验证还原效果测试工作流完整测试流程测试指标为什么选择 podofogcpodofogc 的优势与其他工具对比注意事项1. 测试材料一致性2. 文件命名冲突3. 超时处理总结完整代码podofo_gc.pygc.py 功能介绍PDF 对象流还原工具用于 pdfium 测试核心定位gc.py的核心目的是将经过对象流压缩的 PDF 文件还原为标准交叉引用表XRef Table格式为测试pdfium的对象流压缩功能提供纯净的测试材料。工作原理对象流压缩 vs 交叉引用表格式格式特点用途对象流压缩对象数据存储在流中通过流索引访问减小文件体积适合分发存储交叉引用表每个对象有独立的偏移量记录易于解析和编辑适合测试场景转换流程┌──────────────────────────────────────────────────────────────────┐ │ Step 1: 输入 - 已压缩的 PDF 文件 │ │ 特征: 使用对象流(Object Streams)存储对象 │ │ 包含 /ObjStm 类型的流对象 │ └───────────────────────────┬──────────────────────────────────────┘ │ ▼ podofogc -- 还原对象流 ┌──────────────────────────────────────────────────────────────────┐ │ Step 2: 输出 - 还原后的 PDF 文件 │ │ 特征: 使用标准交叉引用表(XRef Table) │ │ 每个对象有独立的 offset 记录 │ │ 无 /ObjStm 流对象 │ └───────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Step 3: 测试材料 - 用于 pdfium 对象流压缩功能测试 │ │ 输入: 还原后的标准 PDF交叉引用表格式 │ │ 输出: 使用 pdfium 压缩后的 PDF对象流格式 │ │ 验证: 对比压缩率、文件完整性、兼容性 │ └──────────────────────────────────────────────────────────────────┘使用方法1. 配置参数# gc.py 配置部分PODOFO_GC_PATHrD:/PDF_SOURCE/podofo_exe/podofogc.exe# podofogc路径INPUT_DIRrD:/Compress_output/F2# 已压缩的PDF目录OUTPUT_DIRrD:/Compress_output/P2# 还原后的测试材料目录RECURSIVETrue# 递归扫描PRESERVE_STRUCTURETrue# 保留目录结构OVERWRITEFalse# 不覆盖已存在文件2. 运行脚本python gc.py3. 验证还原效果使用qpdf验证还原是否成功# 检查还原后的PDF是否使用交叉引用表qpdf --show-xref restored_file.pdf# 检查是否存在对象流qpdf --show-object-streams restored_file.pdf预期结果--show-xref应显示标准的交叉引用表每行一个对象的 offset--show-object-streams应显示 “no object streams” 或为空测试工作流完整测试流程# 伪代码示例pdfium 对象流压缩测试流程deftest_pdfium_object_stream_compression():# 1. 准备测试材料已通过 gc.py 还原的 PDFtest_filesget_restored_pdfs(D:/Compress_output/P2)forpdfintest_files:# 2. 使用 pdfium 进行对象流压缩compressed_pdfpdfium.compress_with_object_stream(pdf)# 3. 验证压缩效果original_sizeget_file_size(pdf)compressed_sizeget_file_size(compressed_pdf)compression_ratio(1-compressed_size/original_size)*100# 4. 验证文件完整性is_validpdfium.validate(compressed_pdf)# 5. 验证对象流生成has_object_streamcheck_object_stream(compressed_pdf)# 6. 记录测试结果log_result({file:pdf.name,original_size:original_size,compressed_size:compressed_size,compression_ratio:compression_ratio,is_valid:is_valid,has_object_stream:has_object_stream})测试指标指标说明验证方法压缩率压缩前后体积变化百分比文件大小对比文件完整性压缩后文件是否可正常打开pdfium 验证对象流生成是否成功生成对象流qpdf --show-object-streams兼容性是否兼容其他阅读器使用多种阅读器打开测试为什么选择 podofogcpodofogc 的优势┌─────────────────────────────────────────────────────────────┐ │ podofogc 还原特性 │ ├─────────────────────────────────────────────────────────────┤ │ ✓ 完全展开对象流为独立对象 │ │ ✓ 重建标准交叉引用表 │ │ ✓ 清理未引用的垃圾对象 │ │ ✓ 保持页面内容和结构完整 │ │ ✓ 生成干净的测试材料 │ └─────────────────────────────────────────────────────────────┘与其他工具对比工具对象流还原垃圾清理输出质量推荐度podofogc✓✓高★★★★★qpdf --linearize✗✓中★★★☆☆pdftk✗✗中★★☆☆☆注意事项1. 测试材料一致性确保所有测试文件都经过相同的还原流程# 建议在测试前清理输出目录确保每次测试使用相同的输入importshutil shutil.rmtree(OUTPUT_DIR,ignore_errorsTrue)2. 文件命名冲突当PRESERVE_STRUCTUREFalse时同名文件会产生冲突# 如果需要平坦输出建议启用覆盖或添加序号OVERWRITETrue# 或在代码中添加自动重命名逻辑3. 超时处理对于超大 PDF 文件建议增加超时时间# gc.py 第130行timeout600# 从5分钟增加到10分钟总结gc.py在您的 pdfium 对象流压缩测试流程中扮演关键的准备角色还原格式将已压缩的 PDF 还原为标准交叉引用表格式提供纯净材料确保测试输入的一致性和可追溯性支持批量处理高效处理大量测试文件辅助验证通过 podofogc 的垃圾清理确保测试材料质量通过gc.py准备的测试材料可以更准确地评估您的 pdfium 对象流压缩功能的压缩率、正确性和兼容性。完整代码podofo_gc.py#!/usr/bin/env python3# -*- coding: utf-8 -*- 批量使用podofogc处理PDF文件 - 简化版 功能遍历顶级目录及子目录中的所有PDF调用podofogc进行垃圾清理GC保存到另一个目录 importsubprocessimporttimefrompathlibimportPathfromtypingimportDict,List# 配置参数直接修改这里 PODOFO_GC_PATH rD:/PDF_SOURCE/podofo_exe/podofogc.exe # podofogc.exe的路径INPUT_DIRrD:/Compress_output/F2# 输入顶级目录OUTPUT_DIRrD:/Compress_output/P2# 输出顶级目录RECURSIVETrue# 是否递归子目录PRESERVE_STRUCTURETrue# 是否保留目录结构True:保留, False:所有文件直接输出到根目录OVERWRITEFalse# 是否覆盖已存在的文件False:跳过已存在的文件# classPodofoGCProcessor:Podofo GC批量处理器def__init__(self):初始化处理器self.podofo_gc_pathPath(PODOFO_GC_PATH)self.input_dirPath(INPUT_DIR)self.output_dirPath(OUTPUT_DIR)self.recursiveRECURSIVE self.preserve_structurePRESERVE_STRUCTURE self.overwriteOVERWRITE# 验证podofogc是否存在ifnotself.podofo_gc_path.exists():raiseFileNotFoundError(f找不到podofogc程序:{self.podofo_gc_path})# 验证输入目录ifnotself.input_dir.exists():raiseFileNotFoundError(f输入目录不存在:{self.input_dir})# 创建输出目录self.output_dir.mkdir(parentsTrue,exist_okTrue)# 统计信息self.stats{total:0,success:0,failed:0,skipped:0,input_size_total:0,output_size_total:0}defget_pdf_files(self)-List[Path]:获取所有需要处理的PDF文件ifself.recursive:# 递归查找所有PDF文件pdf_fileslist(self.input_dir.glob(**/*.pdf))else:# 只在当前目录查找pdf_fileslist(self.input_dir.glob(*.pdf))# 按路径排序保持一致性pdf_files.sort()returnpdf_filesdefget_output_path(self,input_path:Path)-Path:根据输入路径计算输出路径ifself.preserve_structure:# 保留目录结构relative_pathinput_path.relative_to(self.input_dir)output_pathself.output_dir/relative_pathelse:# 平坦结构所有文件直接放在输出目录output_pathself.output_dir/input_path.namereturnoutput_pathdefprocess_single_file(self,input_path:Path)-Dict:处理单个PDF文件result{input:input_path,output:None,success:False,error:None,input_size:0,output_size:0}try:# 获取输出路径output_pathself.get_output_path(input_path)result[output]output_path# 创建输出目录output_path.parent.mkdir(parentsTrue,exist_okTrue)# 检查输出文件是否已存在ifoutput_path.exists()andnotself.overwrite:result[error]输出文件已存在跳过returnresult# 获取输入文件大小input_sizeinput_path.stat().st_size result[input_size]input_sizeprint(f处理:{input_path.name})print(f 源文件:{input_path})print(f 目标文件:{output_path})# 构建命令: podofogc.exe input.pdf output.pdfcmd[str(self.podofo_gc_path),str(input_path),str(output_path)]# 执行命令start_timetime.time()processsubprocess.run(cmd,capture_outputTrue,textTrue,encodingutf-8,errorsignore,timeout300# 5分钟超时)elapsed_timetime.time()-start_timeifprocess.returncode0:# 成功output_sizeoutput_path.stat().st_sizeifoutput_path.exists()else0result[output_size]output_size result[success]True# 计算压缩率ifinput_size0:compression_ratio(1-output_size/input_size)*100else:compression_ratio0print(f ✓ 成功! 耗时:{elapsed_time:.1f}s)print(f 大小:{input_size/1024:.1f}KB -{output_size/1024:.1f}KB (压缩率:{compression_ratio:.1f}%))else:# 失败error_msgprocess.stderrifprocess.stderrelseprocess.stdout result[error]error_msgorf返回码:{process.returncode}print(f ✗ 失败:{result[error][:100]})exceptsubprocess.TimeoutExpired:result[error]处理超时超过5分钟print(f ✗ 超时:{input_path.name})exceptExceptionase:result[error]str(e)print(f ✗ 异常:{e})returnresultdefprocess_all(self)-Dict:批量处理所有PDF文件# 获取所有PDF文件print(f\n{*60})print(f扫描目录:{self.input_dir})pdf_filesself.get_pdf_files()ifnotpdf_files:print(f未找到任何PDF文件)returnself.stats self.stats[total]len(pdf_files)print(f找到{len(pdf_files)}个PDF文件)print(f输出目录:{self.output_dir})print(f保留目录结构:{self.preserve_structure})print(f覆盖已存在文件:{self.overwrite})print(f{*60}\n)# 开始处理start_timetime.time()fori,pdf_fileinenumerate(pdf_files,1):print(f\n[{i}/{len(pdf_files)}] 处理中...)resultself.process_single_file(pdf_file)ifresult[success]:self.stats[success]1self.stats[input_size_total]result[input_size]self.stats[output_size_total]result[output_size]elifresult[error]and跳过inresult[error]:self.stats[skipped]1print(f ⊙ 跳过:{result[error]})else:self.stats[failed]1elapsed_timetime.time()-start_time self._print_summary(elapsed_time)returnself.statsdef_print_summary(self,elapsed_time:float):打印处理总结print(f\n{*60})print(处理完成统计信息:)print(f 总文件数:{self.stats[total]})print(f 成功:{self.stats[success]})print(f 失败:{self.stats[failed]})print(f 跳过:{self.stats[skipped]})ifself.stats[success]0:input_mbself.stats[input_size_total]/(1024*1024)output_mbself.stats[output_size_total]/(1024*1024)saved_mbinput_mb-output_mb compression_ratio(saved_mb/input_mb)*100ifinput_mb0else0print(f 总输入大小:{input_mb:.2f}MB)print(f 总输出大小:{output_mb:.2f}MB)print(f 节省空间:{saved_mb:.2f}MB ({compression_ratio:.1f}%))print(f 总耗时:{elapsed_time:.1f}秒)print(f{*60})defmain():主函数try:# 创建处理器并执行processorPodofoGCProcessor()statsprocessor.process_all()# 根据结果返回退出码return0ifstats[failed]0else1exceptExceptionase:print(f程序执行失败:{e})return1if__name____main__:exit(main())