#!/usr/bin/env python3 """ 文档转 PDF 工具 使用 LibreOffice 将各种文档格式转换为 PDF 支持格式: - Word: .doc, .docx, .rtf - Excel: .xls, .xlsx - PowerPoint: .ppt, .pptx - OpenDocument: .odt, .ods, .odp - 文本: .txt, .csv - 图片: .jpg, .png (会嵌入PDF) """ import subprocess import os import sys from pathlib import Path from typing import Optional, List import concurrent.futures class Doc2PDF: """文档转 PDF 转换器""" SUPPORTED_FORMATS = { # 文档 '.doc', '.docx', '.rtf', '.odt', '.txt', # 表格 '.xls', '.xlsx', '.ods', '.csv', # 演示文稿 '.ppt', '.pptx', '.odp', # 图片 '.jpg', '.jpeg', '.png', '.bmp', '.gif', # 其他 '.html', '.htm', '.xml' } def __init__(self, libreoffice_path: str = 'libreoffice'): self.libreoffice_path = libreoffice_path self._verify_libreoffice() def _verify_libreoffice(self): """验证 LibreOffice 是否可用""" try: result = subprocess.run( [self.libreoffice_path, '--version'], capture_output=True, text=True ) if result.returncode != 0: raise RuntimeError("LibreOffice 不可用") print(f"✓ 检测到 {result.stdout.strip().split(chr(10))[0]}") except FileNotFoundError: raise RuntimeError("未找到 LibreOffice,请先安装") def convert( self, input_file: str, output_dir: Optional[str] = None, timeout: int = 120 ) -> str: """ 转换单个文件为 PDF Args: input_file: 输入文件路径 output_dir: 输出目录(默认为输入文件所在目录) timeout: 超时时间(秒) Returns: 生成的 PDF 文件路径 """ input_path = Path(input_file).resolve() if not input_path.exists(): raise FileNotFoundError(f"文件不存在: {input_file}") suffix = input_path.suffix.lower() if suffix not in self.SUPPORTED_FORMATS: raise ValueError(f"不支持的格式: {suffix}") if output_dir is None: output_dir = str(input_path.parent) else: output_dir = str(Path(output_dir).resolve()) os.makedirs(output_dir, exist_ok=True) cmd = [ self.libreoffice_path, '--headless', '--invisible', '--nologo', '--nofirststartwizard', '--convert-to', 'pdf', '--outdir', output_dir, str(input_path) ] print(f" 转换中: {input_path.name} ...", end=' ', flush=True) try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout ) if result.returncode != 0: print("✗") raise RuntimeError(f"转换失败: {result.stderr}") output_file = os.path.join(output_dir, f"{input_path.stem}.pdf") if os.path.exists(output_file): print("✓") return output_file else: print("✗") raise RuntimeError("转换完成但未找到输出文件") except subprocess.TimeoutExpired: print("✗ (超时)") raise RuntimeError(f"转换超时 ({timeout}秒)") def convert_batch( self, input_files: List[str], output_dir: Optional[str] = None, max_workers: int = 1 # LibreOffice 并发可能有问题,建议设为1 ) -> List[dict]: """ 批量转换文件 Args: input_files: 输入文件列表 output_dir: 输出目录 max_workers: 并发数 Returns: 转换结果列表 """ results = [] print(f"\n开始批量转换 {len(input_files)} 个文件...\n") for i, input_file in enumerate(input_files, 1): print(f"[{i}/{len(input_files)}]", end='') try: output_file = self.convert(input_file, output_dir) results.append({ 'input': input_file, 'output': output_file, 'success': True, 'error': None }) except Exception as e: results.append({ 'input': input_file, 'output': None, 'success': False, 'error': str(e) }) # 打印统计 success_count = sum(1 for r in results if r['success']) print(f"\n转换完成: {success_count}/{len(results)} 成功") return results def convert_directory( self, input_dir: str, output_dir: Optional[str] = None, recursive: bool = False ) -> List[dict]: """ 转换目录中的所有支持文件 Args: input_dir: 输入目录 output_dir: 输出目录 recursive: 是否递归处理子目录 """ input_path = Path(input_dir) if not input_path.is_dir(): raise NotADirectoryError(f"不是目录: {input_dir}") # 收集所有支持的文件 files = [] pattern = '**/*' if recursive else '*' for file_path in input_path.glob(pattern): if file_path.is_file() and file_path.suffix.lower() in self.SUPPORTED_FORMATS: files.append(str(file_path)) if not files: print("未找到支持的文件") return [] return self.convert_batch(files, output_dir) def main(): """命令行入口""" import argparse parser = argparse.ArgumentParser( description='使用 LibreOffice 将文档转换为 PDF', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' 示例: python doc2pdf.py document.docx python doc2pdf.py document.xlsx -o ./output python doc2pdf.py ./documents/ -o ./pdfs -r ''' ) parser.add_argument('input', help='输入文件或目录') parser.add_argument('-o', '--output', help='输出目录') parser.add_argument('-r', '--recursive', action='store_true', help='递归处理子目录') parser.add_argument('-t', '--timeout', type=int, default=120, help='单个文件转换超时时间(秒)') args = parser.parse_args() converter = Doc2PDF() input_path = Path(args.input) if input_path.is_file(): # 单文件转换 try: output = converter.convert(args.input, args.output, args.timeout) print(f"\n输出文件: {output}") except Exception as e: print(f"\n错误: {e}") sys.exit(1) elif input_path.is_dir(): # 目录转换 results = converter.convert_directory( args.input, args.output, args.recursive ) # 显示失败的文件 failures = [r for r in results if not r['success']] if failures: print("\n失败的文件:") for f in failures: print(f" - {f['input']}: {f['error']}") else: print(f"错误: 路径不存在 - {args.input}") sys.exit(1) if __name__ == '__main__': main()