gc.py 功能介绍:PDF 对象流还原工具(用于 pdfium 测试)

文章目录

gc.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. 配置参数

python 复制代码
# gc.py 配置部分
PODOFO_GC_PATH = r"D:/PDF_SOURCE/podofo_exe/podofogc.exe"  # podofogc路径
INPUT_DIR = r"D:/Compress_output/F2"                        # 已压缩的PDF目录
OUTPUT_DIR = r"D:/Compress_output/P2"                       # 还原后的测试材料目录
RECURSIVE = True                                            # 递归扫描
PRESERVE_STRUCTURE = True                                   # 保留目录结构
OVERWRITE = False                                           # 不覆盖已存在文件

2. 运行脚本

bash 复制代码
python gc.py

3. 验证还原效果

使用 qpdf 验证还原是否成功:

bash 复制代码
# 检查还原后的PDF是否使用交叉引用表
qpdf --show-xref restored_file.pdf

# 检查是否存在对象流
qpdf --show-object-streams restored_file.pdf

预期结果

  • --show-xref 应显示标准的交叉引用表(每行一个对象的 offset)
  • --show-object-streams 应显示 "no object streams" 或为空

测试工作流

完整测试流程

python 复制代码
# 伪代码示例:pdfium 对象流压缩测试流程

def test_pdfium_object_stream_compression():
    # 1. 准备测试材料(已通过 gc.py 还原的 PDF)
    test_files = get_restored_pdfs("D:/Compress_output/P2")
    
    for pdf in test_files:
        # 2. 使用 pdfium 进行对象流压缩
        compressed_pdf = pdfium.compress_with_object_stream(pdf)
        
        # 3. 验证压缩效果
        original_size = get_file_size(pdf)
        compressed_size = get_file_size(compressed_pdf)
        compression_ratio = (1 - compressed_size / original_size) * 100
        
        # 4. 验证文件完整性
        is_valid = pdfium.validate(compressed_pdf)
        
        # 5. 验证对象流生成
        has_object_stream = check_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
兼容性 是否兼容其他阅读器 使用多种阅读器打开测试

为什么选择 podofogc

podofogc 的优势

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    podofogc 还原特性                        │
├─────────────────────────────────────────────────────────────┤
│  ✓ 完全展开对象流为独立对象                                  │
│  ✓ 重建标准交叉引用表                                        │
│  ✓ 清理未引用的垃圾对象                                      │
│  ✓ 保持页面内容和结构完整                                    │
│  ✓ 生成干净的测试材料                                        │
└─────────────────────────────────────────────────────────────┘

与其他工具对比

工具 对象流还原 垃圾清理 输出质量 推荐度
podofogc ★★★★★
qpdf --linearize ★★★☆☆
pdftk ★★☆☆☆

注意事项

1. 测试材料一致性

确保所有测试文件都经过相同的还原流程:

python 复制代码
# 建议:在测试前清理输出目录,确保每次测试使用相同的输入
import shutil
shutil.rmtree(OUTPUT_DIR, ignore_errors=True)

2. 文件命名冲突

PRESERVE_STRUCTURE=False 时,同名文件会产生冲突:

python 复制代码
# 如果需要平坦输出,建议启用覆盖或添加序号
OVERWRITE = True  # 或在代码中添加自动重命名逻辑

3. 超时处理

对于超大 PDF 文件,建议增加超时时间:

python 复制代码
# gc.py 第130行
timeout=600  # 从5分钟增加到10分钟

总结

gc.py 在您的 pdfium 对象流压缩测试流程中扮演关键的准备角色

  1. 还原格式:将已压缩的 PDF 还原为标准交叉引用表格式
  2. 提供纯净材料:确保测试输入的一致性和可追溯性
  3. 支持批量处理:高效处理大量测试文件
  4. 辅助验证:通过 podofogc 的垃圾清理确保测试材料质量

通过 gc.py 准备的测试材料,可以更准确地评估您的 pdfium 对象流压缩功能的压缩率、正确性和兼容性。

完整代码

podofo_gc.py

