"""Convert HTML file(s) or a directory of HTML files into a single PPTX.
Reuses the existing `htmls_to_pptx` helper from
`core.ppt_generator.utils.common`, which renders each HTML with a headless
browser, merges the PDFs, then converts via LibreOffice into a PPTX.
Inputs:
- A single directory: every ``.html`` / ``.htm`` file inside is collected
(natural-sorted by filename) and merged into one PPTX.
- A single HTML file: converted into a one-page PPTX.
- Multiple HTML file paths: merged into one PPTX in the order given.
Usage:
python html_to_pptx.py <input_dir> [-o <output_dir>] [-n <filename>]
python html_to_pptx.py <page.html> [-o <output_dir>] [-n <filename>]
python html_to_pptx.py <p1.html> <p2.html> ... [-o <output_dir>] [-n <filename>]
"""
import argparse
import asyncio
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.append(str(ROOT))
from core.utils.logger import logger
HTML_SUFFIXES = {".html", ".htm"}
def _natural_key(path: Path):
"""Sort 0.html, 1.html, ..., 10.html naturally instead of lexicographically."""
parts = re.split(r"(\d+)", path.stem)
return [int(p) if p.isdigit() else p.lower() for p in parts]
def _collect_html_files_from_dir(input_dir: Path) -> list[str]:
html_paths = sorted(
(p for p in input_dir.iterdir() if p.is_file() and p.suffix.lower() in HTML_SUFFIXES),
key=_natural_key,
)
return [str(p) for p in html_paths]
def _resolve_inputs(inputs: list[Path]) -> tuple[list[str], Path]:
"""Resolve the user-supplied paths into (ordered html file list, default_output_dir).
- 1 directory → enumerate its HTML files, default output dir = the directory
- 1+ html files → use as-is, default output dir = parent of the first file
"""
if len(inputs) == 1 and inputs[0].is_dir():
input_dir = inputs[0]
html_paths = _collect_html_files_from_dir(input_dir)
if not html_paths:
raise ValueError(f"在目录中未找到 HTML 文件: {input_dir}")
return html_paths, input_dir
html_paths: list[str] = []
for path in inputs:
if not path.exists():
raise ValueError(f"输入路径不存在: {path}")
if path.is_dir():
raise ValueError(
f"混合输入不支持:当提供多个路径时每个都必须是 HTML 文件,但 {path} 是目录"
)
if path.suffix.lower() not in HTML_SUFFIXES:
raise ValueError(f"不是 HTML 文件: {path}")
html_paths.append(str(path))
return html_paths, inputs[0].parent
async def _convert(html_paths: list[str], output_dir: Path, filename: str) -> None:
from core.ppt_generator.utils.common import htmls_to_pptx
output_dir.mkdir(parents=True, exist_ok=True)
logger.info(f">>> 共 {len(html_paths)} 个 HTML 文件,按以下顺序合成:")
for idx, path in enumerate(html_paths):
logger.info(f" [{idx + 1:02d}] {path}")
logger.info(f">>> 输出目录: {output_dir}")
logger.info(f">>> 输出文件名: {filename}")
pdf_path, pptx_path = await htmls_to_pptx(html_paths, str(output_dir), filename)
logger.info(f">>> 合并 PDF: {pdf_path}")
if pptx_path:
logger.info(f">>> 生成 PPTX: {pptx_path}")
else:
raise RuntimeError("PDF 转 PPTX 失败,请检查日志中的 LibreOffice 报错信息。")
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="将 HTML 文件或目录合成为单个 PPTX 文件。"
)
parser.add_argument(
"inputs",
type=Path,
nargs="+",
help="一个 HTML 目录,或一个/多个 HTML 文件路径",
)
parser.add_argument(
"-o",
"--output-dir",
type=Path,
default=None,
help="PPTX 输出目录(默认:目录输入时为该目录本身,文件输入时为第一个文件所在目录)",
)
parser.add_argument(
"-n",
"--filename",
default="output",
help="PPTX 文件名(不含扩展名,默认 output)",
)
return parser.parse_args()
def main() -> None:
args = _parse_args()
inputs = [p.expanduser().resolve() for p in args.inputs]
html_paths, default_output_dir = _resolve_inputs(inputs)
output_dir: Path = (args.output_dir or default_output_dir).expanduser().resolve()
asyncio.run(_convert(html_paths, output_dir, args.filename))
if __name__ == "__main__":
try:
main()
except Exception as exc:
logger.error(str(exc))
raise