#!/usr/bin/env python3 """Office to PDF using the preinstalled LibreOffice, isolated per invocation.""" from pathlib import Path import shutil import subprocess import sys import tempfile from _pdf_common import ( SkillArgumentParser, output_pdf, new_temp_pdf, publish_temp_file, run_cli, ) FORMATS = { ".docx", ".doc", ".odt", ".rtf", ".pptx", ".ppt", ".odp", ".xlsx", ".xls", ".ods", } def convert(args): from pypdf import PdfReader source = Path(args.input).expanduser().resolve() if ( not source.is_file() or source.suffix.lower() not in FORMATS or not 0 < source.stat().st_size <= 25 * 1024 * 1024 ): raise ValueError( "输入需为不超过 25 MiB 的本地 Office 文档;不支持把 PDF 直接反向转成可编辑 Office" ) if not 1 <= args.timeout <= 600: raise ValueError("timeout 必须在 1–600 秒之间") executable = shutil.which("soffice") or shutil.which("libreoffice") if not executable: raise RuntimeError("基础镜像缺少 LibreOffice,需要更新镜像") target = output_pdf(args.output, args.overwrite) temporary = new_temp_pdf(target) try: with tempfile.TemporaryDirectory(prefix="pdf-office-") as folder: root = Path(folder) incoming = root / "input" outgoing = root / "output" profile = root / "profile" incoming.mkdir() outgoing.mkdir() profile.mkdir() local = incoming / ("source" + source.suffix.lower()) shutil.copyfile(source, local) command = [ executable, "-env:UserInstallation=" + profile.as_uri(), "--headless", "--nologo", "--nodefault", "--nofirststartwizard", "--convert-to", "pdf", "--outdir", str(outgoing), str(local), ] completed = subprocess.run( command, capture_output=True, text=True, timeout=args.timeout, check=False, ) result = outgoing / "source.pdf" if completed.returncode or not result.is_file(): raise RuntimeError( "Office 转 PDF 失败:" + (completed.stderr or completed.stdout)[-1500:] ) count = len(PdfReader(result).pages) if not count: raise ValueError("转换结果没有页面") shutil.copyfile(result, temporary) publish_temp_file(temporary, target, args.overwrite) finally: temporary.unlink(missing_ok=True) return { "source": str(source), "path": str(target), "page_count": count, "engine": "libreoffice", "requires_visual_review": True, "note": "转换前需在对应 Office skill 中重算公式并核对字体、图表和打印范围。", } def main(argv=None): parser = SkillArgumentParser(description="Office 文档导出 PDF,源文件保持不变") parser.add_argument("--input", required=True) parser.add_argument("--output", required=True) parser.add_argument("--timeout", type=int, default=180) parser.add_argument("--overwrite", action="store_true") return run_cli( lambda: convert(parser.parse_args(sys.argv[1:] if argv is None else argv)) ) if __name__ == "__main__": raise SystemExit(main())