python 复制代码
#!/usr/bin/env python3  
# -*- coding: utf-8 -*-  
"""  
批量使用podofogc处理PDF文件 - 简化版  
功能:遍历顶级目录及子目录中的所有PDF,调用podofogc进行垃圾清理(GC),保存到另一个目录  
"""  
  
import subprocess  
import time  
from pathlib import Path  
from typing import Dict, List  
  
# ==================== 配置参数(直接修改这里) ====================PODOFO_GC_PATH = r"D:/PDF_SOURCE/podofo_exe/podofogc.exe"  # podofogc.exe的路径  
INPUT_DIR = r"D:/Compress_output/F2"  # 输入顶级目录  
OUTPUT_DIR = r"D:/Compress_output/P2"  # 输出顶级目录  
RECURSIVE = True  # 是否递归子目录  
PRESERVE_STRUCTURE = True  # 是否保留目录结构(True:保留, False:所有文件直接输出到根目录)  
OVERWRITE = False  # 是否覆盖已存在的文件(False:跳过已存在的文件)  
  
  
# =================================================================  
  
  
class PodofoGCProcessor:  
    """Podofo GC批量处理器"""  
  
    def __init__(self):  
        """初始化处理器"""  
        self.podofo_gc_path = Path(PODOFO_GC_PATH)  
        self.input_dir = Path(INPUT_DIR)  
        self.output_dir = Path(OUTPUT_DIR)  
        self.recursive = RECURSIVE  
        self.preserve_structure = PRESERVE_STRUCTURE  
        self.overwrite = OVERWRITE  
  
        # 验证podofogc是否存在  
        if not self.podofo_gc_path.exists():  
            raise FileNotFoundError(f"找不到podofogc程序: {self.podofo_gc_path}")  
  
        # 验证输入目录  
        if not self.input_dir.exists():  
            raise FileNotFoundError(f"输入目录不存在: {self.input_dir}")  
  
        # 创建输出目录  
        self.output_dir.mkdir(parents=True, exist_ok=True)  
  
        # 统计信息  
        self.stats = {  
            'total': 0,  
            'success': 0,  
            'failed': 0,  
            'skipped': 0,  
            'input_size_total': 0,  
            'output_size_total': 0  
        }  
  
    def get_pdf_files(self) -> List[Path]:  
        """获取所有需要处理的PDF文件"""  
        if self.recursive:  
            # 递归查找所有PDF文件  
            pdf_files = list(self.input_dir.glob("**/*.pdf"))  
        else:            # 只在当前目录查找  
            pdf_files = list(self.input_dir.glob("*.pdf"))  
  
        # 按路径排序,保持一致性  
        pdf_files.sort()  
        return pdf_files  
  
    def get_output_path(self, input_path: Path) -> Path:  
        """根据输入路径计算输出路径"""  
        if self.preserve_structure:  
            # 保留目录结构  
            relative_path = input_path.relative_to(self.input_dir)  
            output_path = self.output_dir / relative_path  
        else:  
            # 平坦结构,所有文件直接放在输出目录  
            output_path = self.output_dir / input_path.name  
  
        return output_path  
  
    def process_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_path = self.get_output_path(input_path)  
            result['output'] = output_path  
  
            # 创建输出目录  
            output_path.parent.mkdir(parents=True, exist_ok=True)  
  
            # 检查输出文件是否已存在  
            if output_path.exists() and not self.overwrite:  
                result['error'] = "输出文件已存在,跳过"  
                return result  
  
            # 获取输入文件大小  
            input_size = input_path.stat().st_size  
            result['input_size'] = input_size  
  
            print(f"处理: {input_path.name}")  
            print(f"  源文件: {input_path}")  
            print(f"  目标文件: {output_path}")  
  
            # 构建命令: podofogc.exe input.pdf output.pdf  
            cmd = [  
                str(self.podofo_gc_path),  
                str(input_path),  
                str(output_path)  
            ]  
            # 执行命令  
            start_time = time.time()  
            process = subprocess.run(  
                cmd,  
                capture_output=True,  
                text=True,  
                encoding='utf-8',  
                errors='ignore',  
                timeout=300  # 5分钟超时  
            )  
            elapsed_time = time.time() - start_time  
  
            if process.returncode == 0:  
                # 成功  
                output_size = output_path.stat().st_size if output_path.exists() else 0  
                result['output_size'] = output_size  
                result['success'] = True  
  
                # 计算压缩率  
                if input_size > 0:  
                    compression_ratio = (1 - output_size / input_size) * 100  
                else:  
                    compression_ratio = 0  
  
                print(f"  ✓ 成功! 耗时: {elapsed_time:.1f}s")  
                print(  
                    f"    大小: {input_size / 1024:.1f}KB -> {output_size / 1024:.1f}KB (压缩率: {compression_ratio:.1f}%)")  
            else:                # 失败  
                error_msg = process.stderr if process.stderr else process.stdout  
                result['error'] = error_msg or f"返回码: {process.returncode}"  
                print(f"  ✗ 失败: {result['error'][:100]}")  
  
        except subprocess.TimeoutExpired:  
            result['error'] = "处理超时(超过5分钟)"  
            print(f"  ✗ 超时: {input_path.name}")  
        except Exception as e:  
            result['error'] = str(e)  
            print(f"  ✗ 异常: {e}")  
  
        return result  
  
    def process_all(self) -> Dict:  
        """批量处理所有PDF文件"""  
        # 获取所有PDF文件  
        print(f"\n{'=' * 60}")  
        print(f"扫描目录: {self.input_dir}")  
        pdf_files = self.get_pdf_files()  
  
        if not pdf_files:  
            print(f"未找到任何PDF文件!")  
            return self.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_time = time.time()  
  
        for i, pdf_file in enumerate(pdf_files, 1):  
            print(f"\n[{i}/{len(pdf_files)}] 处理中...")  
            result = self.process_single_file(pdf_file)  
  
            if result['success']:  
                self.stats['success'] += 1  
                self.stats['input_size_total'] += result['input_size']  
                self.stats['output_size_total'] += result['output_size']  
            elif result['error'] and "跳过" in result['error']:  
                self.stats['skipped'] += 1  
                print(f"  ⊙ 跳过: {result['error']}")  
            else:                self.stats['failed'] += 1  
  
        elapsed_time = time.time() - start_time  
        self._print_summary(elapsed_time)  
  
        return self.stats  
  
    def _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']}")  
  
        if self.stats['success'] > 0:  
            input_mb = self.stats['input_size_total'] / (1024 * 1024)  
            output_mb = self.stats['output_size_total'] / (1024 * 1024)  
            saved_mb = input_mb - output_mb  
            compression_ratio = (saved_mb / input_mb) * 100 if input_mb > 0 else 0  
  
            print(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}")  
  
  
