mxivideo/python_core/cli/commands/template.py

470 lines
17 KiB
Python

"""
模板管理CLI命令
"""
from pathlib import Path
from typing import Optional, List
import typer
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
from rich.live import Live
import json
from python_core.services.template_manager import TemplateManager, TemplateInfo
from python_core.utils.logger import logger
console = Console()
template_app = typer.Typer(name="template", help="模板管理命令")
@template_app.command("import")
def batch_import(
source_folder: str = typer.Argument(..., help="包含模板子文件夹的源文件夹"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出"),
show_progress: bool = typer.Option(True, "--progress", "-p", help="显示进度条")
):
"""批量导入模板"""
try:
console.print(f"📦 [bold blue]批量导入模板[/bold blue]")
console.print(f"📁 源文件夹: {source_folder}")
# 验证源文件夹
source_path = Path(source_folder)
if not source_path.exists():
console.print(f"[red]❌ 源文件夹不存在: {source_folder}[/red]")
raise typer.Exit(1)
if not source_path.is_dir():
console.print(f"[red]❌ 路径不是文件夹: {source_folder}[/red]")
raise typer.Exit(1)
# 创建模板管理器
manager = TemplateManager()
# 进度回调函数
progress_messages = []
def progress_callback(message: str):
progress_messages.append(message)
if verbose:
console.print(f" {message}")
# 执行批量导入
if show_progress and not verbose:
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=console
) as progress:
task = progress.add_task("正在导入模板...", total=None)
def progress_with_bar(message: str):
progress.update(task, description=message)
progress_callback(message)
result = manager.batch_import_templates(source_folder, progress_with_bar)
else:
result = manager.batch_import_templates(source_folder, progress_callback)
# 显示结果
if result['status']:
console.print(f"\n✅ [bold green]导入完成![/bold green]")
console.print(f"📊 成功导入: {result['imported_count']} 个模板")
console.print(f"❌ 导入失败: {result['failed_count']} 个模板")
# 显示成功导入的模板
if result['imported_templates'] and verbose:
console.print(f"\n📋 [bold green]成功导入的模板:[/bold green]")
for template in result['imported_templates']:
if isinstance(template, dict):
console.print(f"{template.get('name', 'Unknown')} (ID: {template.get('id', 'Unknown')})")
else:
console.print(f"{template.name} (ID: {template.id})")
# 显示失败的模板
if result['failed_templates']:
console.print(f"\n❌ [bold red]导入失败的模板:[/bold red]")
for failed in result['failed_templates']:
console.print(f"{failed['name']}: {failed['error']}")
else:
console.print(f"[red]❌ 导入失败: {result['msg']}[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]❌ 批量导入失败: {str(e)}[/red]")
raise typer.Exit(1)
@template_app.command("list")
def list_templates(
limit: int = typer.Option(20, "--limit", "-l", help="显示数量限制"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出"),
format_output: str = typer.Option("table", "--format", "-f", help="输出格式: table, json")
):
"""列出所有模板"""
try:
console.print(f"📋 [bold blue]模板列表[/bold blue]")
# 创建模板管理器
manager = TemplateManager()
# 获取模板列表
templates = manager.get_templates()
if not templates:
console.print("📭 没有模板")
return
# 限制显示数量
total_count = len(templates)
if len(templates) > limit:
templates = templates[:limit]
console.print(f"📊 显示前 {limit} 个模板(共 {total_count} 个)")
else:
console.print(f"📊 共 {total_count} 个模板")
if format_output == "json":
# JSON格式输出
templates_data = []
for template in templates:
if isinstance(template, TemplateInfo):
from dataclasses import asdict
templates_data.append(asdict(template))
else:
templates_data.append(template)
console.print(json.dumps(templates_data, ensure_ascii=False, indent=2))
return
# 表格格式输出
table = Table(title="模板列表")
table.add_column("ID", style="cyan", width=12)
table.add_column("名称", style="green")
table.add_column("描述", style="yellow")
table.add_column("时长", style="magenta")
table.add_column("素材数", style="blue")
table.add_column("轨道数", style="red")
table.add_column("创建时间", style="dim")
if verbose:
table.add_column("标签", style="cyan")
table.add_column("资源路径", style="dim")
for template in templates:
# 格式化时长
duration = getattr(template, 'duration', 0)
if duration > 60:
duration_str = f"{duration // 60}m{duration % 60}s"
else:
duration_str = f"{duration}s"
# 格式化创建时间
created_at = getattr(template, 'created_at', '')
if 'T' in created_at:
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
row = [
getattr(template, 'id', '')[:12],
getattr(template, 'name', ''),
getattr(template, 'description', '')[:50] + ('...' if len(getattr(template, 'description', '')) > 50 else ''),
duration_str,
str(getattr(template, 'material_count', 0)),
str(getattr(template, 'track_count', 0)),
created_at
]
if verbose:
tags = getattr(template, 'tags', [])
tags_str = ', '.join(tags) if tags else '-'
resources_path = getattr(template, 'resources_path', '')
row.extend([
tags_str,
resources_path
])
table.add_row(*row)
console.print(table)
except Exception as e:
console.print(f"[red]❌ 获取模板列表失败: {str(e)}[/red]")
raise typer.Exit(1)
@template_app.command("get")
def get_template(
template_id: str = typer.Argument(..., help="模板ID"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="详细输出"),
format_output: str = typer.Option("panel", "--format", "-f", help="输出格式: panel, json")
):
"""获取模板详情"""
try:
console.print(f"🔍 [bold blue]获取模板详情[/bold blue]")
console.print(f"模板ID: {template_id}")
# 创建模板管理器
manager = TemplateManager()
# 获取模板
template = manager.get_template(template_id)
if not template:
console.print(f"[red]❌ 未找到模板: {template_id}[/red]")
raise typer.Exit(1)
if format_output == "json":
# JSON格式输出
if isinstance(template, TemplateInfo):
from dataclasses import asdict
template_data = asdict(template)
else:
template_data = template
console.print(json.dumps(template_data, ensure_ascii=False, indent=2))
return
# 面板格式输出
console.print(f"\n✅ [bold green]模板详情[/bold green]")
# 格式化时长
duration = getattr(template, 'duration', 0)
if duration > 60:
duration_str = f"{duration // 60}{duration % 60}"
else:
duration_str = f"{duration}"
# 基本信息
basic_info = f"""
🆔 模板ID: {getattr(template, 'id', '')}
📝 名称: {getattr(template, 'name', '')}
📄 描述: {getattr(template, 'description', '')}
⏱️ 时长: {duration_str}
📦 素材数: {getattr(template, 'material_count', 0)}
🎬 轨道数: {getattr(template, 'track_count', 0)}
📅 创建时间: {getattr(template, 'created_at', '')}
🔄 更新时间: {getattr(template, 'updated_at', '')}
"""
panel = Panel(basic_info.strip(), title="模板基本信息", border_style="green")
console.print(panel)
if verbose:
# 详细信息
tags = getattr(template, 'tags', [])
tags_str = ', '.join(tags) if tags else ''
detail_info = f"""
🏷️ 标签: {tags_str}
📁 草稿路径: {getattr(template, 'draft_content_path', '')}
📂 资源路径: {getattr(template, 'resources_path', '')}
🖼️ 缩略图: {getattr(template, 'thumbnail_path', '') or ''}
"""
detail_panel = Panel(detail_info.strip(), title="详细信息", border_style="blue")
console.print(detail_panel)
# 画布配置
canvas_config = getattr(template, 'canvas_config', {})
if canvas_config:
canvas_info = f"""
📐 画布配置:
宽度: {canvas_config.get('width', 'Unknown')}
高度: {canvas_config.get('height', 'Unknown')}
帧率: {canvas_config.get('fps', 'Unknown')}
"""
canvas_panel = Panel(canvas_info.strip(), title="画布配置", border_style="yellow")
console.print(canvas_panel)
except Exception as e:
console.print(f"[red]❌ 获取模板详情失败: {str(e)}[/red]")
raise typer.Exit(1)
@template_app.command("detail")
def get_template_detail(
template_id: str = typer.Argument(..., help="模板ID"),
format_output: str = typer.Option("json", "--format", "-f", help="输出格式: json, summary")
):
"""获取模板详细信息(包含轨道和片段)"""
try:
console.print(f"🔍 [bold blue]获取模板详细信息[/bold blue]")
console.print(f"模板ID: {template_id}")
# 创建模板管理器
manager = TemplateManager()
# 获取模板详细信息
detail = manager.get_template_detail(template_id)
if not detail:
console.print(f"[red]❌ 未找到模板详细信息: {template_id}[/red]")
raise typer.Exit(1)
if format_output == "json":
# JSON格式输出
console.print(json.dumps(detail, ensure_ascii=False, indent=2))
return
# 摘要格式输出
console.print(f"\n✅ [bold green]模板详细信息[/bold green]")
# 基本信息
template_info = detail.get('template', {})
console.print(f"📝 名称: {template_info.get('name', 'Unknown')}")
console.print(f"📄 描述: {template_info.get('description', 'Unknown')}")
console.print(f"⏱️ 时长: {template_info.get('duration', 0)}")
# 轨道信息
tracks = detail.get('tracks', [])
console.print(f"\n🎬 [bold green]轨道信息 ({len(tracks)} 个轨道)[/bold green]")
for i, track in enumerate(tracks, 1):
track_type = track.get('type', 'unknown')
segments_count = len(track.get('segments', []))
console.print(f" 轨道 {i}: {track_type} ({segments_count} 个片段)")
# 显示片段信息
for j, segment in enumerate(track.get('segments', []), 1):
segment_name = segment.get('name', f'片段{j}')
start_time = segment.get('start_time', 0)
end_time = segment.get('end_time', 0)
console.print(f" 片段 {j}: {segment_name} ({start_time}s - {end_time}s)")
except Exception as e:
console.print(f"[red]❌ 获取模板详细信息失败: {str(e)}[/red]")
raise typer.Exit(1)
@template_app.command("delete")
def delete_template(
template_id: str = typer.Argument(..., help="模板ID"),
force: bool = typer.Option(False, "--force", "-f", help="强制删除,不询问确认")
):
"""删除模板"""
try:
console.print(f"🗑️ [bold red]删除模板[/bold red]")
console.print(f"模板ID: {template_id}")
# 创建模板管理器
manager = TemplateManager()
# 获取模板信息
template = manager.get_template(template_id)
if not template:
console.print(f"[red]❌ 未找到模板: {template_id}[/red]")
raise typer.Exit(1)
console.print(f"模板名称: {getattr(template, 'name', 'Unknown')}")
console.print(f"模板描述: {getattr(template, 'description', 'Unknown')}")
# 确认删除
if not force:
confirm = typer.confirm("确定要删除这个模板吗?此操作不可恢复。")
if not confirm:
console.print("❌ 操作已取消")
return
# 删除模板
success = manager.delete_template(template_id)
if success:
console.print(f"✅ [bold green]模板删除成功[/bold green]")
else:
console.print(f"[red]❌ 模板删除失败[/red]")
raise typer.Exit(1)
except Exception as e:
console.print(f"[red]❌ 删除模板失败: {str(e)}[/red]")
raise typer.Exit(1)
@template_app.command("stats")
def show_stats():
"""显示模板统计信息"""
try:
console.print(f"📊 [bold blue]模板统计信息[/bold blue]")
# 创建模板管理器
manager = TemplateManager()
# 获取所有模板
templates = manager.get_templates()
if not templates:
console.print("📭 没有模板")
return
# 计算统计信息
total_templates = len(templates)
total_materials = sum(getattr(template, 'material_count', 0) for template in templates)
total_tracks = sum(getattr(template, 'track_count', 0) for template in templates)
total_duration = sum(getattr(template, 'duration', 0) for template in templates)
# 格式化总时长
if total_duration > 3600:
duration_str = f"{total_duration // 3600}小时{(total_duration % 3600) // 60}{total_duration % 60}"
elif total_duration > 60:
duration_str = f"{total_duration // 60}{total_duration % 60}"
else:
duration_str = f"{total_duration}"
# 创建统计面板
stats_content = f"""
📈 [bold green]总体统计[/bold green]
模板总数: {total_templates}
素材总数: {total_materials}
轨道总数: {total_tracks}
总时长: {duration_str}
平均时长: {total_duration // total_templates if total_templates > 0 else 0}
"""
stats_panel = Panel(stats_content.strip(), title="模板统计", border_style="green")
console.print(stats_panel)
# 显示最近的模板
recent_templates = sorted(templates, key=lambda x: getattr(x, 'created_at', ''), reverse=True)[:5]
if recent_templates:
console.print(f"\n🕒 [bold green]最近创建的模板[/bold green]")
recent_table = Table()
recent_table.add_column("名称", style="green")
recent_table.add_column("时长", style="magenta")
recent_table.add_column("创建时间", style="dim")
for template in recent_templates:
duration = getattr(template, 'duration', 0)
duration_str = f"{duration}s" if duration < 60 else f"{duration // 60}m{duration % 60}s"
created_at = getattr(template, 'created_at', '')
if 'T' in created_at:
created_at = created_at.split('T')[0] + ' ' + created_at.split('T')[1][:8]
recent_table.add_row(
getattr(template, 'name', 'Unknown'),
duration_str,
created_at
)
console.print(recent_table)
except Exception as e:
console.print(f"[red]❌ 获取统计信息失败: {str(e)}[/red]")
raise typer.Exit(1)
if __name__ == "__main__":
template_app()