#!/usr/bin/env python3 """ YAML to PPTX Converter 将 YAML 格式的演示文稿源文件转换为 PPTX 文件 使用方法: # 验证 YAML 文件 uv run yaml2pptx.py check input.yaml # 转换为 PPTX uv run yaml2pptx.py convert input.yaml output.pptx uv run yaml2pptx.py convert input.yaml # 自动生成 input.pptx # 启动预览服务器 uv run yaml2pptx.py preview input.yaml """ import sys import argparse import random from pathlib import Path # 导入模块 from utils import log_info, log_success, log_error, log_progress from loaders.yaml_loader import YAMLError from core.presentation import Presentation from renderers.pptx_renderer import PptxGenerator from preview.server import start_preview_server from validators import Validator def parse_args(): """解析命令行参数""" parser = argparse.ArgumentParser( description="将 YAML 格式的演示文稿源文件转换为 PPTX 文件" ) subparsers = parser.add_subparsers(dest='command', required=True) # check 子命令 check_parser = subparsers.add_parser('check', help='验证 YAML 文件') check_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") check_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") # convert 子命令 convert_parser = subparsers.add_parser('convert', help='转换 YAML 为 PPTX') convert_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") convert_parser.add_argument("output", type=str, nargs="?", help="输出的 PPTX 文件路径(可选,默认为输入文件名.pptx)") convert_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") convert_parser.add_argument("--skip-validation", action="store_true", help="跳过自动验证") convert_parser.add_argument("-f", "--force", action="store_true", help="强制覆盖已存在文件") # preview 子命令 preview_parser = subparsers.add_parser('preview', help='启动预览服务器') preview_parser.add_argument("input", type=str, help="输入的 YAML 文件路径") preview_parser.add_argument("--template-dir", type=str, default=None, help="模板文件目录路径") preview_parser.add_argument("--port", type=int, default=None, help="服务器端口(默认:随机端口 30000-40000)") preview_parser.add_argument("--host", type=str, default="127.0.0.1", help="主机地址(默认:127.0.0.1)") preview_parser.add_argument("--no-browser", action="store_true", help="不自动打开浏览器") return parser.parse_args() def handle_check(args): """处理 check 子命令""" try: input_path = Path(args.input) # 处理模板目录 template_dir = None if args.template_dir: template_dir = Path(args.template_dir) # 执行验证 validator = Validator() result = validator.validate(input_path, template_dir) # 输出验证结果 print(result.format_output()) # 返回退出码 if result.has_errors(): sys.exit(1) else: sys.exit(0) except Exception as e: log_error(f"验证失败: {str(e)}") import traceback traceback.print_exc() sys.exit(1) def handle_preview(args): """处理 preview 子命令""" # 检查输入文件是否存在 input_path = Path(args.input) if not input_path.exists(): log_error(f"输入文件不存在: {input_path}") sys.exit(1) # 处理端口:如果未指定,随机选择 30000-40000 范围内的端口 port = args.port if port is None: port = random.randint(30000, 40000) # 处理模板目录 template_dir = Path(args.template_dir) if args.template_dir else None # 启动预览服务器 start_preview_server( yaml_file=input_path, template_dir=template_dir, port=port, host=args.host, open_browser=not args.no_browser ) def main(): """主函数:加载 YAML → 渲染幻灯片 → 生成 PPTX""" try: args = parse_args() # 路由到对应的处理函数 if args.command == 'check': handle_check(args) elif args.command == 'convert': handle_convert(args) elif args.command == 'preview': handle_preview(args) except YAMLError as e: log_error(str(e)) sys.exit(1) except Exception as e: log_error(f"未知错误: {str(e)}") import traceback traceback.print_exc() sys.exit(1) def handle_convert(args): """处理 convert 子命令""" # 检查是否提供了输入文件 if not args.input: log_error("错误: 缺少输入文件参数") sys.exit(1) # 处理输入文件路径 input_path = Path(args.input) # 检查输入文件是否存在 if not input_path.exists(): log_error(f"输入文件不存在: {input_path}") sys.exit(1) # 处理输出文件路径 if args.output: output_path = Path(args.output) else: # 自动生成输出文件名 output_path = input_path.with_suffix(".pptx") # 检查输出文件是否已存在(除非使用 --force) if output_path.exists() and not args.force: log_error(f"输出文件已存在: {output_path}") log_error("使用 --force 或 -f 强制覆盖") sys.exit(1) log_info(f"开始转换: {input_path}") log_info(f"输出文件: {output_path}") # 自动验证(除非使用 --skip-validation) if not args.skip_validation: log_info("验证 YAML 文件...") template_dir = Path(args.template_dir) if args.template_dir else None validator = Validator() result = validator.validate(input_path, template_dir) # 如果有错误,输出并终止 if result.has_errors(): print("\n" + result.format_output()) log_error("验证失败,转换已终止") sys.exit(1) # 如果有警告,输出但继续 if result.warnings: print("\n" + result.format_output()) print() # 空行 # 1. 加载演示文稿 log_info("加载演示文稿...") pres = Presentation(input_path, templates_dir=args.template_dir) # 2. 创建 PPTX 生成器 log_info(f"创建演示文稿 ({pres.size})...") generator = PptxGenerator(pres.size) # 3. 渲染所有幻灯片 slides_data = pres.data.get('slides', []) total_slides = len(slides_data) # 统计实际渲染的幻灯片数量 enabled_slides = [s for s in slides_data if s.get('enabled', True)] enabled_count = len(enabled_slides) slide_index = 0 for i, slide_data in enumerate(slides_data, 1): # 检查页面级 enabled if not slide_data.get('enabled', True): continue # 跳过禁用的页面 slide_index += 1 log_progress(slide_index, enabled_count, f"处理幻灯片") rendered_slide = pres.render_slide(slide_data) generator.add_slide(rendered_slide, input_path.parent) # 4. 保存 PPTX 文件 log_info("保存 PPTX 文件...") generator.save(output_path) log_success(f"转换完成: {output_path}") if __name__ == "__main__": main()