def main():  
    """主函数"""  
    try:  
        # 创建处理器并执行  
        processor = PodofoGCProcessor()  
        stats = processor.process_all()  
  
        # 根据结果返回退出码  
        return 0 if stats['failed'] == 0 else 1  
  
    except Exception as e:  
        print(f"程序执行失败: {e}")  
        return 1  
  
  
if __name__ == "__main__":  
    exit(main())
相关推荐
啦啦啦_99991 小时前
2. 梯度下降算法分类 & 梯度下降与正规方程对比
人工智能·算法·分类
沙白猿1 小时前
代码随想录 28(动态规划)
算法·动态规划
wuweijianlove2 小时前
算法复杂度与工程性能的双重度量体系技术7
算法
小年糕是糕手2 小时前
【C/C++刷题集】栈、stack、队列、queue核心精讲
c语言·开发语言·数据结构·数据库·c++·算法·蓝桥杯
ji_shuke2 小时前
访问s3里pdf文件出现strict-origin-when-cross-origin问题修复
pdf
CHANG_THE_WORLD2 小时前
使用python调用podofogc文件 批量处理 pdf文件
windows·python·pdf
隔壁大炮2 小时前
CNN图像分类案例
人工智能·pytorch·python·深度学习·算法·分类·cnn
始三角龙2 小时前
LeetCode hoot 100 -- 最小覆盖子串
算法·leetcode·职场和发展
小年糕是糕手2 小时前
【C/C++刷题集】顺序表、vector、链表、list核心精讲
c语言·开发语言·数据结构·c++·算法·leetcode·蓝桥杯