|
|
@@ -0,0 +1,285 @@
|
|
|
+#!/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()
|
|
|
+
|