fix
This commit is contained in:
504
examples/typer_media_manager.py
Normal file
504
examples/typer_media_manager.py
Normal file
@@ -0,0 +1,504 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
基于Typer开发规范的媒体管理器实现示例
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
from enum import Enum
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
try:
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.panel import Panel
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
except ImportError:
|
||||
print("❌ 需要安装 typer 和 rich: pip install typer rich")
|
||||
sys.exit(1)
|
||||
|
||||
from python_core.utils.progress import ProgressJSONRPCCommander
|
||||
from python_core.services.base import ProgressServiceBase
|
||||
|
||||
console = Console()
|
||||
|
||||
# 1. 类型定义(遵循规范)
|
||||
class OutputFormat(str, Enum):
|
||||
"""输出格式枚举"""
|
||||
JSON = "json"
|
||||
CSV = "csv"
|
||||
TXT = "txt"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
class ProcessingMode(str, Enum):
|
||||
"""处理模式枚举"""
|
||||
FAST = "fast"
|
||||
BALANCED = "balanced"
|
||||
QUALITY = "quality"
|
||||
|
||||
# 2. 服务类(遵循规范)
|
||||
class MediaManagerService(ProgressServiceBase):
|
||||
"""媒体管理服务"""
|
||||
|
||||
def get_service_name(self) -> str:
|
||||
return "media_manager"
|
||||
|
||||
def upload_video(self, video_path: Path, tags: List[str] = None, filename: str = None) -> dict:
|
||||
"""上传单个视频"""
|
||||
if tags is None:
|
||||
tags = []
|
||||
|
||||
# 模拟上传过程
|
||||
steps = [
|
||||
"计算文件哈希...",
|
||||
"检查重复文件...",
|
||||
"复制文件到存储...",
|
||||
"提取视频信息...",
|
||||
"检测场景变化..."
|
||||
]
|
||||
|
||||
for step in steps:
|
||||
self.report_progress(step)
|
||||
time.sleep(0.3) # 模拟处理时间
|
||||
|
||||
result = {
|
||||
"video_path": str(video_path),
|
||||
"filename": filename or video_path.name,
|
||||
"tags": tags,
|
||||
"success": True,
|
||||
"scenes_detected": 3,
|
||||
"duration": 120.5
|
||||
}
|
||||
|
||||
# 保存结果到存储
|
||||
self.save_data("uploads", f"upload_{int(time.time())}", result)
|
||||
|
||||
return result
|
||||
|
||||
def batch_upload(self, directory: Path, tags: List[str] = None, recursive: bool = False) -> dict:
|
||||
"""批量上传视频"""
|
||||
if tags is None:
|
||||
tags = []
|
||||
|
||||
# 扫描视频文件
|
||||
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.wmv'}
|
||||
video_files = []
|
||||
|
||||
if recursive:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(directory.rglob(f"*{ext}"))
|
||||
else:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(directory.glob(f"*{ext}"))
|
||||
|
||||
results = []
|
||||
for i, video_file in enumerate(video_files):
|
||||
self.report_progress(f"处理文件: {video_file.name} ({i+1}/{len(video_files)})")
|
||||
|
||||
try:
|
||||
result = self.upload_video(video_file, tags)
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"video_path": str(video_file),
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
batch_result = {
|
||||
"total_files": len(video_files),
|
||||
"successful": len([r for r in results if r.get("success")]),
|
||||
"failed": len([r for r in results if not r.get("success")]),
|
||||
"results": results
|
||||
}
|
||||
|
||||
# 保存批量结果
|
||||
self.save_data("batch_uploads", f"batch_{int(time.time())}", batch_result)
|
||||
|
||||
return batch_result
|
||||
|
||||
def get_recent_uploads(self, limit: int = 10) -> List[dict]:
|
||||
"""获取最近的上传记录"""
|
||||
keys = self.list_keys("uploads")
|
||||
recent_keys = sorted(keys)[-limit:]
|
||||
return list(self.load_batch_data("uploads", recent_keys).values())
|
||||
|
||||
# 3. Typer命令行接口(遵循规范)
|
||||
class MediaManagerCommander(ProgressJSONRPCCommander):
|
||||
"""基于Typer的媒体管理器命令行接口"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("media_manager")
|
||||
self.app = typer.Typer(
|
||||
name="media_manager",
|
||||
help="""
|
||||
🎬 媒体管理器
|
||||
|
||||
功能强大的视频处理和管理工具,支持:
|
||||
• 视频上传和元数据提取
|
||||
• 自动场景检测和分割
|
||||
• 批量处理和进度跟踪
|
||||
• 多种输出格式
|
||||
|
||||
使用 --help 查看具体命令帮助。
|
||||
""",
|
||||
rich_markup_mode="rich",
|
||||
no_args_is_help=True
|
||||
)
|
||||
self.service = MediaManagerService()
|
||||
self._setup_commands()
|
||||
|
||||
def _register_commands(self):
|
||||
"""注册命令(继承要求)"""
|
||||
pass # Typer通过装饰器自动注册
|
||||
|
||||
def _is_progressive_command(self, command: str) -> bool:
|
||||
"""判断是否需要进度报告"""
|
||||
return command in ["batch_upload", "analyze"]
|
||||
|
||||
def _execute_with_progress(self, command: str, args: dict):
|
||||
"""执行带进度的命令"""
|
||||
pass # 通过Typer命令直接处理
|
||||
|
||||
def _execute_simple_command(self, command: str, args: dict):
|
||||
"""执行简单命令"""
|
||||
pass # 通过Typer命令直接处理
|
||||
|
||||
def _setup_commands(self):
|
||||
"""设置Typer命令"""
|
||||
|
||||
@self.app.command()
|
||||
def upload(
|
||||
video_path: Path = typer.Argument(
|
||||
...,
|
||||
help="📹 视频文件路径",
|
||||
exists=True
|
||||
),
|
||||
tags: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tags", "-t",
|
||||
help="🏷️ 标签列表(逗号分隔)"
|
||||
),
|
||||
filename: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--filename", "-f",
|
||||
help="📝 自定义文件名"
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose", "-v",
|
||||
help="📝 详细输出"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📤 上传视频文件
|
||||
|
||||
上传单个视频文件到媒体库,自动进行场景检测和元数据提取。
|
||||
|
||||
示例:
|
||||
media_manager upload video.mp4 --tags "demo,test"
|
||||
media_manager upload /path/to/video.mp4 --filename "my_video"
|
||||
|
||||
注意:
|
||||
- 支持的格式: MP4, AVI, MOV, MKV, WMV
|
||||
- 自动检测重复文件
|
||||
- 自动提取视频元数据
|
||||
"""
|
||||
# 参数验证
|
||||
if not video_path.exists():
|
||||
console.print("❌ [red]视频文件不存在[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# 解析标签
|
||||
tag_list = []
|
||||
if tags:
|
||||
tag_list = [tag.strip() for tag in tags.split(",")]
|
||||
|
||||
# 显示开始信息
|
||||
console.print(f"🚀 开始上传: [bold blue]{video_path}[/bold blue]")
|
||||
|
||||
try:
|
||||
# 设置进度回调
|
||||
def progress_callback(message: str):
|
||||
console.print(f"📊 {message}")
|
||||
|
||||
self.service.set_progress_callback(progress_callback)
|
||||
|
||||
# 执行上传
|
||||
result = self.service.upload_video(video_path, tag_list, filename)
|
||||
|
||||
# 显示结果
|
||||
console.print("✅ [bold green]上传完成[/bold green]")
|
||||
|
||||
if verbose or True: # 总是显示基本信息
|
||||
self._show_upload_result(result)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]上传失败: {e}[/red]")
|
||||
if verbose:
|
||||
console.print_exception()
|
||||
raise typer.Exit(1)
|
||||
|
||||
@self.app.command()
|
||||
def batch_upload(
|
||||
input_directory: Path = typer.Argument(
|
||||
...,
|
||||
help="📁 输入目录路径",
|
||||
exists=True,
|
||||
file_okay=False
|
||||
),
|
||||
output_path: Optional[Path] = typer.Option(
|
||||
None,
|
||||
"--output", "-o",
|
||||
help="📄 结果输出文件路径"
|
||||
),
|
||||
tags: Optional[str] = typer.Option(
|
||||
None,
|
||||
"--tags", "-t",
|
||||
help="🏷️ 标签列表(逗号分隔)"
|
||||
),
|
||||
recursive: bool = typer.Option(
|
||||
False,
|
||||
"--recursive", "-r",
|
||||
help="🔄 递归处理子目录"
|
||||
),
|
||||
output_format: OutputFormat = typer.Option(
|
||||
OutputFormat.JSON,
|
||||
"--format", "-f",
|
||||
help="📄 输出格式"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📦 批量上传视频文件(带进度条)
|
||||
|
||||
批量处理目录中的所有视频文件,支持递归扫描和进度跟踪。
|
||||
|
||||
示例:
|
||||
media_manager batch-upload /videos --tags "batch,demo"
|
||||
media_manager batch-upload /videos -r --output results.json
|
||||
"""
|
||||
console.print(f"📦 批量上传目录: [bold blue]{input_directory}[/bold blue]")
|
||||
|
||||
# 解析标签
|
||||
tag_list = []
|
||||
if tags:
|
||||
tag_list = [tag.strip() for tag in tags.split(",")]
|
||||
|
||||
# 扫描文件数量
|
||||
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.wmv'}
|
||||
video_files = []
|
||||
|
||||
if recursive:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(input_directory.rglob(f"*{ext}"))
|
||||
else:
|
||||
for ext in video_extensions:
|
||||
video_files.extend(input_directory.glob(f"*{ext}"))
|
||||
|
||||
if not video_files:
|
||||
console.print("⚠️ [yellow]未找到可处理的视频文件[/yellow]")
|
||||
return
|
||||
|
||||
console.print(f"📋 找到 {len(video_files)} 个视频文件")
|
||||
|
||||
# 使用进度任务
|
||||
with self.create_task("批量上传", len(video_files)) as task:
|
||||
def progress_callback(message: str):
|
||||
# 从消息中提取文件信息更新任务
|
||||
if "处理文件:" in message:
|
||||
task.update(message=message)
|
||||
|
||||
self.service.set_progress_callback(progress_callback)
|
||||
|
||||
# 执行批量上传
|
||||
result = self.service.batch_upload(input_directory, tag_list, recursive)
|
||||
|
||||
task.finish(f"批量上传完成: {result['successful']}/{result['total_files']} 成功")
|
||||
|
||||
# 显示结果摘要
|
||||
self._show_batch_result(result)
|
||||
|
||||
# 保存结果文件
|
||||
if output_path:
|
||||
self._save_batch_results(result, output_path, output_format)
|
||||
console.print(f"📄 结果已保存到: {output_path}")
|
||||
|
||||
@self.app.command()
|
||||
def list_uploads(
|
||||
limit: int = typer.Option(
|
||||
10,
|
||||
"--limit", "-l",
|
||||
min=1,
|
||||
max=100,
|
||||
help="📊 显示数量限制"
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose", "-v",
|
||||
help="📝 详细信息"
|
||||
)
|
||||
):
|
||||
"""
|
||||
📋 列出最近的上传记录
|
||||
|
||||
显示最近上传的视频文件信息和统计数据。
|
||||
"""
|
||||
console.print("📋 最近的上传记录")
|
||||
|
||||
try:
|
||||
uploads = self.service.get_recent_uploads(limit)
|
||||
|
||||
if not uploads:
|
||||
console.print("⚠️ [yellow]暂无上传记录[/yellow]")
|
||||
return
|
||||
|
||||
self._show_uploads_table(uploads, verbose)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]获取记录失败: {e}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
@self.app.command()
|
||||
def status():
|
||||
"""
|
||||
📊 显示系统状态
|
||||
|
||||
显示媒体管理器的当前状态和统计信息。
|
||||
"""
|
||||
console.print("📊 媒体管理器状态")
|
||||
|
||||
try:
|
||||
# 获取统计信息
|
||||
uploads_stats = self.service.get_collection_stats("uploads")
|
||||
batch_stats = self.service.get_collection_stats("batch_uploads")
|
||||
|
||||
# 创建状态表格
|
||||
table = Table(title="系统状态")
|
||||
table.add_column("组件", style="cyan")
|
||||
table.add_column("状态", style="green")
|
||||
table.add_column("详情", style="yellow")
|
||||
|
||||
table.add_row("存储", "✅ 正常", f"JSON文件存储")
|
||||
table.add_row("上传记录", "✅ 活跃", f"{uploads_stats.get('file_count', 0)} 条记录")
|
||||
table.add_row("批量任务", "✅ 就绪", f"{batch_stats.get('file_count', 0)} 个任务")
|
||||
table.add_row("进度系统", "✅ 就绪", "JSON-RPC协议")
|
||||
|
||||
console.print(table)
|
||||
|
||||
# 创建信息面板
|
||||
info_panel = Panel(
|
||||
"[bold blue]媒体管理器运行正常[/bold blue]\n"
|
||||
"所有组件状态良好,可以开始处理视频文件。",
|
||||
title="📊 状态摘要",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(info_panel)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"❌ [red]获取状态失败: {e}[/red]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
def _show_upload_result(self, result: dict):
|
||||
"""显示上传结果"""
|
||||
table = Table(title="📤 上传结果")
|
||||
table.add_column("项目", style="cyan")
|
||||
table.add_column("值", style="green")
|
||||
|
||||
table.add_row("文件名", result["filename"])
|
||||
table.add_row("标签", ", ".join(result["tags"]) if result["tags"] else "无")
|
||||
table.add_row("检测场景", str(result["scenes_detected"]))
|
||||
table.add_row("视频时长", f"{result['duration']:.1f}秒")
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _show_batch_result(self, result: dict):
|
||||
"""显示批量结果摘要"""
|
||||
panel = Panel(
|
||||
f"[bold green]总文件: {result['total_files']}[/bold green]\n"
|
||||
f"[bold blue]成功: {result['successful']}[/bold blue]\n"
|
||||
f"[bold red]失败: {result['failed']}[/bold red]",
|
||||
title="📦 批量上传摘要",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
def _show_uploads_table(self, uploads: List[dict], verbose: bool):
|
||||
"""显示上传记录表格"""
|
||||
table = Table(title="📋 上传记录")
|
||||
table.add_column("文件名", style="cyan")
|
||||
table.add_column("场景数", style="green")
|
||||
table.add_column("时长", style="yellow")
|
||||
|
||||
if verbose:
|
||||
table.add_column("标签", style="magenta")
|
||||
|
||||
for upload in uploads:
|
||||
row = [
|
||||
upload["filename"],
|
||||
str(upload.get("scenes_detected", "N/A")),
|
||||
f"{upload.get('duration', 0):.1f}s"
|
||||
]
|
||||
|
||||
if verbose:
|
||||
tags = ", ".join(upload.get("tags", [])) or "无"
|
||||
row.append(tags)
|
||||
|
||||
table.add_row(*row)
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _save_batch_results(self, result: dict, output_path: Path, format: OutputFormat):
|
||||
"""保存批量结果"""
|
||||
if format == OutputFormat.JSON:
|
||||
import json
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, ensure_ascii=False)
|
||||
elif format == OutputFormat.CSV:
|
||||
import csv
|
||||
with open(output_path, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['filename', 'success', 'scenes', 'duration', 'error'])
|
||||
for item in result['results']:
|
||||
writer.writerow([
|
||||
item.get('filename', ''),
|
||||
item.get('success', False),
|
||||
item.get('scenes_detected', ''),
|
||||
item.get('duration', ''),
|
||||
item.get('error', '')
|
||||
])
|
||||
else: # TXT
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"批量上传结果\n")
|
||||
f.write(f"总文件数: {result['total_files']}\n")
|
||||
f.write(f"成功: {result['successful']}\n")
|
||||
f.write(f"失败: {result['failed']}\n\n")
|
||||
|
||||
for item in result['results']:
|
||||
f.write(f"文件: {item.get('filename', '')}\n")
|
||||
f.write(f" 状态: {'成功' if item.get('success') else '失败'}\n")
|
||||
if item.get('success'):
|
||||
f.write(f" 场景数: {item.get('scenes_detected', 'N/A')}\n")
|
||||
f.write(f" 时长: {item.get('duration', 0):.1f}秒\n")
|
||||
else:
|
||||
f.write(f" 错误: {item.get('error', '')}\n")
|
||||
f.write("\n")
|
||||
|
||||
def run(self):
|
||||
"""运行CLI"""
|
||||
self.app()
|
||||
|
||||
def main():
|
||||
"""主入口函数"""
|
||||
commander = MediaManagerCommander()
|
||||
commander.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user