| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- #!/usr/bin/env python3
- """
- 脚本结果可视化工具 V2
- 功能:为 output_demo_script_v2.json 中的每个视频生成独立的HTML可视化页面
- 交互形式:卡片+点击详情
- """
- import json
- import argparse
- import sys
- from pathlib import Path
- from datetime import datetime
- from typing import List, Dict, Any, Optional
- import html as html_module
- # 保证可以从项目根目录导入
- PROJECT_ROOT = Path(__file__).parent.parent
- if str(PROJECT_ROOT) not in sys.path:
- sys.path.insert(0, str(PROJECT_ROOT))
- # 导入tab模块
- from static.visualize_v2.tab1 import generate_tab1_content
- from static.visualize_v2.tab2 import generate_tab2_content
- from static.visualize_v2.tab3 import generate_tab3_content
- from static.visualize_v2.tab4 import generate_tab4_content
- class ScriptResultVisualizerV2:
- """脚本结果可视化器 V2"""
- def __init__(self, json_file: str = None):
- """
- 初始化可视化器
- Args:
- json_file: JSON文件路径
- """
- if json_file is None:
- self.json_file = None
- else:
- self.json_file = Path(json_file)
- if not self.json_file.is_absolute():
- self.json_file = Path.cwd() / json_file
- def load_json_data(self, file_path: Path) -> Optional[Dict[str, Any]]:
- """
- 加载JSON文件
- Args:
- file_path: JSON文件路径
- Returns:
- JSON数据字典,加载失败返回None
- """
- try:
- with open(file_path, 'r', encoding='utf-8') as f:
- return json.load(f)
- except Exception as e:
- print(f"加载文件失败 {file_path}: {e}")
- return None
- def generate_html(self, data: Dict[str, Any], video_data: Dict[str, Any], json_filename: str) -> str:
- """生成完整的HTML页面"""
- # 开始构建HTML
- html = '<!DOCTYPE html>\n'
- html += '<html lang="zh-CN">\n'
- html += '<head>\n'
- html += ' <meta charset="UTF-8">\n'
- html += ' <meta name="viewport" content="width=device-width, initial-scale=1.0">\n'
- html += f' <title>脚本结果可视化 V2 - {json_filename}</title>\n'
- html += ' <link rel="stylesheet" href="visualize/style.css">\n'
- html += '</head>\n'
- html += '<body>\n'
- html += '<div class="container">\n'
- # 页眉
- html += '<div class="header">\n'
- html += ' <h1>脚本结果可视化 V2</h1>\n'
- # 显示视频信息
- video_title = video_data.get("title", "")
- video_id = video_data.get("video_id", "")
- if video_title:
- html += f' <div class="subtitle">{html_module.escape(video_title)}</div>\n'
- if video_id:
- html += f' <div class="subtitle">视频ID: {video_id}</div>\n'
- html += f' <div class="subtitle">{json_filename}</div>\n'
- html += f' <div class="subtitle">生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</div>\n'
- html += '</div>\n'
- # Tab导航
- html += '<div class="tabs">\n'
- html += ' <button class="tab active" onclick="switchTab(\'tab1\')">结构化内容库</button>\n'
- html += ' <button class="tab" onclick="switchTab(\'tab2\')">L3单元解构</button>\n'
- html += ' <button class="tab" onclick="switchTab(\'tab3\')">整体结构理解</button>\n'
- html += ' <button class="tab" onclick="switchTab(\'tab4\')">金句提取</button>\n'
- html += '</div>\n'
- # 主内容
- html += '<div class="content">\n'
- # Tab1内容:结构化内容库
- html += generate_tab1_content(data)
- # Tab2内容:L3单元解构
- html += generate_tab2_content(data)
- # Tab3内容:整体结构理解
- html += generate_tab3_content(data)
- # Tab4内容:金句提取
- html += generate_tab4_content(data)
- html += '</div>\n'
- # 页脚
- html += '<div class="footer">\n'
- html += f' <p>生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>\n'
- html += '</div>\n'
- html += '</div>\n'
- # JavaScript
- html += '<script src="visualize/script.js"></script>\n'
- html += '</body>\n'
- html += '</html>\n'
- return html
- def save_all_html(self, output_dir: str | Path | None = None) -> List[str]:
- """
- 基于 output_demo_script_v2.json,为其中每个视频生成一个独立的 HTML 页面。
- 支持结构:
- {
- "results": [
- {
- "video_data": {...},
- "script_result": {...}
- },
- ...
- ]
- }
- """
- if self.json_file is None:
- print("❌ 错误: 未指定JSON文件")
- return []
- # 加载JSON数据
- data = self.load_json_data(self.json_file)
- if data is None:
- return []
- results = data.get("results") or []
- if not isinstance(results, list) or not results:
- print("⚠️ JSON 中未找到有效的 results 数组")
- return []
- # 确定输出目录
- if output_dir is None:
- # 默认输出到examples/html目录
- output_dir = Path(__file__).parent / "html"
- else:
- output_dir = Path(output_dir)
- if not output_dir.is_absolute():
- output_dir = Path.cwd() / output_dir
- # 创建输出目录
- output_dir.mkdir(parents=True, exist_ok=True)
- # 确保样式和脚本文件可用:从 html/visualize 拷贝到 输出目录/visualize
- source_visualize_dir = Path(__file__).parent / "html" / "visualize"
- target_visualize_dir = output_dir / "visualize"
- if source_visualize_dir.exists() and source_visualize_dir.is_dir():
- import shutil
- target_visualize_dir.mkdir(parents=True, exist_ok=True)
- for item in source_visualize_dir.iterdir():
- dst = target_visualize_dir / item.name
- if item.is_file():
- # 如果源文件和目标文件是同一个,跳过
- if item.resolve() != dst.resolve():
- shutil.copy2(item, dst)
- generated_paths: List[str] = []
- print(f"📁 检测到 output_demo_script_v2 格式,包含 {len(results)} 条结果")
- for idx, item in enumerate(results, start=1):
- script_data = item.get("script_result")
- if not isinstance(script_data, dict):
- print(f"⚠️ 跳过第 {idx} 条结果:缺少 script_result 字段或结构不正确")
- continue
- video_data = item.get("video_data") or {}
- video_id = video_data.get("video_id") or video_data.get("channel_content_id")
- # 用于 HTML 内部展示的"文件名"标签
- json_label = f"{self.json_file.name}#{idx}"
- # 生成输出文件名:{video_id}_v2.html
- if video_id:
- output_filename = f"{video_id}_v2.html"
- else:
- output_filename = f"{self.json_file.stem}_{idx}_v2.html"
- output_path = output_dir / output_filename
- html_content = self.generate_html(script_data, video_data, json_label)
- with open(output_path, "w", encoding="utf-8") as f:
- f.write(html_content)
- generated_paths.append(str(output_path))
- print(f"✅ HTML文件已生成: {output_path}")
- if not generated_paths:
- print("⚠️ 未能从 JSON 中生成任何 HTML 文件")
- return generated_paths
- def main():
- """主函数"""
- # 解析命令行参数
- parser = argparse.ArgumentParser(
- description='脚本结果可视化工具 V2 - 基于 output_demo_script_v2.json 为每个视频生成独立的HTML页面',
- formatter_class=argparse.RawDescriptionHelpFormatter,
- epilog="""
- 使用示例:
- # 在当前 examples 目录下使用默认的 output_demo_script_v2.json 并输出到 examples/html
- python visualize_script_results_v2.py
- # 指定 JSON 文件
- python visualize_script_results_v2.py examples/output_demo_script_v2.json
- # 指定 JSON 文件和输出目录
- python visualize_script_results_v2.py examples/output_demo_script_v2.json --output-dir examples/html
- """
- )
- parser.add_argument(
- 'json_file',
- type=str,
- nargs='?',
- help='JSON文件路径(默认为 examples/output_demo_script_v2.json)'
- )
- parser.add_argument(
- '-o', '--output-dir',
- type=str,
- default=None,
- help='输出目录路径(默认: examples/html)'
- )
- args = parser.parse_args()
- # 确定 JSON 文件路径
- if args.json_file:
- json_path = Path(args.json_file)
- if not json_path.is_absolute():
- json_path = Path.cwd() / json_path
- else:
- # 默认使用 examples/output_demo_script_v2.json
- json_path = Path(__file__).parent / "output_demo_script_v2.json"
- print("🚀 开始生成脚本结果可视化 V2...")
- print(f"📁 JSON文件: {json_path}")
- print(f"📄 输出目录: {args.output_dir or (Path(__file__).parent / 'html')}")
- print()
- visualizer = ScriptResultVisualizerV2(json_file=str(json_path))
- generated_files = visualizer.save_all_html(output_dir=args.output_dir)
- if generated_files:
- print()
- print(f"🎉 完成! 共生成 {len(generated_files)} 个HTML文件")
- # 提示其中一个示例文件
- print(f"📄 示例: 请在浏览器中打开: {generated_files[0]}")
- if __name__ == "__main__":
- main()
|