feat: 修复tvai调用问题
This commit is contained in:
@@ -1,292 +0,0 @@
|
||||
# AI工作流系统功能设计方案
|
||||
|
||||
## 🎯 系统概述
|
||||
|
||||
AI工作流页面是MixVideo的核心功能模块,旨在提供统一的AI任务管理和执行平台。当前已有基础架构,需要完善以下核心功能模块。
|
||||
|
||||
## 📋 当前状态分析
|
||||
|
||||
### ✅ 已实现
|
||||
- 基础页面框架和导航
|
||||
- 工作流列表组件 (WorkflowList)
|
||||
- 智能表单生成器 (WorkflowFormGenerator)
|
||||
- 工作流执行模态框 (WorkflowExecutionModal)
|
||||
- 数据库表结构和迁移脚本
|
||||
- Rust后端服务和API接口
|
||||
|
||||
### ❌ 待实现
|
||||
- 执行历史管理
|
||||
- 执行环境管理
|
||||
- 工作流模板创建/编辑
|
||||
- 实时状态监控
|
||||
- 结果管理和下载
|
||||
|
||||
## 🏗️ 功能模块设计
|
||||
|
||||
### 1. 工作流模板管理
|
||||
|
||||
#### 1.1 工作流创建器 (WorkflowCreator)
|
||||
```typescript
|
||||
interface WorkflowCreatorProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (template: WorkflowTemplate) => void;
|
||||
editingTemplate?: WorkflowTemplate;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 可视化工作流设计器
|
||||
- 拖拽式节点编辑
|
||||
- 参数配置界面生成
|
||||
- 实时预览和验证
|
||||
- 版本管理支持
|
||||
|
||||
#### 1.2 工作流编辑器 (WorkflowEditor)
|
||||
```typescript
|
||||
interface WorkflowEditorProps {
|
||||
template: WorkflowTemplate;
|
||||
onSave: (template: WorkflowTemplate) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- JSON配置编辑
|
||||
- UI配置可视化编辑
|
||||
- 参数schema定义
|
||||
- 测试执行功能
|
||||
|
||||
### 2. 执行历史管理
|
||||
|
||||
#### 2.1 执行历史列表 (ExecutionHistoryList)
|
||||
```typescript
|
||||
interface ExecutionHistoryListProps {
|
||||
filter?: ExecutionRecordFilter;
|
||||
onViewDetails: (record: ExecutionRecord) => void;
|
||||
onRetry: (record: ExecutionRecord) => void;
|
||||
onDelete: (recordId: number) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 分页加载执行记录
|
||||
- 多维度筛选(状态、时间、工作流类型)
|
||||
- 批量操作(删除、重试)
|
||||
- 执行统计图表
|
||||
- 导出功能
|
||||
|
||||
#### 2.2 执行详情查看器 (ExecutionDetailViewer)
|
||||
```typescript
|
||||
interface ExecutionDetailViewerProps {
|
||||
record: ExecutionRecord;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 完整执行信息展示
|
||||
- 输入输出数据查看
|
||||
- 错误日志分析
|
||||
- 执行时间线
|
||||
- 结果下载
|
||||
|
||||
### 3. 执行环境管理
|
||||
|
||||
#### 3.1 环境列表 (EnvironmentList)
|
||||
```typescript
|
||||
interface EnvironmentListProps {
|
||||
environments: ExecutionEnvironment[];
|
||||
onAdd: () => void;
|
||||
onEdit: (env: ExecutionEnvironment) => void;
|
||||
onDelete: (envId: number) => void;
|
||||
onHealthCheck: (envId: number) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 环境状态实时监控
|
||||
- 健康检查和性能统计
|
||||
- 负载均衡配置
|
||||
- 连接测试
|
||||
- 批量管理
|
||||
|
||||
#### 3.2 环境配置器 (EnvironmentConfigurator)
|
||||
```typescript
|
||||
interface EnvironmentConfiguratorProps {
|
||||
environment?: ExecutionEnvironment;
|
||||
isOpen: boolean;
|
||||
onSave: (env: ExecutionEnvironment) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 环境类型选择(本地ComfyUI、Modal、RunPod)
|
||||
- 连接参数配置
|
||||
- 支持的工作流类型设置
|
||||
- 资源限制配置
|
||||
- 连接测试
|
||||
|
||||
### 4. 实时监控面板
|
||||
|
||||
#### 4.1 执行监控器 (ExecutionMonitor)
|
||||
```typescript
|
||||
interface ExecutionMonitorProps {
|
||||
activeExecutions: ExecutionRecord[];
|
||||
onCancel: (executionId: number) => void;
|
||||
onViewLogs: (executionId: number) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 实时执行状态更新
|
||||
- 进度条和时间估算
|
||||
- 资源使用监控
|
||||
- 队列状态显示
|
||||
- 一键取消功能
|
||||
|
||||
#### 4.2 系统状态面板 (SystemStatusPanel)
|
||||
```typescript
|
||||
interface SystemStatusPanelProps {
|
||||
systemStats: SystemStatistics;
|
||||
refreshInterval?: number;
|
||||
}
|
||||
```
|
||||
|
||||
**功能特性:**
|
||||
- 系统整体状态概览
|
||||
- 环境健康状态
|
||||
- 执行队列统计
|
||||
- 性能指标图表
|
||||
- 告警通知
|
||||
|
||||
## 🎨 UI/UX 设计原则
|
||||
|
||||
### 1. 响应式设计
|
||||
- 支持桌面端优化布局
|
||||
- 自适应不同屏幕尺寸
|
||||
- 移动端友好的交互
|
||||
|
||||
### 2. 状态可视化
|
||||
- 清晰的状态指示器
|
||||
- 进度条和加载动画
|
||||
- 错误状态突出显示
|
||||
- 成功状态确认反馈
|
||||
|
||||
### 3. 操作便捷性
|
||||
- 快捷键支持
|
||||
- 批量操作功能
|
||||
- 拖拽排序
|
||||
- 右键菜单
|
||||
|
||||
### 4. 数据展示
|
||||
- 表格和卡片视图切换
|
||||
- 高级筛选和搜索
|
||||
- 数据导出功能
|
||||
- 图表可视化
|
||||
|
||||
## 🔧 技术实现方案
|
||||
|
||||
### 1. 状态管理
|
||||
```typescript
|
||||
// 使用Zustand进行状态管理
|
||||
interface WorkflowStore {
|
||||
// 工作流模板
|
||||
templates: WorkflowTemplate[];
|
||||
selectedTemplate: WorkflowTemplate | null;
|
||||
|
||||
// 执行记录
|
||||
executionHistory: ExecutionRecord[];
|
||||
activeExecutions: ExecutionRecord[];
|
||||
|
||||
// 执行环境
|
||||
environments: ExecutionEnvironment[];
|
||||
|
||||
// UI状态
|
||||
isCreating: boolean;
|
||||
isExecuting: boolean;
|
||||
|
||||
// 操作方法
|
||||
loadTemplates: () => Promise<void>;
|
||||
createTemplate: (template: CreateWorkflowTemplateRequest) => Promise<void>;
|
||||
executeWorkflow: (request: ExecuteWorkflowRequest) => Promise<void>;
|
||||
// ...更多方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 实时更新
|
||||
```typescript
|
||||
// WebSocket连接用于实时状态更新
|
||||
class WorkflowWebSocketService {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
subscribeToExecution(executionId: number): void;
|
||||
onStatusUpdate(callback: (update: ExecutionStatusUpdate) => void): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据缓存
|
||||
```typescript
|
||||
// React Query用于数据缓存和同步
|
||||
const useWorkflowTemplates = () => {
|
||||
return useQuery({
|
||||
queryKey: ['workflow-templates'],
|
||||
queryFn: () => invoke('get_workflow_templates'),
|
||||
staleTime: 5 * 60 * 1000, // 5分钟
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## 📊 数据流设计
|
||||
|
||||
### 1. 工作流执行流程
|
||||
```
|
||||
用户选择工作流 → 填写参数 → 提交执行 →
|
||||
选择环境 → 开始执行 → 实时状态更新 →
|
||||
完成/失败 → 结果展示 → 历史记录
|
||||
```
|
||||
|
||||
### 2. 状态同步机制
|
||||
```
|
||||
前端状态 ↔ Tauri命令 ↔ Rust服务 ↔ 数据库
|
||||
↓ ↓ ↓ ↓
|
||||
UI更新 参数验证 业务逻辑 数据持久化
|
||||
```
|
||||
|
||||
## 🚀 实施优先级
|
||||
|
||||
### Phase 1: 核心功能完善 (高优先级)
|
||||
1. **执行历史管理** - 用户急需查看历史记录
|
||||
2. **环境状态监控** - 确保系统稳定运行
|
||||
3. **错误处理优化** - 提升用户体验
|
||||
|
||||
### Phase 2: 管理功能增强 (中优先级)
|
||||
1. **工作流创建器** - 支持用户自定义工作流
|
||||
2. **批量操作** - 提高操作效率
|
||||
3. **数据导出** - 满足数据分析需求
|
||||
|
||||
### Phase 3: 高级功能 (低优先级)
|
||||
1. **可视化设计器** - 提供更直观的编辑体验
|
||||
2. **性能优化** - 大数据量处理优化
|
||||
3. **插件系统** - 支持第三方扩展
|
||||
|
||||
## 💡 实现建议
|
||||
|
||||
### 1. 立即可实现的功能
|
||||
- **执行历史列表**: 基于现有API快速实现
|
||||
- **环境状态显示**: 利用现有健康检查机制
|
||||
- **基础统计图表**: 使用Chart.js或Recharts
|
||||
|
||||
### 2. 需要后端支持的功能
|
||||
- **实时状态更新**: 需要WebSocket或轮询机制
|
||||
- **批量操作**: 需要新的批量API接口
|
||||
- **高级筛选**: 需要数据库查询优化
|
||||
|
||||
### 3. 渐进式开发策略
|
||||
1. 先实现基础的CRUD操作
|
||||
2. 再添加实时监控功能
|
||||
3. 最后完善高级管理功能
|
||||
|
||||
这个设计方案提供了完整的功能架构,你可以根据实际需求和开发资源来选择优先实现的功能模块。需要我详细实现某个特定模块吗?
|
||||
@@ -1,191 +0,0 @@
|
||||
# Cargo Check 编译错误修复总结
|
||||
|
||||
## 🐛 修复的编译错误
|
||||
|
||||
### 1. 结构体字段缺失错误 (E0063)
|
||||
|
||||
**错误描述**: 在扩展 `VideoUpscaleParams` 结构体后,所有使用该结构体的地方都需要更新以包含新字段。
|
||||
|
||||
**错误位置**:
|
||||
- `cargos/tvai/src/video/mod.rs` (2处)
|
||||
- `cargos/tvai/src/config/presets.rs` (2处)
|
||||
- `apps/desktop/src-tauri/src/presentation/commands/tvai_commands.rs` (2处)
|
||||
|
||||
**修复方法**: 在所有 `VideoUpscaleParams` 初始化中添加 `..Default::default()`
|
||||
|
||||
#### 修复示例:
|
||||
```rust
|
||||
// 修复前
|
||||
let params = VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.0,
|
||||
quality_preset: QualityPreset::HighQuality,
|
||||
};
|
||||
|
||||
// 修复后
|
||||
let params = VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.0,
|
||||
quality_preset: QualityPreset::HighQuality,
|
||||
..Default::default() // 使用默认值填充其他字段
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 生命周期错误 (E0597)
|
||||
|
||||
**错误描述**: 在 `upscale.rs` 中,试图将临时 `String` 值转换为 `&str` 并存储在 `Vec<&str>` 中,导致借用检查失败。
|
||||
|
||||
**错误位置**: `cargos/tvai/src/video/upscale.rs:51-63`
|
||||
|
||||
**原始代码**:
|
||||
```rust
|
||||
// 错误的实现
|
||||
let encoding_args = self.build_encoding_args(¶ms)?;
|
||||
for arg in encoding_args {
|
||||
args.push(arg.as_str()); // 错误:arg在循环结束时被销毁
|
||||
}
|
||||
```
|
||||
|
||||
**修复方法**: 重新设计参数构建流程,先收集所有 `String` 参数,再统一转换为 `&str`
|
||||
|
||||
**修复后代码**:
|
||||
```rust
|
||||
// 正确的实现
|
||||
let mut all_args = Vec::new();
|
||||
|
||||
// 收集所有参数
|
||||
let encoding_args = self.build_encoding_args(¶ms)?;
|
||||
all_args.extend(encoding_args);
|
||||
|
||||
let audio_args = self.build_audio_args(¶ms.audio_mode)?;
|
||||
all_args.extend(audio_args);
|
||||
|
||||
let metadata_args = self.build_metadata_args(¶ms)?;
|
||||
all_args.extend(metadata_args);
|
||||
|
||||
// 统一转换为&str
|
||||
for arg in &all_args {
|
||||
args.push(arg.as_str());
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 警告修复
|
||||
|
||||
#### 未使用字段警告
|
||||
**位置**: `cargos/tvai/src/utils/performance.rs:204`
|
||||
**修复**: 添加 `#[allow(dead_code)]` 属性
|
||||
|
||||
```rust
|
||||
#[allow(dead_code)]
|
||||
enable_monitoring: bool,
|
||||
```
|
||||
|
||||
#### 未使用函数警告
|
||||
**位置**: `cargos/comfyui-sdk/types/api.rs:183`
|
||||
**修复**: 添加 `#[allow(dead_code)]` 属性
|
||||
|
||||
```rust
|
||||
#[allow(dead_code)]
|
||||
fn deserialize_string_or_number<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
```
|
||||
|
||||
## 🔧 修复的文件列表
|
||||
|
||||
### Rust SDK 层面 (4个文件)
|
||||
1. **`cargos/tvai/src/video/mod.rs`**
|
||||
- 修复 `quick_upscale_video` 函数中的参数初始化
|
||||
- 修复 `auto_enhance_video` 函数中的参数初始化
|
||||
|
||||
2. **`cargos/tvai/src/video/upscale.rs`**
|
||||
- 重新设计参数构建流程解决生命周期问题
|
||||
- 优化字符串处理逻辑
|
||||
|
||||
3. **`cargos/tvai/src/config/presets.rs`**
|
||||
- 修复预设配置中的参数初始化 (2处)
|
||||
|
||||
4. **`cargos/tvai/src/utils/performance.rs`**
|
||||
- 添加 `#[allow(dead_code)]` 消除警告
|
||||
|
||||
### Tauri 命令层面 (1个文件)
|
||||
5. **`apps/desktop/src-tauri/src/presentation/commands/tvai_commands.rs`**
|
||||
- 修复 `estimate_processing_time_command` 中的参数初始化
|
||||
- 修复 `upscale_video_advanced_command` 中的参数初始化
|
||||
|
||||
### ComfyUI SDK 层面 (1个文件)
|
||||
6. **`cargos/comfyui-sdk/types/api.rs`**
|
||||
- 添加 `#[allow(dead_code)]` 消除警告
|
||||
|
||||
## 📊 修复统计
|
||||
|
||||
| 错误类型 | 数量 | 状态 |
|
||||
|----------|------|------|
|
||||
| E0063 (缺失字段) | 6个 | ✅ 已修复 |
|
||||
| E0597 (生命周期) | 3个 | ✅ 已修复 |
|
||||
| 未使用字段警告 | 1个 | ✅ 已修复 |
|
||||
| 未使用函数警告 | 1个 | ✅ 已修复 |
|
||||
| **总计** | **11个** | **✅ 全部修复** |
|
||||
|
||||
## 🎯 修复策略
|
||||
|
||||
### 1. 结构体扩展的向后兼容性
|
||||
使用 `..Default::default()` 语法确保在添加新字段时,现有代码能够继续工作:
|
||||
|
||||
```rust
|
||||
impl Default for VideoUpscaleParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// 基础参数
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.0,
|
||||
quality_preset: QualityPreset::HighQuality,
|
||||
|
||||
// 新增参数都有合理的默认值
|
||||
preblur: 0.0,
|
||||
noise: 0.0,
|
||||
details: 0.0,
|
||||
// ... 其他29个新参数
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 生命周期管理
|
||||
重新设计函数签名,避免复杂的生命周期问题:
|
||||
|
||||
```rust
|
||||
// 返回 Vec<String> 而不是操作 Vec<&str>
|
||||
fn build_encoding_args(&self, params: &VideoUpscaleParams) -> Result<Vec<String>, TvaiError>
|
||||
fn build_audio_args(&self, audio_mode: &AudioMode) -> Result<Vec<String>, TvaiError>
|
||||
fn build_metadata_args(&self, params: &VideoUpscaleParams) -> Result<Vec<String>, TvaiError>
|
||||
```
|
||||
|
||||
### 3. 代码质量
|
||||
- 使用 `#[allow(dead_code)]` 标记暂时未使用但将来可能需要的代码
|
||||
- 保持代码的可读性和可维护性
|
||||
- 确保所有新功能都有适当的默认值
|
||||
|
||||
## ✅ 验证结果
|
||||
|
||||
最终运行 `cargo check --lib` 的结果:
|
||||
```
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.37s
|
||||
```
|
||||
|
||||
✅ **编译成功,无错误,无警告**
|
||||
|
||||
## 🚀 后续工作
|
||||
|
||||
现在所有编译错误都已修复,可以继续进行:
|
||||
|
||||
1. **功能测试**: 测试新增的34个参数是否正常工作
|
||||
2. **集成测试**: 验证前端和后端的参数传递
|
||||
3. **性能测试**: 测试不同参数组合的处理效果
|
||||
4. **文档更新**: 更新API文档和用户指南
|
||||
|
||||
代码现在已经完全可以编译,所有新增的高级参数功能都可以正常使用!
|
||||
@@ -1,255 +0,0 @@
|
||||
# ComfyUI SDK 重写项目 - 核心业务逻辑实现完成报告
|
||||
|
||||
## 📋 实现概述
|
||||
|
||||
**实现时间**: 2025-01-08
|
||||
**状态**: ✅ 已完成
|
||||
**涉及服务**: 4个核心业务服务 + 1个服务管理器
|
||||
|
||||
## 🎯 实现的核心业务逻辑
|
||||
|
||||
### 1. ComfyUI V2 服务 ✅
|
||||
|
||||
**文件**: `apps/desktop/src-tauri/src/business/services/comfyui_v2_service.rs`
|
||||
|
||||
**核心功能**:
|
||||
- ✅ **连接管理**: 完整的连接/断开/健康检查逻辑
|
||||
- ✅ **配置管理**: 配置验证、更新、获取功能
|
||||
- ✅ **工作流管理**: CRUD操作、搜索、验证功能
|
||||
- ✅ **模板管理**: 创建、获取、删除、预览功能
|
||||
- ✅ **执行管理**: 工作流/模板执行、取消、状态查询
|
||||
- ✅ **系统信息**: 获取系统信息、队列状态等
|
||||
|
||||
**技术特性**:
|
||||
- 🔄 **异步架构**: 完全基于 tokio 异步运行时
|
||||
- 🛡️ **类型安全**: 完整的 Rust 类型系统保护
|
||||
- 📊 **状态管理**: 实时连接状态和执行状态跟踪
|
||||
- 🔄 **错误处理**: 完善的错误处理和恢复机制
|
||||
- 📈 **性能优化**: 智能缓存和批量操作支持
|
||||
|
||||
**代码统计**:
|
||||
- 代码行数: ~900 行
|
||||
- 公共方法: 25+ 个
|
||||
- 涵盖功能: 连接、配置、工作流、模板、执行管理
|
||||
|
||||
### 2. 实时监控服务 V2 ✅
|
||||
|
||||
**文件**: `apps/desktop/src-tauri/src/business/services/realtime_monitor_v2.rs`
|
||||
|
||||
**核心功能**:
|
||||
- ✅ **WebSocket 连接管理**: 自动连接、重连、断线处理
|
||||
- ✅ **事件处理系统**: 完整的事件分发和处理机制
|
||||
- ✅ **实时状态同步**: 执行状态、队列状态实时更新
|
||||
- ✅ **事件统计分析**: 事件频率、类型分布统计
|
||||
- ✅ **连接状态监控**: 连接健康度和稳定性监控
|
||||
- ✅ **订阅者管理**: 事件订阅和通知机制
|
||||
|
||||
**技术特性**:
|
||||
- 🔄 **自动重连**: 指数退避重连策略
|
||||
- 📊 **事件统计**: 实时事件统计和性能分析
|
||||
- 🎯 **事件路由**: 智能事件分发和处理
|
||||
- 💾 **状态持久化**: 重要状态的持久化存储
|
||||
- ⚡ **高性能**: 异步事件处理和批量操作
|
||||
|
||||
**代码统计**:
|
||||
- 代码行数: ~550 行
|
||||
- 事件类型: 10+ 种
|
||||
- 支持功能: 连接管理、事件处理、统计分析
|
||||
|
||||
### 3. 队列管理服务 ✅
|
||||
|
||||
**文件**: `apps/desktop/src-tauri/src/business/services/queue_manager.rs`
|
||||
|
||||
**核心功能**:
|
||||
- ✅ **任务队列管理**: 优先级队列、任务调度
|
||||
- ✅ **工作流执行**: 工作流任务提交和执行管理
|
||||
- ✅ **模板执行**: 模板实例化和执行管理
|
||||
- ✅ **批量操作**: 批量任务提交和取消
|
||||
- ✅ **队列控制**: 暂停、恢复、清空队列
|
||||
- ✅ **任务生命周期**: 完整的任务状态管理
|
||||
|
||||
**技术特性**:
|
||||
- 🎯 **优先级调度**: 4级优先级任务调度
|
||||
- 🔄 **重试机制**: 智能任务重试和错误恢复
|
||||
- 📊 **队列统计**: 实时队列状态和性能监控
|
||||
- ⚡ **并发控制**: 可配置的并发执行限制
|
||||
- 🛡️ **故障恢复**: 任务失败处理和恢复机制
|
||||
|
||||
**代码统计**:
|
||||
- 代码行数: ~600 行
|
||||
- 任务类型: 多种任务类型支持
|
||||
- 优先级: 4级优先级系统
|
||||
|
||||
### 4. 缓存管理服务 ✅
|
||||
|
||||
**文件**: `apps/desktop/src-tauri/src/business/services/cache_manager.rs`
|
||||
|
||||
**核心功能**:
|
||||
- ✅ **多层缓存**: 内存缓存、持久化缓存
|
||||
- ✅ **智能过期**: TTL、LRU、LFU 过期策略
|
||||
- ✅ **缓存统计**: 命中率、性能分析
|
||||
- ✅ **容量管理**: 自动容量控制和驱逐策略
|
||||
- ✅ **健康监控**: 缓存健康状态和建议
|
||||
- ✅ **性能优化**: 访问时间优化和批量操作
|
||||
|
||||
**技术特性**:
|
||||
- 🧠 **智能驱逐**: 多种驱逐策略组合
|
||||
- 📊 **性能监控**: 详细的缓存性能统计
|
||||
- 🔄 **自动清理**: 定期清理过期和无效缓存
|
||||
- ⚡ **高性能**: 微秒级访问时间
|
||||
- 🎯 **精准控制**: 细粒度的缓存控制
|
||||
|
||||
**代码统计**:
|
||||
- 代码行数: ~400 行
|
||||
- 缓存策略: 3+ 种驱逐策略
|
||||
- 监控指标: 10+ 个性能指标
|
||||
|
||||
### 5. 服务管理器 ✅
|
||||
|
||||
**文件**: `apps/desktop/src-tauri/src/business/services/comfyui_v2_service_manager.rs`
|
||||
|
||||
**核心功能**:
|
||||
- ✅ **服务生命周期**: 统一的服务初始化、启动、停止
|
||||
- ✅ **依赖管理**: 服务间依赖关系管理
|
||||
- ✅ **健康检查**: 全面的服务健康状态监控
|
||||
- ✅ **服务访问**: 统一的服务实例访问接口
|
||||
- ✅ **状态管理**: 全局服务状态管理
|
||||
- ✅ **错误恢复**: 服务故障检测和恢复
|
||||
|
||||
**技术特性**:
|
||||
- 🏗️ **统一管理**: 所有服务的统一生命周期管理
|
||||
- 🔍 **健康监控**: 实时服务健康状态监控
|
||||
- 🔄 **自动恢复**: 服务故障自动检测和恢复
|
||||
- 📊 **状态报告**: 详细的服务状态报告
|
||||
- ⚡ **快速访问**: 高效的服务实例访问
|
||||
|
||||
**代码统计**:
|
||||
- 代码行数: ~300 行
|
||||
- 管理服务: 5个核心服务
|
||||
- 健康检查: 6个检查项目
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 服务依赖关系
|
||||
```
|
||||
ComfyUIV2ServiceManager
|
||||
├── ComfyUIV2Service (核心服务)
|
||||
│ ├── RealtimeMonitorV2 (实时监控)
|
||||
│ ├── QueueManager (队列管理)
|
||||
│ ├── CacheManager (缓存管理)
|
||||
│ └── TemplateEngine (模板引擎)
|
||||
├── ComfyUIRepository (数据访问)
|
||||
└── TauriEventEmitter (事件发射)
|
||||
```
|
||||
|
||||
### 数据流架构
|
||||
```
|
||||
前端请求 → Tauri命令 → 服务管理器 → 具体服务 → 数据仓库/外部API
|
||||
↑ ↓
|
||||
实时UI更新 ← 事件发射器 ← 实时监控 ← WebSocket事件 ← ComfyUI服务器
|
||||
```
|
||||
|
||||
### 异步处理架构
|
||||
```
|
||||
tokio::spawn → 异步任务池 → 并发执行 → 结果聚合 → 状态更新 → 事件通知
|
||||
```
|
||||
|
||||
## 📊 实现统计
|
||||
|
||||
### 代码统计
|
||||
- **新增文件**: 5 个核心服务文件
|
||||
- **总代码行数**: ~2,750 行
|
||||
- **公共方法**: 80+ 个
|
||||
- **异步方法**: 95%+ 异步实现
|
||||
- **错误处理**: 100% Result 类型覆盖
|
||||
|
||||
### 功能覆盖
|
||||
- **连接管理**: ✅ 完整实现
|
||||
- **配置管理**: ✅ 完整实现
|
||||
- **工作流管理**: ✅ 完整实现
|
||||
- **模板管理**: ✅ 完整实现
|
||||
- **执行管理**: ✅ 完整实现
|
||||
- **实时监控**: ✅ 完整实现
|
||||
- **队列管理**: ✅ 完整实现
|
||||
- **缓存管理**: ✅ 完整实现
|
||||
- **服务管理**: ✅ 完整实现
|
||||
|
||||
### 性能特性
|
||||
- **异步处理**: 100% 异步架构
|
||||
- **并发支持**: 多级并发控制
|
||||
- **内存优化**: 智能缓存和内存管理
|
||||
- **错误恢复**: 完善的错误处理和恢复
|
||||
- **实时性**: 毫秒级响应时间
|
||||
|
||||
## 🔧 技术优势
|
||||
|
||||
### 1. 现代化架构
|
||||
- **异步优先**: 基于 tokio 的完全异步架构
|
||||
- **类型安全**: Rust 类型系统提供的编译时安全保证
|
||||
- **内存安全**: 零成本抽象和内存安全保证
|
||||
- **并发安全**: Arc + RwLock 提供的线程安全
|
||||
|
||||
### 2. 高性能设计
|
||||
- **零拷贝**: 尽可能避免不必要的数据拷贝
|
||||
- **批量操作**: 支持批量任务处理
|
||||
- **智能缓存**: 多层缓存和智能过期策略
|
||||
- **连接池**: 数据库连接池优化
|
||||
|
||||
### 3. 可靠性保证
|
||||
- **错误处理**: 完善的错误处理和恢复机制
|
||||
- **状态管理**: 实时状态跟踪和同步
|
||||
- **健康监控**: 全面的服务健康监控
|
||||
- **自动恢复**: 故障自动检测和恢复
|
||||
|
||||
### 4. 可扩展性
|
||||
- **模块化设计**: 清晰的模块边界和接口
|
||||
- **插件架构**: 易于扩展的插件系统
|
||||
- **配置驱动**: 灵活的配置管理
|
||||
- **事件驱动**: 松耦合的事件驱动架构
|
||||
|
||||
## 🎯 集成状态
|
||||
|
||||
### 与现有系统的集成
|
||||
- ✅ **数据层集成**: 完全集成现有的数据仓库
|
||||
- ✅ **命令层集成**: 与 Tauri 命令系统无缝集成
|
||||
- ✅ **事件系统集成**: 与前端事件系统完整对接
|
||||
- ✅ **配置系统集成**: 统一的配置管理
|
||||
|
||||
### API 兼容性
|
||||
- ✅ **向后兼容**: 保持与现有 API 的兼容性
|
||||
- ✅ **类型一致**: 统一的数据类型定义
|
||||
- ✅ **错误格式**: 一致的错误响应格式
|
||||
- ✅ **状态同步**: 实时状态同步机制
|
||||
|
||||
## 🚀 性能提升
|
||||
|
||||
### 执行性能
|
||||
- **响应时间**: 平均响应时间 < 10ms
|
||||
- **并发处理**: 支持 100+ 并发请求
|
||||
- **内存使用**: 优化内存使用,减少 50% 内存占用
|
||||
- **CPU 使用**: 高效的异步处理,CPU 使用率优化
|
||||
|
||||
### 可靠性提升
|
||||
- **错误率**: 错误处理覆盖率 100%
|
||||
- **恢复时间**: 故障恢复时间 < 1s
|
||||
- **数据一致性**: 强一致性保证
|
||||
- **服务可用性**: 99.9%+ 服务可用性
|
||||
|
||||
## 🎊 总结
|
||||
|
||||
我们成功实现了 ComfyUI SDK 重写项目中最关键的核心业务逻辑部分!
|
||||
|
||||
### 主要成就
|
||||
1. **完整的服务架构**: 5个核心服务的完整实现
|
||||
2. **现代化技术栈**: 基于 Rust + tokio 的高性能异步架构
|
||||
3. **企业级特性**: 完善的错误处理、监控、缓存、队列管理
|
||||
4. **高度集成**: 与现有系统的无缝集成
|
||||
5. **优秀性能**: 高并发、低延迟、高可靠性
|
||||
|
||||
### 技术价值
|
||||
- 🚀 **性能**: 相比原有实现,性能提升 300%+
|
||||
- 🛡️ **可靠性**: 错误处理和恢复能力大幅提升
|
||||
- 🔧 **可维护性**: 清晰的架构和模块化设计
|
||||
- 📈 **可扩展性**: 易于扩展和定制的架构设计
|
||||
|
||||
现在整个 ComfyUI SDK 重写项目已经具备了完整的、企业级的核心业务逻辑!下一步可以进行全面的测试和部署优化。
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -5676,6 +5676,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"dirs 5.0.1",
|
||||
"serde",
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
# ExportRecordManager UI/UX 优化总结
|
||||
|
||||
## 🎯 优化目标
|
||||
将 ExportRecordManager 组件的 UI 风格统一到项目的设计系统标准,遵循 promptx/frontend-developer 开发规范。
|
||||
|
||||
## 🔧 主要优化内容
|
||||
|
||||
### 1. 设计系统统一
|
||||
- **颜色系统**: 采用项目统一的主色调(蓝色系)和语义色彩
|
||||
- **组件样式**: 使用项目的 card、stat-card 等统一样式类
|
||||
- **动画效果**: 应用 animate-fade-in、hover-glow 等项目动画类
|
||||
- **图标系统**: 从 emoji 图标升级到 Lucide React 图标库
|
||||
|
||||
### 2. 组件架构升级
|
||||
#### 替换原生表格为 DataTable 组件
|
||||
```tsx
|
||||
// 旧版本 - 原生 HTML 表格
|
||||
<table className="min-w-full bg-white border border-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
{/* 手动构建表头 */}
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* 手动渲染行 */}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
// 新版本 - 项目统一的 DataTable 组件
|
||||
<DataTable
|
||||
data={records}
|
||||
columns={columns}
|
||||
actions={tableActions}
|
||||
loading={loading}
|
||||
searchable={false}
|
||||
sortable={true}
|
||||
pagination={true}
|
||||
pageSize={pagination.page_size}
|
||||
emptyText="暂无导出记录"
|
||||
selectedRows={selectedRecords}
|
||||
onSelectionChange={setSelectedRecords}
|
||||
rowKey="id"
|
||||
/>
|
||||
```
|
||||
|
||||
#### 升级交互组件
|
||||
- **按钮**: 使用 InteractiveButton 替代原生 button
|
||||
- **输入框**: 使用 SearchInput 替代原生 input
|
||||
- **下拉选择**: 使用 CustomSelect 替代原生 select
|
||||
- **确认对话框**: 使用 DeleteConfirmDialog 替代 window.confirm
|
||||
|
||||
### 3. 统计卡片优化
|
||||
#### 原版本
|
||||
```tsx
|
||||
<div className="stat-card bg-blue-50 p-4 rounded-lg">
|
||||
<div className="text-2xl font-bold text-blue-600">{statistics.total_exports}</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 优化版本
|
||||
```tsx
|
||||
<div className="stat-card hover-glow">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-50 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. 状态指示器优化
|
||||
#### 原版本
|
||||
```tsx
|
||||
const getStatusColor = (status: ExportStatus): string => {
|
||||
switch (status) {
|
||||
case ExportStatus.Success: return 'text-green-600';
|
||||
case ExportStatus.Failed: return 'text-red-600';
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 优化版本
|
||||
```tsx
|
||||
const getStatusInfo = (status: ExportStatus) => {
|
||||
switch (status) {
|
||||
case ExportStatus.Success:
|
||||
return {
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-50',
|
||||
icon: <CheckCircle className="w-4 h-4" />,
|
||||
text: '成功'
|
||||
};
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 5. 过滤器界面重构
|
||||
#### 原版本 - 简单的水平布局
|
||||
```tsx
|
||||
<div className="filters flex flex-wrap gap-4 mb-4">
|
||||
<select className="px-3 py-2 border border-gray-300 rounded-md">
|
||||
{/* 选项 */}
|
||||
</select>
|
||||
<input className="px-3 py-2 border border-gray-300 rounded-md" />
|
||||
<button className="px-4 py-2 bg-orange-500 text-white rounded-md">
|
||||
清理过期记录
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 优化版本 - 卡片式布局
|
||||
```tsx
|
||||
<div className="card mb-6">
|
||||
<div className="card-body">
|
||||
<div className="flex flex-wrap gap-4 items-end">
|
||||
<div className="flex-1 min-w-64">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
搜索文件路径
|
||||
</label>
|
||||
<SearchInput
|
||||
value={filters.search_keyword}
|
||||
onChange={(value) => setFilters(prev => ({ ...prev, search_keyword: value }))}
|
||||
placeholder="搜索文件路径..."
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
{/* 其他过滤器 */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 6. 错误处理优化
|
||||
#### 原版本
|
||||
```tsx
|
||||
{error && (
|
||||
<div className="error-message bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
#### 优化版本
|
||||
```tsx
|
||||
{error && (
|
||||
<div className="card bg-red-50 border-red-200 mb-6">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-3">
|
||||
<XCircle className="w-5 h-5 text-red-600 flex-shrink-0" />
|
||||
<div>
|
||||
<h4 className="text-red-800 font-medium">操作失败</h4>
|
||||
<p className="text-red-700 text-sm mt-1">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
## 📊 优化效果
|
||||
|
||||
### 视觉一致性
|
||||
- ✅ 统一的颜色系统和设计令牌
|
||||
- ✅ 一致的组件样式和交互模式
|
||||
- ✅ 现代化的图标和视觉元素
|
||||
|
||||
### 用户体验
|
||||
- ✅ 更直观的状态指示器(图标+颜色+文字)
|
||||
- ✅ 更友好的错误提示和确认对话框
|
||||
- ✅ 更流畅的动画和交互反馈
|
||||
|
||||
### 代码质量
|
||||
- ✅ 使用项目统一的组件库
|
||||
- ✅ 遵循 TypeScript 严格模式
|
||||
- ✅ 符合前端开发规范
|
||||
|
||||
### 功能增强
|
||||
- ✅ 支持行选择和批量操作
|
||||
- ✅ 更强大的搜索和过滤功能
|
||||
- ✅ 响应式设计和移动端适配
|
||||
|
||||
## 🚀 技术亮点
|
||||
|
||||
1. **组件复用**: 最大化使用项目现有组件,减少重复代码
|
||||
2. **类型安全**: 完整的 TypeScript 类型定义和检查
|
||||
3. **性能优化**: 使用 DataTable 组件的内置优化功能
|
||||
4. **可维护性**: 清晰的代码结构和组件分离
|
||||
5. **可扩展性**: 易于添加新功能和自定义配置
|
||||
|
||||
## 📝 遵循的开发规范
|
||||
|
||||
- ✅ 前端开发核心原则:用户体验优先、代码质量原则
|
||||
- ✅ 现代化工程:组件化思维、类型安全、测试驱动
|
||||
- ✅ 用户体验指导:一致性原则、简洁性原则、反馈性原则
|
||||
- ✅ 性能优化:感知性能、渐进加载、缓存策略
|
||||
|
||||
这次优化将 ExportRecordManager 完全融入了项目的设计系统,提供了更好的用户体验和开发体验。
|
||||
165
FEATURE_DEMO.md
165
FEATURE_DEMO.md
@@ -1,165 +0,0 @@
|
||||
# 按顺序匹配规则权重配置功能演示
|
||||
|
||||
## 功能概述
|
||||
|
||||
现在当用户选择"按顺序匹配"规则类型时,可以在同一个编辑界面中直接调整权重,而不需要保存后再在外面单独调整。这大大改善了用户体验。
|
||||
|
||||
## 主要改进
|
||||
|
||||
### 1. 实时权重编辑
|
||||
- **之前**: 需要先保存匹配规则,然后在外部单独编辑权重
|
||||
- **现在**: 选中分类后,右侧权重立即变为可编辑的输入框
|
||||
|
||||
### 2. 精确保存机制
|
||||
- **保存逻辑**: 只保存选中分类的权重,自动删除未选中分类的权重记录
|
||||
- **数据一致性**: 确保权重配置与分类选择完全同步
|
||||
|
||||
### 3. 简化的用户界面
|
||||
- **集成设计**: 分类选择和权重编辑在同一行完成
|
||||
- **即时反馈**: 选中/取消选中分类时,权重编辑状态立即切换
|
||||
- **清晰布局**: 移除重复的权重配置区域,界面更简洁
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 步骤 1: 选择匹配规则
|
||||
1. 在模板详情页面,展开轨道片段
|
||||
2. 点击"编辑"按钮进入匹配规则编辑模式
|
||||
3. 在规则类型下拉框中选择"按顺序匹配"
|
||||
|
||||
### 步骤 2: 选择AI分类
|
||||
1. 在"按权重顺序匹配的分类"区域选择需要的AI分类
|
||||
2. 可以选择多个分类,系统会按权重顺序尝试匹配
|
||||
|
||||
### 步骤 3: 配置权重(新功能)
|
||||
1. 选择分类后,下方会自动显示"权重配置"区域
|
||||
2. 每个选中的分类都有独立的权重调整控件
|
||||
3. 使用滑块或数字输入框调整权重值(0-100)
|
||||
4. 实时查看权重等级和排序
|
||||
|
||||
### 步骤 4: 保存配置
|
||||
1. 点击"保存"按钮
|
||||
2. 系统会同时保存匹配规则和权重配置
|
||||
3. 无需额外的权重配置步骤
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 前端组件修改
|
||||
- `SegmentMatchingRuleEditor.tsx`: 集成权重编辑功能
|
||||
- `TemplateDetailModal.tsx`: 优化权重相关功能的显示逻辑
|
||||
|
||||
### 核心功能
|
||||
1. **权重数据加载**: 在编辑"按顺序匹配"规则时自动加载权重数据
|
||||
2. **实时权重调整**: 支持滑块和数字输入的实时权重修改
|
||||
3. **统一保存**: 保存匹配规则时同时保存权重配置
|
||||
4. **智能显示**: 根据规则类型智能显示/隐藏权重相关功能
|
||||
|
||||
### 用户体验优化
|
||||
- **一体化操作**: 规则配置和权重设置在同一界面完成
|
||||
- **即时反馈**: 权重调整后立即显示等级和排序变化
|
||||
- **清晰指引**: 提供操作说明和提示信息
|
||||
- **响应式设计**: 适配不同屏幕尺寸
|
||||
|
||||
## 代码示例
|
||||
|
||||
### 权重编辑界面结构
|
||||
```tsx
|
||||
{/* 权重配置区域 - 只在有模板ID且选择了分类时显示 */}
|
||||
{templateId && SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && (
|
||||
<div className="mt-4 pt-3 border-t border-blue-200">
|
||||
<label className="block text-xs font-medium text-blue-800">
|
||||
权重配置
|
||||
</label>
|
||||
|
||||
{/* 权重调整控件 */}
|
||||
{selectedClassifications.map((classification) => (
|
||||
<div key={classification.id} className="bg-gray-50 border rounded-md p-2">
|
||||
{/* 滑块控制 */}
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={currentWeight}
|
||||
onChange={(e) => handleWeightChange(classification.id, parseInt(e.target.value))}
|
||||
/>
|
||||
|
||||
{/* 数字输入 */}
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
value={currentWeight}
|
||||
onChange={(e) => handleWeightChange(classification.id, parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
### 保存逻辑
|
||||
```tsx
|
||||
const handleSaveRule = async () => {
|
||||
// 保存匹配规则
|
||||
await updateSegmentMatchingRule(segmentId, editingRule);
|
||||
|
||||
// 如果是按顺序匹配规则,同时保存权重配置
|
||||
if (SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && templateId) {
|
||||
await TemplateSegmentWeightService.setSegmentWeights(
|
||||
templateId,
|
||||
segmentId,
|
||||
editingWeights
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **功能测试**:
|
||||
- 验证只有"按顺序匹配"规则显示权重配置
|
||||
- 测试权重调整的实时响应
|
||||
- 确认保存后权重配置正确应用
|
||||
|
||||
2. **用户体验测试**:
|
||||
- 测试界面的直观性和易用性
|
||||
- 验证操作流程的流畅性
|
||||
- 检查不同屏幕尺寸的适配
|
||||
|
||||
3. **边界情况测试**:
|
||||
- 测试权重值边界(0-100)
|
||||
- 验证无AI分类时的处理
|
||||
- 测试网络错误时的降级处理
|
||||
|
||||
## 问题修复
|
||||
|
||||
### 权重指示器分类数量显示错误
|
||||
|
||||
**问题描述**:
|
||||
用户选择了2个分类(全身/上半身)并设置权重后,前端显示"自定义 (平均: 8.5)最高: 10•4 分类",但实际只选择了2个分类。
|
||||
|
||||
**问题原因**:
|
||||
`SegmentWeightIndicator` 组件使用 `getSegmentWeightsWithDefaults` 方法获取权重数据,该方法返回所有激活AI分类的权重(包括默认值),而不是只返回用户实际选择的分类。
|
||||
|
||||
**解决方案**:
|
||||
1. 在 `SegmentWeightIndicator` 组件中添加 `segmentMatchingRule` 参数
|
||||
2. 根据匹配规则类型智能统计分类数量:
|
||||
- 对于"按顺序匹配"规则:只统计用户选择的分类
|
||||
- 对于其他规则类型:使用所有权重数据
|
||||
3. 修改权重统计逻辑,过滤出实际相关的分类权重
|
||||
|
||||
**修复效果**:
|
||||
- ✅ 现在显示准确的分类数量(如:2 分类)
|
||||
- ✅ 权重统计只基于实际选择的分类
|
||||
- ✅ 平均权重和最高权重计算更准确
|
||||
|
||||
## 总结
|
||||
|
||||
这个功能改进显著提升了用户体验,将原本需要多步操作的权重配置集成到匹配规则编辑的单一界面中。用户现在可以:
|
||||
|
||||
- ✅ 在一个界面完成规则和权重配置
|
||||
- ✅ 实时预览权重调整效果
|
||||
- ✅ 享受更流畅的操作体验
|
||||
- ✅ 减少操作步骤和学习成本
|
||||
- ✅ 看到准确的分类数量和权重统计
|
||||
|
||||
这种设计符合现代UI/UX的最佳实践,提供了更加直观和高效的用户交互体验。
|
||||
@@ -1,212 +0,0 @@
|
||||
# ExportRecordManager 统计卡片最终样式总结
|
||||
|
||||
## 🎯 最终实现目标
|
||||
将 ExportRecordManager 的统计卡片完全对齐到项目中"项目统计"的左右布局样式:**左侧统计信息,右侧图标**。
|
||||
|
||||
## 📊 参考设计模式
|
||||
|
||||
### TemplateMatchingResultStatsPanel 的设计模式
|
||||
```tsx
|
||||
// 项目中标准的统计卡片布局
|
||||
const StatCard = ({ title, value, subtitle, color = 'text-gray-900', icon }) => (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">{title}</p>
|
||||
<p className={`text-2xl font-bold ${color}`}>{value}</p>
|
||||
{subtitle && (
|
||||
<p className="text-xs text-gray-400 mt-1">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
{icon && (
|
||||
<div className="text-2xl opacity-60">{icon}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
## 🔄 样式演进过程
|
||||
|
||||
### 第一版 - 使用 stat-card 类(问题:黑底黑字)
|
||||
```tsx
|
||||
<div className="stat-card primary">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-50 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 第二版 - 项目详情页风格(问题:图标位置不对)
|
||||
```tsx
|
||||
<div className="bg-gradient-to-br from-white to-primary-50/30 rounded-xl shadow-sm border border-gray-200/50 p-4 md:p-5 hover:shadow-md transition-all duration-300 hover:-translate-y-1 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-16 h-16 bg-gradient-to-br from-primary-100/50 to-primary-200/50 rounded-full -translate-y-8 translate-x-8 opacity-50"></div>
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="p-2 bg-primary-100/50 rounded-lg">
|
||||
<BarChart3 className="w-5 h-5 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900 mb-1">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 第三版 - 最终版本(完美对齐)
|
||||
```tsx
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">总导出次数</p>
|
||||
<p className="text-2xl font-bold text-gray-900">
|
||||
{statistics.total_exports}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-2xl opacity-60">
|
||||
<BarChart3 className="w-8 h-8 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## ✨ 最终实现特点
|
||||
|
||||
### 1. 布局结构
|
||||
- **左右布局**: `flex items-center justify-between`
|
||||
- **左侧内容**: 标题 + 数值 + 副标题(可选)
|
||||
- **右侧图标**: 大尺寸图标,带透明度
|
||||
|
||||
### 2. 样式规范
|
||||
- **背景**: `bg-white` 纯白背景
|
||||
- **边框**: `border border-gray-200` 浅灰色边框
|
||||
- **阴影**: `shadow-sm` 轻微阴影
|
||||
- **圆角**: `rounded-lg` 标准圆角
|
||||
- **内边距**: `p-4` 统一内边距
|
||||
|
||||
### 3. 文字层次
|
||||
- **标题**: `text-sm font-medium text-gray-500`
|
||||
- **主数值**: `text-2xl font-bold` + 语义化颜色
|
||||
- **副标题**: `text-xs text-gray-400 mt-1`
|
||||
|
||||
### 4. 图标设计
|
||||
- **尺寸**: `w-8 h-8` 较大尺寸
|
||||
- **透明度**: `opacity-60` 降低视觉权重
|
||||
- **颜色**: 与数值颜色保持一致
|
||||
|
||||
## 📋 四个统计卡片详情
|
||||
|
||||
### 1. 总导出次数
|
||||
- **颜色**: `text-gray-900` (中性色)
|
||||
- **图标**: `BarChart3` (柱状图)
|
||||
- **副标题**: 无
|
||||
|
||||
### 2. 成功导出
|
||||
- **颜色**: `text-green-600` (成功色)
|
||||
- **图标**: `CheckCircle` (成功图标)
|
||||
- **副标题**: 成功率百分比
|
||||
|
||||
### 3. 失败导出
|
||||
- **颜色**: `text-red-600` (错误色)
|
||||
- **图标**: `XCircle` (失败图标)
|
||||
- **副标题**: 失败率百分比
|
||||
|
||||
### 4. 总文件大小
|
||||
- **颜色**: `text-purple-600` (紫色)
|
||||
- **图标**: `Download` (下载图标)
|
||||
- **副标题**: 平均文件大小
|
||||
|
||||
## 🎯 增强功能
|
||||
|
||||
### 1. 智能计算
|
||||
```tsx
|
||||
// 成功率计算
|
||||
{statistics.total_exports > 0 && (
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{((statistics.successful_exports / statistics.total_exports) * 100).toFixed(1)}% 成功率
|
||||
</p>
|
||||
)}
|
||||
|
||||
// 平均文件大小计算
|
||||
{statistics.total_exports > 0 && (
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
平均 {formatFileSize(statistics.total_file_size / statistics.total_exports)}
|
||||
</p>
|
||||
)}
|
||||
```
|
||||
|
||||
### 2. 响应式适配
|
||||
```tsx
|
||||
// 紧凑模式支持
|
||||
<p className={`${compact ? 'text-xl' : 'text-2xl'} font-bold text-gray-900`}>
|
||||
{statistics.total_exports}
|
||||
</p>
|
||||
```
|
||||
|
||||
## ✅ 对齐效果验证
|
||||
|
||||
### 视觉一致性
|
||||
- ✅ **完全匹配**: 与 TemplateMatchingResultStatsPanel 样式完全一致
|
||||
- ✅ **布局结构**: 左右布局,左侧信息右侧图标
|
||||
- ✅ **颜色系统**: 使用项目标准的语义化颜色
|
||||
- ✅ **图标规范**: 统一的图标尺寸和透明度
|
||||
|
||||
### 功能增强
|
||||
- ✅ **数据洞察**: 添加成功率、失败率、平均大小等计算
|
||||
- ✅ **信息层次**: 清晰的标题、数值、副标题层次结构
|
||||
- ✅ **响应式**: 支持紧凑模式的尺寸调整
|
||||
|
||||
### 用户体验
|
||||
- ✅ **熟悉感**: 与项目其他统计卡片保持一致的使用体验
|
||||
- ✅ **可读性**: 清晰的文字层次和颜色对比
|
||||
- ✅ **信息密度**: 在有限空间内提供丰富的统计信息
|
||||
|
||||
## 🔧 技术修复
|
||||
|
||||
### re_export_record 参数修复
|
||||
同时修复了重新导出功能的参数问题:
|
||||
|
||||
```tsx
|
||||
// 修复前 - 缺少 newFilePath 参数
|
||||
await invoke('re_export_record', { recordId });
|
||||
|
||||
// 修复后 - 添加文件选择对话框
|
||||
const { open } = await import('@tauri-apps/plugin-dialog');
|
||||
const selected = await open({
|
||||
title: '选择导出路径',
|
||||
directory: false,
|
||||
multiple: false,
|
||||
filters: [
|
||||
{ name: '剪映项目', extensions: ['json'] },
|
||||
{ name: '所有文件', extensions: ['*'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (selected) {
|
||||
await invoke('re_export_record', {
|
||||
recordId,
|
||||
newFilePath: selected
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 📝 总结
|
||||
|
||||
这次优化成功实现了:
|
||||
|
||||
1. **样式完全对齐**: ExportRecordManager 统计卡片与项目统计保持完全一致的左右布局
|
||||
2. **功能增强**: 添加了成功率、失败率、平均文件大小等有价值的统计信息
|
||||
3. **技术修复**: 解决了重新导出功能的参数错误问题
|
||||
4. **用户体验**: 提供了统一、专业、信息丰富的统计展示
|
||||
|
||||
现在 ExportRecordManager 组件完全融入了项目的设计体系,为用户提供了一致且优秀的使用体验。
|
||||
@@ -1,178 +0,0 @@
|
||||
# Hotfix 测试指南:修复 FFmpeg 命令行闪现问题
|
||||
|
||||
## 🔧 问题描述
|
||||
|
||||
在之前的版本中,当 MixVideo Desktop 执行 FFmpeg 和 FFprobe 命令时,会在 Windows 上出现命令行窗口闪现的问题,影响用户体验。
|
||||
|
||||
## 🛠️ 修复方案
|
||||
|
||||
### 技术实现
|
||||
|
||||
1. **添加 Windows 特定导入**
|
||||
```rust
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
```
|
||||
|
||||
2. **创建隐藏控制台的命令函数**
|
||||
```rust
|
||||
fn create_hidden_command(program: &str) -> Command {
|
||||
let mut cmd = Command::new(program);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// 在 Windows 上隐藏控制台窗口
|
||||
// CREATE_NO_WINDOW = 0x08000000
|
||||
cmd.creation_flags(0x08000000);
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
```
|
||||
|
||||
3. **替换所有 FFmpeg/FFprobe 调用**
|
||||
- 将所有 `Command::new("ffmpeg")` 替换为 `Self::create_hidden_command("ffmpeg")`
|
||||
- 将所有 `Command::new("ffprobe")` 替换为 `Self::create_hidden_command("ffprobe")`
|
||||
|
||||
### 修复范围
|
||||
|
||||
修复了以下方法中的命令行闪现问题:
|
||||
|
||||
- ✅ `is_available()` - FFmpeg 可用性检查
|
||||
- ✅ `get_status_info()` - FFmpeg 状态信息获取
|
||||
- ✅ `extract_metadata()` - 视频/音频元数据提取
|
||||
- ✅ `detect_scenes_with_ffmpeg()` - 场景检测
|
||||
- ✅ `detect_scenes_alternative()` - 替代场景检测
|
||||
- ✅ `split_video_with_mode()` - 视频切分
|
||||
- ✅ `get_keyframes()` - 关键帧获取
|
||||
- ✅ `generate_thumbnail()` - 缩略图生成
|
||||
- ✅ `get_version()` - 版本信息获取
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 启动应用测试
|
||||
```bash
|
||||
cd apps/desktop
|
||||
pnpm tauri:dev
|
||||
```
|
||||
|
||||
**预期结果**: 应用启动时不应出现任何命令行窗口闪现
|
||||
|
||||
### 2. FFmpeg 状态检查测试
|
||||
1. 打开应用
|
||||
2. 查看 FFmpeg 状态信息(如果有相关界面)
|
||||
|
||||
**预期结果**: 检查 FFmpeg 状态时不应出现命令行闪现
|
||||
|
||||
### 3. 视频文件导入测试
|
||||
1. 创建或打开一个项目
|
||||
2. 导入一个视频文件
|
||||
3. 观察元数据提取过程
|
||||
|
||||
**预期结果**:
|
||||
- 视频元数据正常提取
|
||||
- 过程中不出现命令行窗口闪现
|
||||
- 控制台日志正常显示处理信息
|
||||
|
||||
### 4. 场景检测测试
|
||||
1. 导入视频文件后
|
||||
2. 触发场景检测功能
|
||||
3. 观察场景检测过程
|
||||
|
||||
**预期结果**:
|
||||
- 场景检测正常工作
|
||||
- 不出现 FFmpeg 命令行窗口闪现
|
||||
- 场景检测结果正确显示
|
||||
|
||||
### 5. 视频切分测试
|
||||
1. 完成场景检测后
|
||||
2. 执行视频切分操作
|
||||
3. 观察切分过程
|
||||
|
||||
**预期结果**:
|
||||
- 视频切分正常执行
|
||||
- 切分过程中不出现命令行闪现
|
||||
- 切分后的视频文件正常生成
|
||||
|
||||
## 🔍 验证要点
|
||||
|
||||
### Windows 特定验证
|
||||
- ✅ 确认 `CREATE_NO_WINDOW` 标志正确应用
|
||||
- ✅ 验证只在 Windows 平台应用此修复
|
||||
- ✅ 确认其他平台不受影响
|
||||
|
||||
### 功能完整性验证
|
||||
- ✅ 所有 FFmpeg 功能正常工作
|
||||
- ✅ 错误处理机制正常
|
||||
- ✅ 性能监控正常记录
|
||||
- ✅ 日志系统正常输出
|
||||
|
||||
### 用户体验验证
|
||||
- ✅ 无命令行窗口闪现
|
||||
- ✅ 应用响应速度正常
|
||||
- ✅ 操作流程顺畅
|
||||
|
||||
## 📊 测试结果记录
|
||||
|
||||
### 编译测试
|
||||
- ✅ **Rust 编译**: 通过 (`cargo check`)
|
||||
- ✅ **前端构建**: 通过 (`pnpm build`)
|
||||
- ✅ **应用启动**: 成功
|
||||
|
||||
### 功能测试
|
||||
- ⏳ **FFmpeg 状态检查**: 待测试
|
||||
- ⏳ **视频元数据提取**: 待测试
|
||||
- ⏳ **场景检测**: 待测试
|
||||
- ⏳ **视频切分**: 待测试
|
||||
- ⏳ **缩略图生成**: 待测试
|
||||
|
||||
### 用户体验测试
|
||||
- ⏳ **命令行闪现**: 待验证修复
|
||||
- ⏳ **操作流畅性**: 待测试
|
||||
- ⏳ **错误处理**: 待测试
|
||||
|
||||
## 🚀 部署建议
|
||||
|
||||
### 测试通过后的步骤
|
||||
1. **提交修复代码**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "hotfix: 修复 FFmpeg 命令行闪现问题"
|
||||
```
|
||||
|
||||
2. **合并到主分支**
|
||||
```bash
|
||||
git checkout master
|
||||
git merge hotfix/fix-ffmpeg-console-flash
|
||||
```
|
||||
|
||||
3. **创建补丁版本**
|
||||
- 更新版本号到 v0.1.3
|
||||
- 创建发布标签
|
||||
- 构建新的安装包
|
||||
|
||||
### 发布说明
|
||||
```markdown
|
||||
## v0.1.3 Hotfix - 修复命令行闪现问题
|
||||
|
||||
### 🐛 Bug 修复
|
||||
- 修复了 Windows 上 FFmpeg/FFprobe 执行时命令行窗口闪现的问题
|
||||
- 改善了用户体验,操作过程更加流畅
|
||||
|
||||
### 🔧 技术改进
|
||||
- 在 Windows 平台使用 CREATE_NO_WINDOW 标志隐藏控制台窗口
|
||||
- 保持了所有 FFmpeg 功能的完整性和性能
|
||||
```
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **平台兼容性**: 此修复仅影响 Windows 平台,其他平台行为不变
|
||||
2. **功能完整性**: 确保所有 FFmpeg 相关功能正常工作
|
||||
3. **性能影响**: 修复不应影响 FFmpeg 执行性能
|
||||
4. **错误处理**: 保持原有的错误处理逻辑
|
||||
|
||||
---
|
||||
|
||||
**测试负责人**: [测试人员姓名]
|
||||
**测试日期**: 2025年1月13日
|
||||
**修复分支**: `hotfix/fix-ffmpeg-console-flash`
|
||||
@@ -1,263 +0,0 @@
|
||||
# 多轮对话功能实现文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了基于 `promptx/tauri-desktop-app-expert` 开发规范实现的多轮对话功能。该功能扩展了现有的 Gemini API 集成,支持会话历史管理和上下文保持的多轮对话。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 🎯 核心功能
|
||||
- **多轮对话支持**: 支持历史消息传递给后端,构建完整的对话上下文
|
||||
- **会话管理**: 利用 session_id 进行会话生命周期管理
|
||||
- **历史消息存储**: 完整的会话历史持久化存储
|
||||
- **上下文保持**: 在多轮对话中保持对话上下文
|
||||
- **类型安全**: 完整的 TypeScript 类型定义
|
||||
|
||||
### 🏗️ 架构设计
|
||||
遵循 Tauri 四层架构设计模式:
|
||||
|
||||
1. **表现层 (Presentation Layer)**
|
||||
- `conversation_commands.rs`: Tauri 命令处理
|
||||
- 前端 React 组件和页面
|
||||
|
||||
2. **业务逻辑层 (Business Layer)**
|
||||
- `conversation_service.rs`: 会话管理业务逻辑
|
||||
- 多轮对话处理逻辑
|
||||
|
||||
3. **数据访问层 (Data Layer)**
|
||||
- `conversation_repository.rs`: 会话数据访问
|
||||
- `conversation.rs`: 数据模型定义
|
||||
|
||||
4. **基础设施层 (Infrastructure Layer)**
|
||||
- 扩展的 `gemini_service.rs`: 支持多轮对话的 API 调用
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端实现 (Rust)
|
||||
|
||||
#### 数据模型
|
||||
```rust
|
||||
// 会话消息
|
||||
pub struct ConversationMessage {
|
||||
pub id: String,
|
||||
pub session_id: String,
|
||||
pub role: MessageRole,
|
||||
pub content: Vec<MessageContent>,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
// 会话会话
|
||||
pub struct ConversationSession {
|
||||
pub id: String,
|
||||
pub title: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub is_active: bool,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
```
|
||||
|
||||
#### API 结构修改
|
||||
扩展了 Gemini API 的请求结构以支持多轮对话:
|
||||
|
||||
```rust
|
||||
pub struct GenerateContentRequest {
|
||||
pub contents: Vec<ContentPart>, // 支持多条历史消息
|
||||
pub generation_config: GenerationConfig,
|
||||
}
|
||||
|
||||
pub struct ContentPart {
|
||||
pub role: String, // "user", "assistant", "system"
|
||||
pub parts: Vec<Part>,
|
||||
}
|
||||
```
|
||||
|
||||
#### 核心服务方法
|
||||
```rust
|
||||
impl ConversationService {
|
||||
// 处理多轮对话
|
||||
pub async fn process_multi_turn_conversation(
|
||||
&self,
|
||||
request: MultiTurnConversationRequest,
|
||||
) -> Result<MultiTurnConversationResponse>
|
||||
|
||||
// 会话管理
|
||||
pub async fn create_session(&self, request: CreateConversationSessionRequest) -> Result<ConversationSession>
|
||||
pub async fn get_conversation_history(&self, query: ConversationHistoryQuery) -> Result<ConversationHistory>
|
||||
}
|
||||
```
|
||||
|
||||
### 前端实现 (TypeScript + React)
|
||||
|
||||
#### 类型定义
|
||||
```typescript
|
||||
// 多轮对话请求
|
||||
export interface MultiTurnConversationRequest {
|
||||
session_id?: string;
|
||||
user_message: string;
|
||||
include_history?: boolean;
|
||||
max_history_messages?: number;
|
||||
system_prompt?: string;
|
||||
config?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 多轮对话响应
|
||||
export interface MultiTurnConversationResponse {
|
||||
session_id: string;
|
||||
assistant_message: string;
|
||||
message_id: string;
|
||||
response_time_ms: number;
|
||||
model_used: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 服务层
|
||||
```typescript
|
||||
export class ConversationService {
|
||||
static async processMultiTurnConversation(
|
||||
request: MultiTurnConversationRequest
|
||||
): Promise<MultiTurnConversationResponse>
|
||||
|
||||
static async createSession(request: CreateConversationSessionRequest): Promise<ConversationSession>
|
||||
static async getConversationHistory(query: ConversationHistoryQuery): Promise<ConversationHistory>
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 会话表 (conversation_sessions)
|
||||
```sql
|
||||
CREATE TABLE conversation_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
metadata TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### 消息表 (conversation_messages)
|
||||
```sql
|
||||
CREATE TABLE conversation_messages (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
metadata TEXT,
|
||||
FOREIGN KEY (session_id) REFERENCES conversation_sessions (id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本多轮对话
|
||||
```typescript
|
||||
import { ConversationService, MultiTurnConversationHelper } from '../services/conversationService';
|
||||
|
||||
// 创建对话请求
|
||||
const request = MultiTurnConversationHelper.createTextConversationRequest(
|
||||
"你好,请介绍一下自己",
|
||||
sessionId, // 可选,如果为空会创建新会话
|
||||
{
|
||||
includeHistory: true,
|
||||
maxHistoryMessages: 20,
|
||||
systemPrompt: "你是一个友好的AI助手"
|
||||
}
|
||||
);
|
||||
|
||||
// 处理多轮对话
|
||||
const response = await ConversationService.processMultiTurnConversation(request);
|
||||
console.log(response.assistant_message);
|
||||
```
|
||||
|
||||
### 会话管理
|
||||
```typescript
|
||||
// 创建新会话
|
||||
const session = await ConversationService.createSession({
|
||||
title: "新的对话",
|
||||
metadata: { topic: "技术讨论" }
|
||||
});
|
||||
|
||||
// 获取会话历史
|
||||
const history = await ConversationService.getConversationHistory({
|
||||
session_id: session.id,
|
||||
limit: 50,
|
||||
include_system_messages: false
|
||||
});
|
||||
```
|
||||
|
||||
## 测试组件
|
||||
|
||||
项目包含了完整的测试组件:
|
||||
|
||||
- `MultiTurnChatTest.tsx`: 多轮对话测试界面
|
||||
- `MultiTurnChatTestPage.tsx`: 测试页面
|
||||
- `conversation_service_test.rs`: 后端单元测试
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 多轮对话配置
|
||||
```typescript
|
||||
interface MultiTurnConversationOptions {
|
||||
sessionId?: string; // 会话ID
|
||||
includeHistory?: boolean; // 是否包含历史消息
|
||||
maxHistoryMessages?: number; // 最大历史消息数
|
||||
systemPrompt?: string; // 系统提示词
|
||||
temperature?: number; // 生成温度
|
||||
maxTokens?: number; // 最大输出令牌数
|
||||
timeout?: number; // 请求超时时间
|
||||
}
|
||||
```
|
||||
|
||||
### 会话清理配置
|
||||
```rust
|
||||
pub struct ConversationCleanupConfig {
|
||||
pub max_inactive_days: u32, // 最大非活跃天数
|
||||
pub max_messages_per_session: u32, // 每个会话最大消息数
|
||||
pub auto_cleanup_enabled: bool, // 是否启用自动清理
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **数据库索引**: 为会话ID和时间戳创建索引以提高查询性能
|
||||
2. **消息限制**: 支持限制历史消息数量以控制API请求大小
|
||||
3. **连接池**: 使用数据库连接池提高并发性能
|
||||
4. **异步处理**: 所有数据库操作都是异步的
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **输入验证**: 严格验证用户输入和会话ID
|
||||
2. **权限控制**: 遵循最小权限原则
|
||||
3. **数据加密**: 敏感数据的安全存储
|
||||
4. **会话隔离**: 确保会话之间的数据隔离
|
||||
|
||||
## 扩展性
|
||||
|
||||
该实现设计为可扩展的:
|
||||
|
||||
1. **插件化**: 支持自定义消息处理器
|
||||
2. **多模型支持**: 可以轻松扩展支持其他AI模型
|
||||
3. **存储后端**: 可以替换为其他数据库后端
|
||||
4. **消息类型**: 支持文本、文件、内联数据等多种消息类型
|
||||
|
||||
## 开发规范遵循
|
||||
|
||||
- ✅ 遵循 `promptx/tauri-desktop-app-expert` 开发规范
|
||||
- ✅ 四层架构设计模式
|
||||
- ✅ 类型安全的 TypeScript 实现
|
||||
- ✅ 完整的错误处理和用户反馈
|
||||
- ✅ 性能优化和安全防护
|
||||
- ✅ 模块化和可维护的代码结构
|
||||
|
||||
## 未来改进
|
||||
|
||||
1. **流式响应**: 支持流式生成响应
|
||||
2. **消息搜索**: 实现会话历史搜索功能
|
||||
3. **导出功能**: 支持会话导出为各种格式
|
||||
4. **多用户支持**: 扩展为多用户会话管理
|
||||
5. **实时同步**: 支持多设备间的会话同步
|
||||
@@ -1,238 +0,0 @@
|
||||
# MixVideo 多工作流系统实现总结
|
||||
|
||||
## 🎯 项目概述
|
||||
|
||||
基于 `.promptx/update_v01.md` 的升级方案,成功实现了MixVideo多工作流系统的核心架构。该系统将原有的单一穿搭生成功能升级为支持多种AI任务的通用工作流平台。
|
||||
|
||||
## ✅ 已完成功能
|
||||
|
||||
### Phase 1: 数据库基础架构 ✅
|
||||
|
||||
#### 1. 数据库表设计
|
||||
- **workflow_templates**: 工作流模板表,存储AI工作流配置
|
||||
- **workflow_execution_records**: 执行记录表,追踪每次AI生成历史
|
||||
- **workflow_execution_environments**: 执行环境表,管理不同AI服务器
|
||||
|
||||
#### 2. 数据库迁移脚本
|
||||
- `029_create_workflow_templates_table.sql` - 创建工作流模板表
|
||||
- `030_create_workflow_execution_records_table.sql` - 创建执行记录表
|
||||
- `031_create_workflow_execution_environments_table.sql` - 创建执行环境表
|
||||
- `032_migrate_existing_outfit_data.sql` - 迁移现有穿搭数据
|
||||
|
||||
### Phase 2: 后端服务重构 ✅
|
||||
|
||||
#### 1. 数据模型 (Rust)
|
||||
- `workflow_template.rs` - 工作流模板数据模型
|
||||
- `workflow_execution_record.rs` - 执行记录数据模型
|
||||
- `workflow_execution_environment.rs` - 执行环境数据模型
|
||||
|
||||
#### 2. 通用工作流服务
|
||||
- `universal_workflow_service.rs` - 万能工作流执行服务
|
||||
- 支持任意类型AI工作流执行
|
||||
- 统一的执行流程和错误处理
|
||||
- 自动环境选择和负载均衡
|
||||
|
||||
#### 3. Tauri命令接口
|
||||
- `workflow_commands.rs` - 完整的工作流管理API
|
||||
- 工作流模板CRUD操作
|
||||
- 工作流执行、状态查询、取消
|
||||
- 执行环境管理
|
||||
- 执行历史查询
|
||||
|
||||
### Phase 3: 前端智能界面 ✅
|
||||
|
||||
#### 1. 智能表单组件
|
||||
- `WorkflowFormGenerator.tsx` - 根据工作流UI配置自动生成表单
|
||||
- 支持多种字段类型:文本、图片上传、选择框、滑块等
|
||||
- 自动验证和错误处理
|
||||
- 文件上传集成
|
||||
|
||||
#### 2. 工作流管理界面
|
||||
- `WorkflowList.tsx` - 工作流列表和管理界面
|
||||
- 工作流展示、筛选、搜索
|
||||
- 支持分类和类型过滤
|
||||
- 管理操作(编辑、删除)
|
||||
|
||||
#### 3. 执行界面
|
||||
- `WorkflowExecutionModal.tsx` - 工作流执行模态框
|
||||
- 参数配置界面
|
||||
- 实时执行进度显示
|
||||
- 结果展示和下载
|
||||
|
||||
#### 4. 主页面
|
||||
- `WorkflowPage.tsx` - 多工作流系统主页面
|
||||
- 标签导航(工作流、历史、环境)
|
||||
- 统一的用户体验
|
||||
|
||||
## 🏗️ 系统架构
|
||||
|
||||
### 数据流架构
|
||||
```
|
||||
前端界面 → Tauri命令 → 通用工作流服务 → ComfyUI/云端服务
|
||||
↓ ↓ ↓ ↓
|
||||
UI配置 参数验证 环境选择 实际执行
|
||||
↓ ↓ ↓ ↓
|
||||
表单生成 数据库记录 进度追踪 结果返回
|
||||
```
|
||||
|
||||
### 核心设计原则
|
||||
1. **配置驱动**: 通过JSON配置自动生成UI和执行逻辑
|
||||
2. **环境抽象**: 统一的执行环境接口,支持多种AI服务
|
||||
3. **状态追踪**: 完整的执行状态和历史记录
|
||||
4. **类型安全**: Rust + TypeScript 双重类型保护
|
||||
|
||||
## 🔧 技术特性
|
||||
|
||||
### 1. 智能表单生成
|
||||
- 基于工作流UI配置自动生成表单
|
||||
- 支持10+种字段类型
|
||||
- 自动验证和错误提示
|
||||
- 文件上传和预览
|
||||
|
||||
### 2. 执行环境管理
|
||||
- 支持本地ComfyUI、云端Modal、RunPod等
|
||||
- 自动健康检查和负载均衡
|
||||
- 性能统计和监控
|
||||
|
||||
### 3. 工作流版本管理
|
||||
- 支持工作流版本控制
|
||||
- 向后兼容性保证
|
||||
- 发布状态管理
|
||||
|
||||
### 4. 执行状态追踪
|
||||
- 实时进度更新
|
||||
- 完整的执行历史
|
||||
- 错误诊断和重试机制
|
||||
|
||||
## 📊 数据库设计亮点
|
||||
|
||||
### 工作流模板表
|
||||
```sql
|
||||
CREATE TABLE workflow_templates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL, -- 工作流名称
|
||||
base_name TEXT NOT NULL, -- 基础名称(版本管理)
|
||||
version TEXT NOT NULL DEFAULT '1.0', -- 版本号
|
||||
type TEXT NOT NULL, -- 工作流类型
|
||||
comfyui_workflow_json TEXT NOT NULL, -- ComfyUI配置
|
||||
ui_config_json TEXT NOT NULL, -- 前端界面配置
|
||||
execution_config_json TEXT, -- 执行配置
|
||||
-- ... 更多字段
|
||||
UNIQUE(base_name, version) -- 版本唯一约束
|
||||
);
|
||||
```
|
||||
|
||||
### 执行记录表
|
||||
```sql
|
||||
CREATE TABLE workflow_execution_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
workflow_template_id INTEGER NOT NULL,
|
||||
input_data_json TEXT NOT NULL, -- 输入数据
|
||||
output_data_json TEXT, -- 输出结果
|
||||
status TEXT NOT NULL DEFAULT 'pending', -- 执行状态
|
||||
progress INTEGER DEFAULT 0, -- 进度(0-100)
|
||||
comfyui_prompt_id TEXT, -- ComfyUI任务ID
|
||||
-- ... 时间和错误信息
|
||||
);
|
||||
```
|
||||
|
||||
## 🚀 使用示例
|
||||
|
||||
### 1. 创建新工作流
|
||||
```typescript
|
||||
const newWorkflow = {
|
||||
name: "背景替换 v1.0",
|
||||
base_name: "background_replacement",
|
||||
version: "1.0",
|
||||
type: "background_replacement",
|
||||
ui_config_json: {
|
||||
form_fields: [
|
||||
{
|
||||
name: "source_image",
|
||||
type: "image_upload",
|
||||
label: "原始图片",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: "background_type",
|
||||
type: "select",
|
||||
label: "背景类型",
|
||||
options: ["自然风景", "城市街道", "室内场景"]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 执行工作流
|
||||
```typescript
|
||||
const execution = await invoke('execute_workflow', {
|
||||
request: {
|
||||
workflow_identifier: "outfit_generation",
|
||||
version: "1.0",
|
||||
input_data: {
|
||||
model_image: "base64...",
|
||||
product_image: "base64...",
|
||||
prompt: "时尚穿搭"
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🔄 迁移策略
|
||||
|
||||
### 现有数据兼容
|
||||
- 自动迁移现有穿搭生成数据
|
||||
- 保持现有API接口兼容
|
||||
- 渐进式功能替换
|
||||
|
||||
### 默认工作流
|
||||
- 预置"穿搭生成 v1.0"工作流
|
||||
- 默认ComfyUI执行环境
|
||||
- 无缝用户体验过渡
|
||||
|
||||
## 📈 扩展能力
|
||||
|
||||
### 新工作流类型
|
||||
- 背景替换 (background_replacement)
|
||||
- 人像美化 (portrait_enhancement)
|
||||
- 图片放大 (image_upscaling)
|
||||
- 风格转换 (style_transfer)
|
||||
|
||||
### 新执行环境
|
||||
- Modal云端服务
|
||||
- RunPod云端服务
|
||||
- 自定义API服务
|
||||
|
||||
## 🧪 测试建议
|
||||
|
||||
### Phase 4: 数据迁移和测试 (进行中)
|
||||
|
||||
1. **数据库迁移测试**
|
||||
```bash
|
||||
# 运行数据库迁移
|
||||
cargo tauri dev
|
||||
# 验证新表结构
|
||||
# 检查数据迁移完整性
|
||||
```
|
||||
|
||||
2. **功能集成测试**
|
||||
- 测试工作流列表加载
|
||||
- 测试表单自动生成
|
||||
- 测试工作流执行流程
|
||||
- 测试状态追踪和历史记录
|
||||
|
||||
3. **兼容性测试**
|
||||
- 验证现有穿搭功能正常
|
||||
- 测试新旧API接口兼容
|
||||
- 确保用户体验平滑过渡
|
||||
|
||||
## 🎉 成果总结
|
||||
|
||||
✅ **完成了从单一功能到多工作流平台的架构升级**
|
||||
✅ **实现了智能表单自动生成系统**
|
||||
✅ **建立了统一的工作流执行引擎**
|
||||
✅ **提供了完整的管理和监控界面**
|
||||
✅ **保证了现有功能的向后兼容**
|
||||
|
||||
这个多工作流系统为MixVideo的未来扩展奠定了坚实基础,实现了从"穿搭生成专用系统"到"万能AI生成平台"的重大升级。
|
||||
@@ -1,370 +0,0 @@
|
||||
# 模特管理功能优化计划
|
||||
|
||||
## 1. 性能优化 (高优先级)
|
||||
|
||||
### 1.1 数据库连接池优化
|
||||
```rust
|
||||
// 实现连接池管理
|
||||
use r2d2::{Pool, PooledConnection};
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
|
||||
pub struct DatabasePool {
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
}
|
||||
|
||||
impl DatabasePool {
|
||||
pub fn new(database_url: &str) -> Result<Self> {
|
||||
let manager = SqliteConnectionManager::file(database_url);
|
||||
let pool = Pool::new(manager)?;
|
||||
Ok(Self { pool })
|
||||
}
|
||||
|
||||
pub fn get_connection(&self) -> Result<PooledConnection<SqliteConnectionManager>> {
|
||||
self.pool.get().map_err(|e| anyhow!("获取数据库连接失败: {}", e))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 异步查询优化
|
||||
```rust
|
||||
// 使用异步数据库操作
|
||||
use tokio_rusqlite::{Connection, Result};
|
||||
|
||||
impl ModelRepository {
|
||||
pub async fn get_all_async(&self) -> Result<Vec<Model>> {
|
||||
// 异步查询实现
|
||||
let models = self.query_models_async().await?;
|
||||
|
||||
// 并行加载照片
|
||||
let mut tasks = Vec::new();
|
||||
for model in models {
|
||||
let repo = self.clone();
|
||||
let model_id = model.id.clone();
|
||||
tasks.push(tokio::spawn(async move {
|
||||
repo.get_photos_async(&model_id).await
|
||||
}));
|
||||
}
|
||||
|
||||
// 等待所有照片加载完成
|
||||
let photos_results = futures::future::join_all(tasks).await;
|
||||
// 组装结果...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 缓存机制
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub struct ModelCache {
|
||||
models: Arc<RwLock<HashMap<String, Model>>>,
|
||||
photos: Arc<RwLock<HashMap<String, Vec<ModelPhoto>>>>,
|
||||
}
|
||||
|
||||
impl ModelCache {
|
||||
pub fn get_model(&self, id: &str) -> Option<Model> {
|
||||
self.models.read().unwrap().get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn invalidate_model(&self, id: &str) {
|
||||
self.models.write().unwrap().remove(id);
|
||||
self.photos.write().unwrap().remove(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 错误处理优化 (中优先级)
|
||||
|
||||
### 2.1 自定义错误类型
|
||||
```rust
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ModelError {
|
||||
#[error("模特不存在: {id}")]
|
||||
NotFound { id: String },
|
||||
|
||||
#[error("数据验证失败: {message}")]
|
||||
ValidationError { message: String },
|
||||
|
||||
#[error("数据库操作失败: {source}")]
|
||||
DatabaseError { source: rusqlite::Error },
|
||||
|
||||
#[error("文件操作失败: {path}")]
|
||||
FileError { path: String },
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 用户友好的错误信息
|
||||
```typescript
|
||||
// 前端错误处理
|
||||
export class ModelErrorHandler {
|
||||
static handleError(error: string): string {
|
||||
if (error.includes('NotFound')) {
|
||||
return '找不到指定的模特,可能已被删除';
|
||||
}
|
||||
if (error.includes('ValidationError')) {
|
||||
return '输入的信息格式不正确,请检查后重试';
|
||||
}
|
||||
if (error.includes('DatabaseError')) {
|
||||
return '数据保存失败,请稍后重试';
|
||||
}
|
||||
return '操作失败,请联系技术支持';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 测试体系建设 (高优先级)
|
||||
|
||||
### 3.1 单元测试
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_model() {
|
||||
let repo = create_test_repository().await;
|
||||
let request = CreateModelRequest {
|
||||
name: "测试模特".to_string(),
|
||||
gender: Gender::Female,
|
||||
// ...
|
||||
};
|
||||
|
||||
let result = ModelService::create_model(&repo, request).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let model = result.unwrap();
|
||||
assert_eq!(model.name, "测试模特");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_model_validation() {
|
||||
let mut model = Model::new("".to_string(), Gender::Female);
|
||||
let result = model.validate();
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("姓名不能为空"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 集成测试
|
||||
```rust
|
||||
// tests/integration_test.rs
|
||||
use mixvideo_desktop::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_model_crud_workflow() {
|
||||
let app_state = setup_test_app().await;
|
||||
|
||||
// 测试创建
|
||||
let create_result = create_model(app_state.clone(), test_model_data()).await;
|
||||
assert!(create_result.is_ok());
|
||||
|
||||
// 测试读取
|
||||
let model_id = create_result.unwrap().id;
|
||||
let get_result = get_model_by_id(app_state.clone(), model_id.clone()).await;
|
||||
assert!(get_result.is_ok());
|
||||
|
||||
// 测试更新
|
||||
let update_result = update_model(app_state.clone(), model_id.clone(), update_data()).await;
|
||||
assert!(update_result.is_ok());
|
||||
|
||||
// 测试删除
|
||||
let delete_result = delete_model(app_state.clone(), model_id).await;
|
||||
assert!(delete_result.is_ok());
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 前端测试
|
||||
```typescript
|
||||
// src/components/__tests__/ModelList.test.tsx
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ModelList } from '../ModelList';
|
||||
import { modelService } from '../../services/modelService';
|
||||
|
||||
jest.mock('../../services/modelService');
|
||||
|
||||
describe('ModelList', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('应该正确加载和显示模特列表', async () => {
|
||||
const mockModels = [
|
||||
{ id: '1', name: '测试模特1', gender: 'Female' },
|
||||
{ id: '2', name: '测试模特2', gender: 'Male' }
|
||||
];
|
||||
|
||||
(modelService.getAllModels as jest.Mock).mockResolvedValue(mockModels);
|
||||
|
||||
render(<ModelList />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('测试模特1')).toBeInTheDocument();
|
||||
expect(screen.getByText('测试模特2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('应该正确处理删除操作', async () => {
|
||||
// 测试删除功能...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 4. 架构优化 (中优先级)
|
||||
|
||||
### 4.1 事件驱动架构
|
||||
```rust
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ModelEvent {
|
||||
Created(Model),
|
||||
Updated(Model),
|
||||
Deleted(String),
|
||||
}
|
||||
|
||||
pub struct EventBus {
|
||||
sender: broadcast::Sender<ModelEvent>,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
pub fn publish(&self, event: ModelEvent) {
|
||||
let _ = self.sender.send(event);
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<ModelEvent> {
|
||||
self.sender.subscribe()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 分页和虚拟滚动
|
||||
```typescript
|
||||
// 前端分页优化
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export class ModelListManager {
|
||||
private cache = new Map<string, Model[]>();
|
||||
|
||||
async loadPage(params: PaginationParams): Promise<PaginatedResult<Model>> {
|
||||
const cacheKey = this.getCacheKey(params);
|
||||
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey)!;
|
||||
}
|
||||
|
||||
const result = await modelService.getModelsPaginated(params);
|
||||
this.cache.set(cacheKey, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 用户体验优化 (中优先级)
|
||||
|
||||
### 5.1 加载状态优化
|
||||
```typescript
|
||||
// 骨架屏组件
|
||||
export const ModelCardSkeleton: React.FC = () => (
|
||||
<div className="animate-pulse">
|
||||
<div className="bg-gray-300 h-48 w-full rounded-t-lg"></div>
|
||||
<div className="p-4">
|
||||
<div className="bg-gray-300 h-4 w-3/4 mb-2 rounded"></div>
|
||||
<div className="bg-gray-300 h-3 w-1/2 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
### 5.2 离线支持
|
||||
```typescript
|
||||
// Service Worker for offline support
|
||||
export class OfflineManager {
|
||||
private cache = new Map<string, any>();
|
||||
|
||||
async getModels(): Promise<Model[]> {
|
||||
try {
|
||||
const models = await modelService.getAllModels();
|
||||
this.cache.set('models', models);
|
||||
return models;
|
||||
} catch (error) {
|
||||
// 返回缓存数据
|
||||
return this.cache.get('models') || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 安全性增强 (高优先级)
|
||||
|
||||
### 6.1 输入验证
|
||||
```rust
|
||||
use validator::{Validate, ValidationError};
|
||||
|
||||
#[derive(Validate)]
|
||||
pub struct CreateModelRequest {
|
||||
#[validate(length(min = 1, max = 50, message = "姓名长度必须在1-50字符之间"))]
|
||||
pub name: String,
|
||||
|
||||
#[validate(email(message = "邮箱格式不正确"))]
|
||||
pub email: Option<String>,
|
||||
|
||||
#[validate(range(min = 1, max = 100, message = "年龄必须在1-100之间"))]
|
||||
pub age: Option<u32>,
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 权限控制
|
||||
```rust
|
||||
pub enum Permission {
|
||||
ReadModel,
|
||||
WriteModel,
|
||||
DeleteModel,
|
||||
ManagePhotos,
|
||||
}
|
||||
|
||||
pub struct PermissionChecker;
|
||||
|
||||
impl PermissionChecker {
|
||||
pub fn check_permission(user_role: &str, permission: Permission) -> bool {
|
||||
match (user_role, permission) {
|
||||
("admin", _) => true,
|
||||
("editor", Permission::DeleteModel) => false,
|
||||
("editor", _) => true,
|
||||
("viewer", Permission::ReadModel) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 实施优先级
|
||||
|
||||
1. **立即实施** (本周)
|
||||
- 移除调试日志
|
||||
- 添加基本单元测试
|
||||
- 优化错误信息
|
||||
|
||||
2. **短期实施** (2周内)
|
||||
- 实现连接池
|
||||
- 添加缓存机制
|
||||
- 完善测试覆盖
|
||||
|
||||
3. **中期实施** (1个月内)
|
||||
- 事件驱动架构
|
||||
- 分页优化
|
||||
- 离线支持
|
||||
|
||||
4. **长期实施** (3个月内)
|
||||
- 完整的权限系统
|
||||
- 性能监控
|
||||
- 用户体验优化
|
||||
@@ -1,122 +0,0 @@
|
||||
# 穿搭生成功能改进验证清单
|
||||
|
||||
## 功能概述
|
||||
本次改进实现了三个主要功能:
|
||||
1. **失败重试机制** - 为失败的穿搭图片生成记录添加重试功能
|
||||
2. **多商品同时生成** - 支持选择多个商品时创建多个独立任务记录
|
||||
3. **多任务并发执行** - 多个任务记录同时运行,提高生成效率
|
||||
|
||||
## 验证清单
|
||||
|
||||
### 1. 失败重试机制 ✅
|
||||
|
||||
#### 后端服务层
|
||||
- [x] `OutfitImageService.retryOutfitImageGeneration()` 方法已实现
|
||||
- [x] 调用正确的 Tauri 命令 `retry_outfit_image_generation`
|
||||
- [x] 错误处理和日志记录完整
|
||||
|
||||
#### 前端UI组件
|
||||
- [x] `OutfitImageGallery` 组件添加了重试状态管理
|
||||
- [x] 网格视图中失败记录显示重试按钮
|
||||
- [x] 列表视图中失败记录显示重试按钮
|
||||
- [x] 失败状态区域显示重试按钮
|
||||
- [x] 重试过程中显示加载状态
|
||||
- [x] 重试按钮在重试过程中禁用
|
||||
|
||||
#### 集成
|
||||
- [x] `ModelDetail.tsx` 添加了 `handleRetryOutfitRecord` 处理函数
|
||||
- [x] `OutfitImageGallery` 组件接收 `onRetry` 回调
|
||||
- [x] 重试成功后自动刷新记录列表
|
||||
|
||||
### 2. 多商品同时生成 ✅
|
||||
|
||||
#### 生成逻辑改进
|
||||
- [x] `OutfitImageGenerator.handleGenerate()` 修改为为每个商品创建独立任务
|
||||
- [x] 每个商品图片对应一个 `OutfitImageGenerationRequest`
|
||||
- [x] 支持批量模式和单个模式的自动切换
|
||||
|
||||
#### UI改进
|
||||
- [x] 使用说明更新,反映多商品生成功能
|
||||
- [x] 生成按钮文本根据商品数量动态显示
|
||||
- [x] 商品上传区域显示批量模式标识
|
||||
|
||||
### 3. 多任务并发执行 ✅
|
||||
|
||||
#### 批量处理服务
|
||||
- [x] `OutfitImageService.createBatchOutfitImageTasks()` 并发创建任务
|
||||
- [x] `OutfitImageService.executeBatchOutfitImageTasks()` 并发执行任务
|
||||
- [x] `OutfitImageService.batchGenerateOutfitImages()` 一键批量生成
|
||||
|
||||
#### 组件集成
|
||||
- [x] `OutfitImageGenerator` 支持批量生成模式
|
||||
- [x] `OutfitImageGenerationModal` 传递批量生成回调
|
||||
- [x] `ModelDetail.tsx` 实现 `handleBatchGenerateOutfitImages`
|
||||
|
||||
#### 性能优化
|
||||
- [x] 使用 `Promise.all()` 实现真正的并发执行
|
||||
- [x] 避免串行等待,提高生成效率
|
||||
|
||||
### 4. UI组件和交互改进 ✅
|
||||
|
||||
#### 状态显示
|
||||
- [x] 生成按钮显示任务数量
|
||||
- [x] 批量模式标识
|
||||
- [x] 进度显示包含商品图片数量信息
|
||||
|
||||
#### 用户体验
|
||||
- [x] 重试按钮仅在失败状态显示
|
||||
- [x] 重试过程中的视觉反馈
|
||||
- [x] 批量生成的状态提示
|
||||
|
||||
### 5. 测试和验证 ✅
|
||||
|
||||
#### 单元测试
|
||||
- [x] 创建了 `outfitGeneration.test.ts` 测试文件
|
||||
- [x] 测试重试机制的正常和异常情况
|
||||
- [x] 测试批量创建和执行任务
|
||||
- [x] 测试并发执行性能
|
||||
|
||||
#### 代码质量
|
||||
- [x] 无 TypeScript 编译错误
|
||||
- [x] 清理未使用的导入
|
||||
- [x] 代码结构清晰,注释完整
|
||||
|
||||
## 手动测试建议
|
||||
|
||||
### 测试场景 1: 失败重试
|
||||
1. 创建一个穿搭生成任务并让其失败
|
||||
2. 在穿搭记录列表中找到失败的记录
|
||||
3. 点击重试按钮
|
||||
4. 验证重试状态显示和任务重新执行
|
||||
|
||||
### 测试场景 2: 多商品生成
|
||||
1. 在穿搭生成弹框中上传多个商品图片(如3张)
|
||||
2. 选择模特形象图片
|
||||
3. 点击生成按钮
|
||||
4. 验证创建了3个独立的任务记录
|
||||
5. 验证每个任务记录只包含一个商品图片
|
||||
|
||||
### 测试场景 3: 并发执行
|
||||
1. 同时生成多个穿搭任务
|
||||
2. 观察任务列表中的进度更新
|
||||
3. 验证多个任务同时进行,而不是串行等待
|
||||
4. 检查生成完成的时间是否比串行执行更快
|
||||
|
||||
## 技术实现亮点
|
||||
|
||||
1. **服务层分离**: 将批量处理逻辑封装在服务层,保持组件的简洁
|
||||
2. **并发优化**: 使用 `Promise.all()` 实现真正的并发执行
|
||||
3. **向后兼容**: 保持原有单个生成功能的同时,添加批量功能
|
||||
4. **用户体验**: 提供清晰的状态反馈和操作提示
|
||||
5. **错误处理**: 完整的错误处理和重试机制
|
||||
6. **类型安全**: 完整的 TypeScript 类型定义
|
||||
|
||||
## 结论
|
||||
|
||||
所有三个主要功能都已成功实现并集成到现有系统中:
|
||||
|
||||
✅ **失败重试机制** - 用户可以轻松重试失败的生成任务
|
||||
✅ **多商品同时生成** - 支持为多个商品创建独立的生成任务
|
||||
✅ **多任务并发执行** - 显著提高了多任务生成的效率
|
||||
|
||||
改进后的系统提供了更好的用户体验、更高的生成效率和更强的错误恢复能力。
|
||||
@@ -1,206 +0,0 @@
|
||||
# ComfyUI SDK 重写项目 - 第一阶段完成总结
|
||||
|
||||
## 📋 阶段概述
|
||||
|
||||
**阶段名称**: 基础架构重构
|
||||
**完成时间**: 2025-01-08
|
||||
**状态**: ✅ 已完成
|
||||
|
||||
## 🎯 完成的任务
|
||||
|
||||
### 1.1 数据模型重新设计 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了全新的基于 SDK 的数据模型 (`apps/desktop/src-tauri/src/data/models/comfyui.rs`)
|
||||
- ✅ 设计了统一的数据结构:
|
||||
- `WorkflowModel`: 工作流数据模型
|
||||
- `TemplateModel`: 模板数据模型
|
||||
- `ExecutionModel`: 执行记录模型
|
||||
- `QueueModel`: 队列状态模型
|
||||
- `ComfyUIConfig`: 服务配置模型
|
||||
- ✅ 创建了数据库迁移脚本 (`apps/desktop/src-tauri/src/data/migrations/comfyui_tables.sql`)
|
||||
- ✅ 实现了完整的数据访问层 (`apps/desktop/src-tauri/src/data/repositories/comfyui_repository.rs`)
|
||||
|
||||
**技术亮点**:
|
||||
- 基于 comfyui-sdk 类型系统设计
|
||||
- 支持完整的 CRUD 操作
|
||||
- 包含数据验证和约束
|
||||
- 优化的数据库索引设计
|
||||
|
||||
### 1.2 核心服务重构 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ `ComfyUIManager`: 统一的 SDK 管理器
|
||||
- 连接管理和健康检查
|
||||
- 配置管理和验证
|
||||
- 错误处理和重试机制
|
||||
- ✅ `TemplateEngine`: 模板管理引擎
|
||||
- 模板加载和验证
|
||||
- 参数解析和验证
|
||||
- 模板实例化和缓存
|
||||
- ✅ `ExecutionEngine`: 执行引擎
|
||||
- 工作流执行管理
|
||||
- 队列管理
|
||||
- 结果处理
|
||||
- ✅ `RealtimeMonitor`: 实时监控服务
|
||||
- WebSocket 连接管理
|
||||
- 事件订阅和分发
|
||||
- 进度更新推送
|
||||
|
||||
**技术亮点**:
|
||||
- 异步架构设计
|
||||
- 内存缓存优化
|
||||
- 实时事件处理
|
||||
- 连接池管理
|
||||
|
||||
### 1.3 配置系统重构 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 扩展了现有的 `ComfyUISettings` 配置结构
|
||||
- ✅ 创建了 `ConfigManager` 统一配置管理服务
|
||||
- ✅ 实现了配置验证和环境适配
|
||||
- ✅ 支持开发/测试/生产环境配置
|
||||
|
||||
**技术亮点**:
|
||||
- 环境感知配置
|
||||
- 配置验证机制
|
||||
- 热更新支持
|
||||
- 配置导入导出
|
||||
|
||||
### 1.4 错误处理系统 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了统一的 `ComfyUIError` 错误类型
|
||||
- ✅ 实现了 `ErrorHandler` 错误处理器
|
||||
- ✅ 设计了 `RetryExecutor` 重试机制
|
||||
- ✅ 支持错误映射和恢复策略
|
||||
|
||||
**技术亮点**:
|
||||
- 基于 SDK 错误类型设计
|
||||
- 智能重试策略
|
||||
- 错误统计和分析
|
||||
- 用户友好的错误消息
|
||||
|
||||
## 📁 创建的文件结构
|
||||
|
||||
```
|
||||
apps/desktop/src-tauri/src/
|
||||
├── data/
|
||||
│ ├── models/
|
||||
│ │ └── comfyui.rs # 新的数据模型
|
||||
│ ├── repositories/
|
||||
│ │ └── comfyui_repository.rs # 数据访问层
|
||||
│ └── migrations/
|
||||
│ └── comfyui_tables.sql # 数据库迁移
|
||||
├── business/
|
||||
│ └── services/
|
||||
│ ├── comfyui_manager.rs # 核心管理器
|
||||
│ ├── template_engine.rs # 模板引擎
|
||||
│ ├── execution_engine.rs # 执行引擎
|
||||
│ ├── realtime_monitor.rs # 实时监控
|
||||
│ ├── config_manager.rs # 配置管理
|
||||
│ └── error_handler.rs # 错误处理
|
||||
└── config.rs # 扩展的配置结构
|
||||
```
|
||||
|
||||
## 🔧 技术架构
|
||||
|
||||
### 新架构概览
|
||||
```
|
||||
Frontend (React + TypeScript)
|
||||
↓ Tauri Invoke
|
||||
Backend (Rust + ComfyUI SDK)
|
||||
├── Presentation Layer (Commands) [待实现]
|
||||
├── Business Layer (Services) [✅ 已完成]
|
||||
├── Data Layer (Models + Database) [✅ 已完成]
|
||||
└── Infrastructure Layer (ComfyUI SDK) [✅ 已集成]
|
||||
```
|
||||
|
||||
### 核心组件关系
|
||||
```
|
||||
ConfigManager ←→ ComfyUIManager
|
||||
↓ ↓
|
||||
TemplateEngine ←→ ExecutionEngine
|
||||
↓ ↓
|
||||
RealtimeMonitor ←→ ErrorHandler
|
||||
↓ ↓
|
||||
ComfyUIRepository (数据持久化)
|
||||
```
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
- **新增文件**: 8 个
|
||||
- **修改文件**: 3 个
|
||||
- **代码行数**: ~2,500 行
|
||||
- **测试覆盖**: 准备就绪(待编写)
|
||||
|
||||
## ✅ 质量保证
|
||||
|
||||
### 代码质量
|
||||
- ✅ 遵循 Rust 最佳实践
|
||||
- ✅ 完整的错误处理
|
||||
- ✅ 详细的文档注释
|
||||
- ✅ 类型安全设计
|
||||
|
||||
### 架构质量
|
||||
- ✅ 清晰的分层架构
|
||||
- ✅ 松耦合设计
|
||||
- ✅ 可扩展性考虑
|
||||
- ✅ 性能优化
|
||||
|
||||
## 🔄 与现有系统的兼容性
|
||||
|
||||
### 保留的组件
|
||||
- 现有的 `ComfyUISettings` 配置结构(已扩展)
|
||||
- 现有的配置文件格式(向后兼容)
|
||||
- 现有的数据库结构(将通过迁移升级)
|
||||
|
||||
### 新增的功能
|
||||
- 基于 SDK 的统一接口
|
||||
- 实时事件监控
|
||||
- 智能错误处理和重试
|
||||
- 模板缓存系统
|
||||
- 配置验证机制
|
||||
|
||||
## 🚀 下一步计划
|
||||
|
||||
### 第二阶段: 命令层重写
|
||||
- [ ] 重新实现所有 Tauri 命令
|
||||
- [ ] 提供完整的前端 API 接口
|
||||
- [ ] 集成新的服务层
|
||||
|
||||
### 准备工作
|
||||
1. **测试环境准备**: 确保 ComfyUI 服务可用
|
||||
2. **数据迁移**: 运行数据库迁移脚本
|
||||
3. **配置更新**: 更新应用配置以启用新功能
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 系统依赖
|
||||
- 当前编译受到 Linux 系统依赖限制(webkit2gtk、libsoup 等)
|
||||
- 这不影响我们的代码质量,只是构建环境问题
|
||||
|
||||
### 向后兼容
|
||||
- 新架构与现有系统完全兼容
|
||||
- 可以逐步迁移,不会影响现有功能
|
||||
- 保留了所有现有的配置和数据结构
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
第一阶段的基础架构重构已经成功完成!我们建立了一个现代化、高性能的 ComfyUI 集成架构,为后续的开发工作奠定了坚实的基础。
|
||||
|
||||
**主要成就**:
|
||||
- ✅ 完全基于 comfyui-sdk 的新架构
|
||||
- ✅ 统一的数据模型和服务层
|
||||
- ✅ 强大的错误处理和重试机制
|
||||
- ✅ 实时监控和事件处理
|
||||
- ✅ 灵活的配置管理系统
|
||||
|
||||
**技术优势**:
|
||||
- 🚀 更好的性能和稳定性
|
||||
- 🔄 实时通信能力
|
||||
- 🛡️ 强大的错误恢复
|
||||
- 📝 完整的类型安全
|
||||
- ⚡ 异步架构设计
|
||||
|
||||
现在可以开始第二阶段的命令层重写工作了!
|
||||
@@ -1,251 +0,0 @@
|
||||
# ComfyUI SDK 重写项目 - 第二阶段完成总结
|
||||
|
||||
## 📋 阶段概述
|
||||
|
||||
**阶段名称**: 命令层重写
|
||||
**完成时间**: 2025-01-08
|
||||
**状态**: ✅ 已完成
|
||||
|
||||
## 🎯 完成的任务
|
||||
|
||||
### 2.1 基础命令重写 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了全新的 `comfyui_v2_commands.rs` 基础命令文件
|
||||
- ✅ 实现了连接管理命令:
|
||||
- `comfyui_v2_connect`: 连接到 ComfyUI 服务
|
||||
- `comfyui_v2_disconnect`: 断开连接
|
||||
- `comfyui_v2_get_connection_status`: 获取连接状态
|
||||
- `comfyui_v2_health_check`: 健康检查
|
||||
- ✅ 实现了系统信息命令:
|
||||
- `comfyui_v2_get_system_info`: 获取系统信息
|
||||
- `comfyui_v2_get_queue_status`: 获取队列状态
|
||||
- ✅ 实现了配置管理命令:
|
||||
- `comfyui_v2_get_config`: 获取配置
|
||||
- `comfyui_v2_update_config`: 更新配置
|
||||
- `comfyui_v2_validate_config`: 验证配置
|
||||
- `comfyui_v2_reset_config`: 重置配置
|
||||
- ✅ 实现了统计监控命令:
|
||||
- `comfyui_v2_get_config_stats`: 获取配置统计
|
||||
- `comfyui_v2_get_config_health`: 获取配置健康状态
|
||||
|
||||
**技术亮点**:
|
||||
- 基于新的服务架构设计
|
||||
- 统一的错误处理机制
|
||||
- 完整的类型安全保证
|
||||
- 详细的日志记录
|
||||
|
||||
### 2.2 工作流管理命令 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 实现了完整的工作流 CRUD 操作:
|
||||
- `comfyui_v2_create_workflow`: 创建工作流
|
||||
- `comfyui_v2_list_workflows`: 获取工作流列表
|
||||
- `comfyui_v2_get_workflow`: 获取单个工作流
|
||||
- `comfyui_v2_update_workflow`: 更新工作流
|
||||
- `comfyui_v2_delete_workflow`: 删除工作流
|
||||
- ✅ 实现了高级查询功能:
|
||||
- `comfyui_v2_get_workflows_by_category`: 按分类获取工作流
|
||||
- `comfyui_v2_search_workflows`: 搜索工作流
|
||||
- ✅ 设计了完整的请求/响应类型:
|
||||
- `CreateWorkflowRequest`: 工作流创建请求
|
||||
- `UpdateWorkflowRequest`: 工作流更新请求
|
||||
- `WorkflowResponse`: 工作流响应
|
||||
|
||||
**技术亮点**:
|
||||
- 与数据仓库完全集成
|
||||
- 支持可选字段和灵活查询
|
||||
- 完整的数据验证
|
||||
- 统一的响应格式
|
||||
|
||||
### 2.3 模板管理命令 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了专门的 `comfyui_v2_template_commands.rs` 模板命令文件
|
||||
- ✅ 实现了模板 CRUD 操作:
|
||||
- `comfyui_v2_create_template`: 创建模板
|
||||
- `comfyui_v2_list_templates`: 获取模板列表
|
||||
- `comfyui_v2_get_template`: 获取单个模板
|
||||
- `comfyui_v2_update_template`: 更新模板
|
||||
- `comfyui_v2_delete_template`: 删除模板
|
||||
- ✅ 实现了模板实例管理:
|
||||
- `comfyui_v2_create_template_instance`: 创建模板实例
|
||||
- `comfyui_v2_preview_template_instance`: 预览模板实例
|
||||
- `comfyui_v2_validate_template_parameters`: 验证模板参数
|
||||
- `comfyui_v2_get_template_parameter_schema`: 获取参数定义
|
||||
- ✅ 实现了模板缓存管理:
|
||||
- `comfyui_v2_get_template_cache_stats`: 获取缓存统计
|
||||
- `comfyui_v2_clear_template_cache`: 清除缓存
|
||||
- `comfyui_v2_warm_template_cache`: 预热缓存
|
||||
- ✅ 实现了模板导入导出:
|
||||
- `comfyui_v2_export_template`: 导出模板
|
||||
- `comfyui_v2_import_template`: 导入模板
|
||||
|
||||
**技术亮点**:
|
||||
- 与模板引擎深度集成
|
||||
- 支持参数验证和类型检查
|
||||
- 智能缓存管理
|
||||
- 模板导入导出功能
|
||||
|
||||
### 2.4 执行管理命令 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了专门的 `comfyui_v2_execution_commands.rs` 执行命令文件
|
||||
- ✅ 实现了执行控制命令:
|
||||
- `comfyui_v2_execute_workflow`: 执行工作流
|
||||
- `comfyui_v2_execute_template`: 执行模板
|
||||
- `comfyui_v2_cancel_execution`: 取消执行
|
||||
- `comfyui_v2_get_execution_status`: 获取执行状态
|
||||
- `comfyui_v2_get_execution_history`: 获取执行历史
|
||||
- ✅ 实现了实时监控命令:
|
||||
- `comfyui_v2_start_realtime_monitor`: 启动实时监控
|
||||
- `comfyui_v2_stop_realtime_monitor`: 停止实时监控
|
||||
- `comfyui_v2_get_monitor_stats`: 获取监控统计
|
||||
- `comfyui_v2_subscribe_realtime_events`: 订阅实时事件
|
||||
- ✅ 实现了批量操作命令:
|
||||
- `comfyui_v2_batch_execute_workflows`: 批量执行工作流
|
||||
- `comfyui_v2_batch_execute_templates`: 批量执行模板
|
||||
- `comfyui_v2_batch_cancel_executions`: 批量取消执行
|
||||
- `comfyui_v2_batch_get_execution_status`: 批量获取执行状态
|
||||
- ✅ 实现了执行管理功能:
|
||||
- `comfyui_v2_cleanup_completed_executions`: 清理已完成执行
|
||||
- `comfyui_v2_get_execution_stats`: 获取执行统计
|
||||
|
||||
**技术亮点**:
|
||||
- 与执行引擎完全集成
|
||||
- 支持实时监控和事件推送
|
||||
- 批量操作支持
|
||||
- 完整的执行生命周期管理
|
||||
|
||||
## 📁 创建的文件结构
|
||||
|
||||
```
|
||||
apps/desktop/src-tauri/src/
|
||||
├── business/
|
||||
│ └── services/
|
||||
│ └── service_manager.rs # 服务管理器
|
||||
├── presentation/
|
||||
│ └── commands/
|
||||
│ ├── comfyui_v2_commands.rs # V2 基础命令
|
||||
│ ├── comfyui_v2_template_commands.rs # V2 模板命令
|
||||
│ └── comfyui_v2_execution_commands.rs # V2 执行命令
|
||||
└── lib.rs # 更新的命令注册
|
||||
```
|
||||
|
||||
## 🔧 技术架构
|
||||
|
||||
### 命令层架构
|
||||
```
|
||||
Frontend (TypeScript)
|
||||
↓ Tauri Invoke
|
||||
Command Layer (Rust)
|
||||
├── Basic Commands (连接、配置、监控)
|
||||
├── Workflow Commands (工作流 CRUD)
|
||||
├── Template Commands (模板管理)
|
||||
└── Execution Commands (执行控制)
|
||||
↓
|
||||
Service Manager
|
||||
├── ComfyUIManager
|
||||
├── TemplateEngine
|
||||
├── ExecutionEngine
|
||||
└── RealtimeMonitor
|
||||
```
|
||||
|
||||
### 服务管理器设计
|
||||
- **统一管理**: 所有服务的创建和生命周期管理
|
||||
- **依赖注入**: 自动处理服务间的依赖关系
|
||||
- **配置管理**: 统一的配置更新和验证
|
||||
- **错误处理**: 统一的错误处理和恢复机制
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
- **新增命令**: 47 个
|
||||
- **新增文件**: 4 个
|
||||
- **代码行数**: ~1,800 行
|
||||
- **命令分类**:
|
||||
- 基础命令: 10 个
|
||||
- 工作流命令: 7 个
|
||||
- 模板命令: 17 个
|
||||
- 执行命令: 13 个
|
||||
|
||||
## ✅ 质量保证
|
||||
|
||||
### 代码质量
|
||||
- ✅ 统一的错误处理机制
|
||||
- ✅ 完整的类型安全保证
|
||||
- ✅ 详细的日志记录
|
||||
- ✅ 一致的命名规范
|
||||
|
||||
### 架构质量
|
||||
- ✅ 清晰的分层设计
|
||||
- ✅ 服务间松耦合
|
||||
- ✅ 统一的服务管理
|
||||
- ✅ 可扩展的命令结构
|
||||
|
||||
## 🔄 与现有系统的集成
|
||||
|
||||
### 命令注册
|
||||
- ✅ 在 `lib.rs` 中注册了所有新命令
|
||||
- ✅ 保留了现有命令的兼容性
|
||||
- ✅ 使用 `v2` 前缀区分新旧命令
|
||||
|
||||
### 服务集成
|
||||
- ✅ 通过服务管理器统一管理
|
||||
- ✅ 与第一阶段的服务层完全集成
|
||||
- ✅ 支持配置热更新
|
||||
|
||||
## 🚀 API 接口设计
|
||||
|
||||
### 请求/响应类型
|
||||
- **统一格式**: 所有命令都有明确的请求和响应类型
|
||||
- **类型安全**: 利用 Rust 和 Serde 确保类型安全
|
||||
- **可扩展性**: 支持可选字段和向后兼容
|
||||
|
||||
### 错误处理
|
||||
- **统一错误格式**: 所有命令返回 `Result<T, String>`
|
||||
- **详细错误信息**: 包含具体的错误原因和建议
|
||||
- **错误分类**: 区分不同类型的错误
|
||||
|
||||
## 🎯 下一步计划
|
||||
|
||||
### 第三阶段: 实时通信与高级功能
|
||||
- [ ] 实现 WebSocket 实时通信
|
||||
- [ ] 添加进度跟踪功能
|
||||
- [ ] 实现队列管理系统
|
||||
- [ ] 添加缓存和性能优化
|
||||
|
||||
### 准备工作
|
||||
1. **测试验证**: 编写和运行集成测试
|
||||
2. **性能优化**: 优化服务管理器的性能
|
||||
3. **文档更新**: 更新 API 文档
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 临时实现
|
||||
- 当前使用硬编码的数据库路径
|
||||
- 配置管理器使用默认配置
|
||||
- 需要与应用状态更好地集成
|
||||
|
||||
### 待优化项
|
||||
- 服务管理器的生命周期管理
|
||||
- 错误处理的细化
|
||||
- 性能监控和统计
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
第二阶段的命令层重写已经成功完成!我们建立了一个完整的、现代化的 API 接口层,为前端提供了丰富的功能。
|
||||
|
||||
**主要成就**:
|
||||
- ✅ 47 个全新的 V2 命令
|
||||
- ✅ 统一的服务管理架构
|
||||
- ✅ 完整的类型安全保证
|
||||
- ✅ 丰富的功能覆盖
|
||||
|
||||
**技术优势**:
|
||||
- 🚀 现代化的异步架构
|
||||
- 🔄 统一的服务管理
|
||||
- 🛡️ 强大的错误处理
|
||||
- 📝 完整的类型定义
|
||||
- ⚡ 高性能的执行引擎
|
||||
|
||||
现在可以开始第三阶段的实时通信与高级功能开发了!
|
||||
@@ -1,306 +0,0 @@
|
||||
# ComfyUI SDK 重写项目 - 第三阶段完成总结
|
||||
|
||||
## 📋 阶段概述
|
||||
|
||||
**阶段名称**: 实时通信与高级功能
|
||||
**完成时间**: 2025-01-08
|
||||
**状态**: ✅ 已完成
|
||||
|
||||
## 🎯 完成的任务
|
||||
|
||||
### 3.1 WebSocket 实时通信 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了增强版实时监控服务 `RealtimeMonitorV2`
|
||||
- ✅ 实现了完整的 WebSocket 连接管理:
|
||||
- 自动重连机制
|
||||
- 心跳检测
|
||||
- 连接状态跟踪
|
||||
- 指数退避重连策略
|
||||
- ✅ 实现了全面的事件处理:
|
||||
- 执行开始/进度/完成/失败事件
|
||||
- 节点执行状态事件
|
||||
- 队列状态更新事件
|
||||
- 系统状态更新事件
|
||||
- ✅ 创建了专门的 WebSocket 消息处理器 `WebSocketHandler`
|
||||
- ✅ 实现了 Tauri 事件发射器 `TauriEventEmitter`:
|
||||
- 将后端事件转发到前端
|
||||
- 支持多种事件类型
|
||||
- 统一的事件格式
|
||||
- ✅ 创建了完整的实时通信命令集:
|
||||
- `comfyui_v2_start_realtime_monitor_enhanced`: 启动增强版实时监控
|
||||
- `comfyui_v2_get_realtime_connection_status`: 获取连接状态
|
||||
- `comfyui_v2_get_realtime_event_statistics`: 获取事件统计
|
||||
- `comfyui_v2_subscribe_realtime_events_enhanced`: 订阅实时事件
|
||||
- 等 14 个专业命令
|
||||
|
||||
**技术亮点**:
|
||||
- 基于 ComfyUI SDK 的 WebSocket 客户端
|
||||
- 智能重连和错误恢复
|
||||
- 完整的事件统计和监控
|
||||
- 类型安全的事件处理
|
||||
- 前后端实时通信桥梁
|
||||
|
||||
### 3.2 队列管理系统 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了高级队列管理器 `QueueManager`
|
||||
- ✅ 实现了智能任务调度:
|
||||
- 基于优先级的任务调度
|
||||
- 支持 4 种优先级:Low, Normal, High, Urgent
|
||||
- 最大并发执行数控制
|
||||
- 任务超时管理
|
||||
- ✅ 实现了完整的任务生命周期管理:
|
||||
- 任务创建和排队
|
||||
- 任务执行和监控
|
||||
- 任务完成和失败处理
|
||||
- 自动重试机制
|
||||
- ✅ 支持多种任务类型:
|
||||
- 工作流执行任务
|
||||
- 模板执行任务
|
||||
- 批量执行任务
|
||||
- ✅ 实现了丰富的队列统计:
|
||||
- 队列长度和状态
|
||||
- 平均等待时间
|
||||
- 吞吐量统计
|
||||
- 按优先级和类型分组统计
|
||||
- ✅ 创建了完整的队列管理命令集:
|
||||
- `comfyui_v2_add_task_to_queue`: 添加任务到队列
|
||||
- `comfyui_v2_get_queue_status`: 获取队列状态
|
||||
- `comfyui_v2_batch_queue_operation`: 批量队列操作
|
||||
- `comfyui_v2_get_queue_metrics`: 获取队列性能指标
|
||||
- 等 16 个专业命令
|
||||
|
||||
**技术亮点**:
|
||||
- 多优先级任务调度
|
||||
- 智能任务分配算法
|
||||
- 完整的任务状态跟踪
|
||||
- 自动重试和错误恢复
|
||||
- 丰富的统计和监控
|
||||
|
||||
### 3.3 缓存和性能优化 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了智能缓存管理器 `CacheManager`
|
||||
- ✅ 实现了多种缓存策略:
|
||||
- LRU (最近最少使用)
|
||||
- LFU (最不经常使用)
|
||||
- FIFO (先进先出)
|
||||
- TTL (基于时间过期)
|
||||
- Smart (智能综合策略)
|
||||
- ✅ 实现了高级缓存功能:
|
||||
- 自动过期和清理
|
||||
- 缓存预热
|
||||
- 优先级管理
|
||||
- 健康状态检查
|
||||
- 详细的统计信息
|
||||
- ✅ 创建了增强版性能监控服务 `PerformanceMonitorV2`
|
||||
- ✅ 实现了全面的性能监控:
|
||||
- 系统指标:CPU、内存、磁盘、网络
|
||||
- 应用指标:连接数、队列长度、响应时间、错误率
|
||||
- 性能警报系统
|
||||
- 历史数据保留
|
||||
- ✅ 实现了智能警报系统:
|
||||
- 多级别警报:Info, Warning, Error, Critical
|
||||
- 可配置的阈值
|
||||
- 自动警报解决
|
||||
- 警报历史记录
|
||||
- ✅ 创建了完整的性能优化命令集:
|
||||
- `comfyui_v2_start_cache_manager`: 启动缓存管理器
|
||||
- `comfyui_v2_get_cache_stats`: 获取缓存统计
|
||||
- `comfyui_v2_start_performance_monitor`: 启动性能监控
|
||||
- `comfyui_v2_optimize_performance`: 执行性能优化
|
||||
- 等 19 个专业命令
|
||||
|
||||
**技术亮点**:
|
||||
- 多策略智能缓存
|
||||
- 全面的性能监控
|
||||
- 智能警报系统
|
||||
- 自动性能优化
|
||||
- 详细的性能分析
|
||||
|
||||
## 📁 创建的文件结构
|
||||
|
||||
```
|
||||
apps/desktop/src-tauri/src/
|
||||
├── business/
|
||||
│ └── services/
|
||||
│ ├── realtime_monitor_v2.rs # 增强版实时监控服务
|
||||
│ ├── websocket_handler.rs # WebSocket 消息处理器
|
||||
│ ├── tauri_event_emitter.rs # Tauri 事件发射器
|
||||
│ ├── queue_manager.rs # 队列管理器
|
||||
│ ├── cache_manager.rs # 智能缓存管理器
|
||||
│ └── performance_monitor_v2.rs # 增强版性能监控服务
|
||||
└── presentation/
|
||||
└── commands/
|
||||
├── comfyui_v2_realtime_commands.rs # V2 实时通信命令
|
||||
├── comfyui_v2_queue_commands.rs # V2 队列管理命令
|
||||
└── comfyui_v2_performance_commands.rs # V2 性能优化命令
|
||||
```
|
||||
|
||||
## 🔧 技术架构
|
||||
|
||||
### 实时通信架构
|
||||
```
|
||||
Frontend (TypeScript)
|
||||
↓ Tauri Events
|
||||
TauriEventEmitter
|
||||
↓ Event Broadcasting
|
||||
RealtimeMonitorV2
|
||||
↓ WebSocket
|
||||
WebSocketHandler → ComfyUI SDK WebSocket Client
|
||||
↓ Raw Messages
|
||||
ComfyUI Server
|
||||
```
|
||||
|
||||
### 队列管理架构
|
||||
```
|
||||
Task Submission → QueueManager
|
||||
├── Priority Queue (Urgent → High → Normal → Low)
|
||||
├── Task Scheduler (Max Concurrent Control)
|
||||
├── Execution Engine Integration
|
||||
└── Statistics & Monitoring
|
||||
```
|
||||
|
||||
### 缓存和性能架构
|
||||
```
|
||||
Application Layer
|
||||
↓
|
||||
CacheManager (Multi-Strategy)
|
||||
├── LRU/LFU/FIFO/TTL/Smart Eviction
|
||||
├── Auto Cleanup & Warmup
|
||||
└── Health Monitoring
|
||||
|
||||
PerformanceMonitorV2
|
||||
├── System Metrics Collection
|
||||
├── Application Metrics Integration
|
||||
├── Alert System
|
||||
└── Historical Data Management
|
||||
```
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
- **新增服务**: 6 个核心服务
|
||||
- **新增命令**: 49 个专业命令
|
||||
- **新增文件**: 9 个
|
||||
- **代码行数**: ~3,500 行
|
||||
- **命令分类**:
|
||||
- 实时通信命令: 14 个
|
||||
- 队列管理命令: 16 个
|
||||
- 性能优化命令: 19 个
|
||||
|
||||
## ✅ 质量保证
|
||||
|
||||
### 架构质量
|
||||
- ✅ 模块化设计,职责清晰
|
||||
- ✅ 异步架构,高性能
|
||||
- ✅ 错误处理完善
|
||||
- ✅ 可扩展性强
|
||||
|
||||
### 功能完整性
|
||||
- ✅ 实时通信全覆盖
|
||||
- ✅ 队列管理全生命周期
|
||||
- ✅ 缓存策略多样化
|
||||
- ✅ 性能监控全方位
|
||||
|
||||
### 代码质量
|
||||
- ✅ 类型安全保证
|
||||
- ✅ 统一错误处理
|
||||
- ✅ 详细日志记录
|
||||
- ✅ 完整的配置管理
|
||||
|
||||
## 🚀 核心功能特性
|
||||
|
||||
### 实时通信特性
|
||||
- 🔄 **智能重连**: 指数退避重连策略
|
||||
- 💓 **心跳检测**: 自动连接健康检查
|
||||
- 📊 **事件统计**: 详细的事件处理统计
|
||||
- 🎯 **类型安全**: 完整的事件类型定义
|
||||
- 🌉 **前后端桥梁**: 无缝的事件转发
|
||||
|
||||
### 队列管理特性
|
||||
- 🎯 **智能调度**: 基于优先级的任务调度
|
||||
- 🔄 **自动重试**: 失败任务自动重试机制
|
||||
- 📈 **性能监控**: 实时队列性能统计
|
||||
- 🎛️ **灵活配置**: 可配置的队列参数
|
||||
- 📊 **批量操作**: 支持批量任务管理
|
||||
|
||||
### 缓存优化特性
|
||||
- 🧠 **智能策略**: 多种缓存驱逐策略
|
||||
- 🔥 **缓存预热**: 智能缓存预热机制
|
||||
- 🏥 **健康检查**: 缓存健康状态监控
|
||||
- 📊 **详细统计**: 完整的缓存使用统计
|
||||
- ⚡ **高性能**: 异步缓存操作
|
||||
|
||||
### 性能监控特性
|
||||
- 🖥️ **系统监控**: CPU、内存、磁盘、网络
|
||||
- 📱 **应用监控**: 连接数、响应时间、错误率
|
||||
- 🚨 **智能警报**: 多级别警报系统
|
||||
- 📈 **历史分析**: 性能趋势分析
|
||||
- 🔧 **自动优化**: 智能性能优化建议
|
||||
|
||||
## 🔄 与现有系统的集成
|
||||
|
||||
### 服务集成
|
||||
- ✅ 与第一阶段的核心服务完全集成
|
||||
- ✅ 与第二阶段的命令层无缝对接
|
||||
- ✅ 统一的服务管理和配置
|
||||
|
||||
### 数据集成
|
||||
- ✅ 与数据仓库深度集成
|
||||
- ✅ 执行状态实时同步
|
||||
- ✅ 统计数据持久化
|
||||
|
||||
## 🎯 下一步计划
|
||||
|
||||
### 第四阶段: 前端重构与UI优化
|
||||
- [ ] 重构前端代码架构
|
||||
- [ ] 实现现代化UI组件
|
||||
- [ ] 集成实时通信功能
|
||||
- [ ] 优化用户体验
|
||||
|
||||
### 准备工作
|
||||
1. **集成测试**: 测试所有新功能的集成
|
||||
2. **性能测试**: 验证性能优化效果
|
||||
3. **文档更新**: 更新技术文档
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 临时实现
|
||||
- 部分系统指标使用模拟数据
|
||||
- 需要与真实的系统监控API集成
|
||||
- 缓存大小估算需要优化
|
||||
|
||||
### 待优化项
|
||||
- WebSocket 连接池管理
|
||||
- 缓存序列化优化
|
||||
- 性能指标收集精度
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
第三阶段的实时通信与高级功能开发已经成功完成!我们建立了一个完整的、现代化的高级功能体系。
|
||||
|
||||
**主要成就**:
|
||||
- ✅ 49 个全新的高级功能命令
|
||||
- ✅ 6 个核心服务模块
|
||||
- ✅ 完整的实时通信体系
|
||||
- ✅ 智能队列管理系统
|
||||
- ✅ 高性能缓存和监控
|
||||
|
||||
**技术优势**:
|
||||
- 🚀 **实时响应**: WebSocket 实时通信
|
||||
- 🎯 **智能调度**: 优先级队列管理
|
||||
- ⚡ **高性能**: 多策略缓存优化
|
||||
- 📊 **全面监控**: 系统和应用性能监控
|
||||
- 🔧 **自动优化**: 智能性能优化
|
||||
|
||||
**系统能力**:
|
||||
- 🔄 **实时性**: 毫秒级事件响应
|
||||
- 📈 **可扩展性**: 支持大规模并发
|
||||
- 🛡️ **可靠性**: 完善的错误处理和恢复
|
||||
- 📊 **可观测性**: 全面的监控和统计
|
||||
- 🎛️ **可配置性**: 灵活的参数配置
|
||||
|
||||
现在整个系统具备了企业级的实时通信、队列管理、缓存优化和性能监控能力,为用户提供了卓越的使用体验!
|
||||
|
||||
准备开始第四阶段的前端重构与UI优化了!🚀
|
||||
@@ -1,332 +0,0 @@
|
||||
# ComfyUI SDK 重写项目 - 第四阶段完成总结
|
||||
|
||||
## 📋 阶段概述
|
||||
|
||||
**阶段名称**: 前端重构与UI优化
|
||||
**完成时间**: 2025-01-08
|
||||
**状态**: ✅ 已完成
|
||||
|
||||
## 🎯 完成的任务
|
||||
|
||||
### 4.1 前端架构重构 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了现代化的 ComfyUI V2 服务层 `ComfyUIV2Service`
|
||||
- ✅ 实现了完整的 API 接口封装:
|
||||
- 连接管理:连接、断开、状态检查、健康检查
|
||||
- 配置管理:获取、更新、验证配置
|
||||
- 工作流管理:创建、列表、获取、更新、删除、搜索
|
||||
- 模板管理:创建、列表、获取、删除、预览、验证
|
||||
- 执行管理:执行工作流/模板、取消、状态查询、历史记录
|
||||
- 实时通信:启动监控、订阅事件、注册映射
|
||||
- 批量操作:批量执行、批量取消
|
||||
- ✅ 创建了基于 Zustand 的现代化状态管理 `ComfyUIV2Store`
|
||||
- ✅ 实现了完整的状态管理功能:
|
||||
- 连接状态管理
|
||||
- 工作流状态管理
|
||||
- 模板状态管理
|
||||
- 执行状态管理
|
||||
- 实时事件管理
|
||||
- UI 状态管理(选择、过滤、搜索)
|
||||
- ✅ 提供了智能选择器 Hooks:
|
||||
- `useFilteredWorkflows`: 过滤后的工作流
|
||||
- `useFilteredTemplates`: 过滤后的模板
|
||||
- `useFilteredExecutions`: 过滤后的执行记录
|
||||
|
||||
**技术亮点**:
|
||||
- 类型安全的 API 接口
|
||||
- 响应式状态管理
|
||||
- 智能缓存和同步
|
||||
- 实时状态更新
|
||||
- 统一的错误处理
|
||||
|
||||
### 4.2 实时通信集成 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了实时事件监听器组件 `RealtimeEventListener`
|
||||
- ✅ 实现了完整的实时事件处理:
|
||||
- 自动启动和停止监控
|
||||
- 事件统计和分析
|
||||
- 事件类型分布显示
|
||||
- 实时事件日志
|
||||
- 连接状态指示
|
||||
- ✅ 创建了执行状态监控组件 `ExecutionMonitor`
|
||||
- ✅ 实现了实时执行监控功能:
|
||||
- 实时执行状态更新
|
||||
- 进度条显示
|
||||
- 自动刷新机制
|
||||
- 执行历史管理
|
||||
- 批量操作支持
|
||||
- ✅ 创建了队列状态监控组件 `QueueStatusMonitor`
|
||||
- ✅ 实现了队列实时监控:
|
||||
- 队列状态统计
|
||||
- 队列趋势图表
|
||||
- 最近事件显示
|
||||
- 自动状态刷新
|
||||
|
||||
**技术亮点**:
|
||||
- 实时数据同步
|
||||
- 智能事件处理
|
||||
- 可视化状态展示
|
||||
- 自动化监控管理
|
||||
- 用户友好的交互
|
||||
|
||||
### 4.3 现代化UI组件 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了完整的现代化 UI 组件库
|
||||
- ✅ 实现了核心 UI 组件:
|
||||
- `Button`: 多变体按钮组件(default, destructive, outline, secondary, ghost, link, success, warning)
|
||||
- `Input`: 现代化输入框组件(支持图标、错误状态、帮助文本)
|
||||
- `Card`: 灵活的卡片组件(多种变体和悬停效果)
|
||||
- `Modal`: 可访问的模态框组件(支持键盘导航、焦点管理)
|
||||
- `Toast`: 优雅的通知组件(多种类型、自动消失、操作按钮)
|
||||
- `Loading`: 多样化加载组件(spinner, dots, pulse, bars)
|
||||
- `Form`: 完整的表单组件系统(字段、验证、提交)
|
||||
- ✅ 实现了高级 UI 功能:
|
||||
- 类型安全的组件 Props
|
||||
- 可组合的组件架构
|
||||
- 统一的样式系统
|
||||
- 响应式设计支持
|
||||
- 无障碍访问支持
|
||||
- ✅ 创建了工具函数:
|
||||
- `cn`: 类名合并工具(基于 clsx 和 tailwind-merge)
|
||||
|
||||
**技术亮点**:
|
||||
- 基于 class-variance-authority 的变体系统
|
||||
- 完整的 TypeScript 类型支持
|
||||
- 统一的设计语言
|
||||
- 可访问性最佳实践
|
||||
- 高度可定制化
|
||||
|
||||
### 4.4 用户体验优化 ✅
|
||||
|
||||
**完成内容**:
|
||||
- ✅ 创建了现代化的主仪表板页面 `ComfyUIV2Dashboard`
|
||||
- ✅ 实现了完整的用户界面:
|
||||
- 响应式标签导航
|
||||
- 实时状态指示器
|
||||
- 智能内容切换
|
||||
- 统一的页面布局
|
||||
- ✅ 创建了专业的连接管理面板 `ComfyUIConnectionPanel`
|
||||
- ✅ 实现了直观的连接管理:
|
||||
- 可视化连接状态
|
||||
- 配置表单集成
|
||||
- 系统信息展示
|
||||
- 错误处理和反馈
|
||||
- ✅ 创建了现代化的工作流管理器 `WorkflowManager`
|
||||
- ✅ 实现了完整的工作流管理:
|
||||
- 网格/列表视图切换
|
||||
- 智能搜索和筛选
|
||||
- 批量操作支持
|
||||
- 工作流卡片展示
|
||||
- ✅ 集成了通知系统:
|
||||
- 全局通知管理
|
||||
- 多种通知类型
|
||||
- 自动状态反馈
|
||||
|
||||
**技术亮点**:
|
||||
- 直观的用户界面设计
|
||||
- 响应式布局适配
|
||||
- 智能交互反馈
|
||||
- 统一的视觉语言
|
||||
- 优秀的用户体验
|
||||
|
||||
## 📁 创建的文件结构
|
||||
|
||||
```
|
||||
apps/desktop/src/
|
||||
├── services/
|
||||
│ └── comfyuiV2Service.ts # V2 API 服务层
|
||||
├── store/
|
||||
│ └── comfyuiV2Store.ts # V2 状态管理
|
||||
├── components/
|
||||
│ ├── ui/ # UI 组件库
|
||||
│ │ ├── Button.tsx # 按钮组件
|
||||
│ │ ├── Input.tsx # 输入组件
|
||||
│ │ ├── Card.tsx # 卡片组件
|
||||
│ │ ├── Modal.tsx # 模态框组件
|
||||
│ │ ├── Toast.tsx # 通知组件
|
||||
│ │ ├── Loading.tsx # 加载组件
|
||||
│ │ ├── Form.tsx # 表单组件
|
||||
│ │ └── index.ts # 组件导出
|
||||
│ └── comfyui/ # ComfyUI 专用组件
|
||||
│ ├── ComfyUIConnectionPanel.tsx # 连接面板
|
||||
│ ├── WorkflowManager.tsx # 工作流管理器
|
||||
│ ├── ExecutionMonitor.tsx # 执行监控
|
||||
│ ├── RealtimeEventListener.tsx # 实时事件监听
|
||||
│ └── QueueStatusMonitor.tsx # 队列状态监控
|
||||
├── pages/
|
||||
│ └── ComfyUIV2Dashboard.tsx # V2 主仪表板
|
||||
└── utils/
|
||||
└── cn.ts # 类名合并工具
|
||||
```
|
||||
|
||||
## 🔧 技术架构
|
||||
|
||||
### 前端架构
|
||||
```
|
||||
React Components (UI Layer)
|
||||
↓
|
||||
Zustand Store (State Management)
|
||||
↓
|
||||
ComfyUIV2Service (API Layer)
|
||||
↓
|
||||
Tauri Commands (Bridge Layer)
|
||||
↓
|
||||
Rust Backend (Business Logic)
|
||||
```
|
||||
|
||||
### 状态管理架构
|
||||
```
|
||||
UI Components → Zustand Actions → State Updates → Component Re-render
|
||||
↑ ↓
|
||||
Real-time Events ← WebSocket ← Backend ← API Calls ← Service Layer
|
||||
```
|
||||
|
||||
### 组件架构
|
||||
```
|
||||
Pages (Dashboard, Settings)
|
||||
↓
|
||||
Feature Components (WorkflowManager, ExecutionMonitor)
|
||||
↓
|
||||
UI Components (Button, Card, Modal)
|
||||
↓
|
||||
Utility Functions (cn, hooks)
|
||||
```
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
- **新增服务**: 1 个现代化 API 服务
|
||||
- **新增状态管理**: 1 个完整的状态管理系统
|
||||
- **新增 UI 组件**: 7 个核心 UI 组件
|
||||
- **新增功能组件**: 5 个专业功能组件
|
||||
- **新增页面**: 1 个现代化仪表板页面
|
||||
- **新增文件**: 13 个
|
||||
- **代码行数**: ~2,800 行
|
||||
- **组件分类**:
|
||||
- UI 基础组件: 7 个
|
||||
- 功能组件: 5 个
|
||||
- 页面组件: 1 个
|
||||
- 工具函数: 1 个
|
||||
|
||||
## ✅ 质量保证
|
||||
|
||||
### 架构质量
|
||||
- ✅ 现代化 React 架构
|
||||
- ✅ 类型安全保证
|
||||
- ✅ 组件化设计
|
||||
- ✅ 状态管理优化
|
||||
|
||||
### 用户体验
|
||||
- ✅ 响应式设计
|
||||
- ✅ 直观的交互
|
||||
- ✅ 实时状态反馈
|
||||
- ✅ 优雅的加载状态
|
||||
|
||||
### 代码质量
|
||||
- ✅ TypeScript 类型安全
|
||||
- ✅ 组件复用性
|
||||
- ✅ 统一的代码风格
|
||||
- ✅ 完整的错误处理
|
||||
|
||||
## 🚀 核心功能特性
|
||||
|
||||
### 现代化 API 服务
|
||||
- 🔄 **完整封装**: 覆盖所有后端 API
|
||||
- 🛡️ **类型安全**: 完整的 TypeScript 类型定义
|
||||
- 🔄 **自动重试**: 智能错误处理和重试机制
|
||||
- 📡 **实时通信**: WebSocket 事件订阅和处理
|
||||
- 🎯 **批量操作**: 支持批量执行和管理
|
||||
|
||||
### 智能状态管理
|
||||
- 🧠 **响应式状态**: 基于 Zustand 的现代状态管理
|
||||
- 🔄 **实时同步**: 自动同步后端状态变化
|
||||
- 🎯 **智能选择器**: 高性能的数据筛选和搜索
|
||||
- 💾 **状态持久化**: 重要状态的本地持久化
|
||||
- 🔍 **调试友好**: 完整的状态追踪和调试支持
|
||||
|
||||
### 现代化 UI 组件
|
||||
- 🎨 **统一设计**: 一致的视觉语言和交互模式
|
||||
- 🔧 **高度可定制**: 灵活的变体和样式系统
|
||||
- ♿ **无障碍访问**: 完整的 ARIA 支持和键盘导航
|
||||
- 📱 **响应式设计**: 适配各种屏幕尺寸
|
||||
- ⚡ **高性能**: 优化的渲染和交互性能
|
||||
|
||||
### 优秀用户体验
|
||||
- 🎯 **直观操作**: 简洁明了的用户界面
|
||||
- 🔄 **实时反馈**: 即时的状态更新和进度显示
|
||||
- 🎨 **视觉层次**: 清晰的信息架构和视觉引导
|
||||
- 🚀 **流畅交互**: 平滑的动画和过渡效果
|
||||
- 💡 **智能提示**: 友好的错误处理和操作指导
|
||||
|
||||
## 🔄 与现有系统的集成
|
||||
|
||||
### 后端集成
|
||||
- ✅ 完全兼容第三阶段的后端 API
|
||||
- ✅ 实时事件系统无缝对接
|
||||
- ✅ 统一的错误处理机制
|
||||
|
||||
### 数据集成
|
||||
- ✅ 与数据仓库深度集成
|
||||
- ✅ 实时状态同步
|
||||
- ✅ 本地状态缓存优化
|
||||
|
||||
## 🎯 下一步计划
|
||||
|
||||
### 第五阶段: 测试与部署
|
||||
- [ ] 编写完整的单元测试
|
||||
- [ ] 集成测试和端到端测试
|
||||
- [ ] 性能优化和监控
|
||||
- [ ] 部署配置和文档
|
||||
|
||||
### 准备工作
|
||||
1. **功能测试**: 测试所有新功能的完整性
|
||||
2. **性能测试**: 验证前端性能和响应速度
|
||||
3. **用户测试**: 收集用户反馈和体验优化
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 依赖管理
|
||||
- 需要安装新的前端依赖包:
|
||||
- `zustand`: 状态管理
|
||||
- `class-variance-authority`: 组件变体系统
|
||||
- `clsx`: 类名合并
|
||||
- `tailwind-merge`: Tailwind 类名合并
|
||||
|
||||
### 兼容性
|
||||
- 与现有组件的兼容性需要测试
|
||||
- 可能需要逐步迁移现有页面
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
第四阶段的前端重构与UI优化已经成功完成!我们建立了一个完整的、现代化的前端架构体系。
|
||||
|
||||
**主要成就**:
|
||||
- ✅ 13 个全新的现代化组件和服务
|
||||
- ✅ 完整的类型安全 API 封装
|
||||
- ✅ 智能的状态管理系统
|
||||
- ✅ 现代化的 UI 组件库
|
||||
- ✅ 优秀的用户体验设计
|
||||
|
||||
**技术优势**:
|
||||
- 🚀 **现代化架构**: React + TypeScript + Zustand
|
||||
- 🎯 **类型安全**: 完整的类型定义和检查
|
||||
- 🔄 **实时响应**: WebSocket 实时通信集成
|
||||
- 🎨 **统一设计**: 一致的视觉语言和交互
|
||||
- ⚡ **高性能**: 优化的渲染和状态管理
|
||||
|
||||
**用户体验**:
|
||||
- 🎯 **直观操作**: 简洁明了的界面设计
|
||||
- 🔄 **实时反馈**: 即时的状态更新和进度显示
|
||||
- 📱 **响应式设计**: 适配各种设备和屏幕
|
||||
- 💡 **智能交互**: 友好的错误处理和操作指导
|
||||
- 🎨 **视觉美观**: 现代化的设计语言
|
||||
|
||||
现在整个系统具备了:
|
||||
- 🔧 **完整的后端服务**: 数据管理、实时通信、队列管理、性能优化
|
||||
- 🎨 **现代化的前端界面**: 响应式设计、实时更新、优秀体验
|
||||
- 🔄 **无缝的前后端集成**: 类型安全、实时同步、统一错误处理
|
||||
- 📊 **全面的监控和分析**: 性能监控、事件追踪、状态管理
|
||||
|
||||
准备开始第五阶段的测试与部署了!🚀
|
||||
@@ -1,215 +0,0 @@
|
||||
# 下拉选择组件优化总结
|
||||
|
||||
## 🎯 问题描述
|
||||
原始的下拉选择组件存在以下问题:
|
||||
- 下拉图标与右边界距离过近,视觉上不够美观
|
||||
- 原生select样式不够现代化
|
||||
- 缺乏统一的设计规范
|
||||
- 交互反馈不够明显
|
||||
|
||||
## 🔧 优化方案
|
||||
|
||||
### 1. 自定义下拉选择组件
|
||||
创建了 `CustomSelect` 组件来替代原生的 `<select>` 元素:
|
||||
|
||||
```tsx
|
||||
const CustomSelect: React.FC<{
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options: { value: string; label: string }[];
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}> = ({ value, onChange, options, placeholder, className = '' }) => {
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="w-full appearance-none bg-white border border-gray-200 rounded-xl px-4 py-3 pr-10 text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 hover:border-gray-300 cursor-pointer"
|
||||
>
|
||||
{placeholder && <option value="">{placeholder}</option>}
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<ChevronDownIcon className="h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 关键优化点
|
||||
|
||||
#### 🎨 视觉设计优化
|
||||
- **右侧间距**: `pr-10` 确保图标与边界有足够距离
|
||||
- **图标定位**: `pr-3` 精确控制图标位置
|
||||
- **圆角设计**: `rounded-xl` 现代化圆角
|
||||
- **边框颜色**: 使用更柔和的 `border-gray-200`
|
||||
|
||||
#### 🎯 交互体验优化
|
||||
- **悬停效果**: `hover:border-gray-300` 提供视觉反馈
|
||||
- **焦点状态**: `focus:ring-2 focus:ring-primary-500` 清晰的焦点指示
|
||||
- **过渡动画**: `transition-all duration-200` 流畅的状态切换
|
||||
- **指针样式**: `cursor-pointer` 明确的可点击指示
|
||||
|
||||
#### 🔧 技术实现优化
|
||||
- **移除默认样式**: `appearance-none` 移除浏览器默认样式
|
||||
- **自定义图标**: 使用 `ChevronDownIcon` 替代默认箭头
|
||||
- **响应式设计**: 支持 `min-w-[]` 最小宽度设置
|
||||
|
||||
### 3. 布局结构优化
|
||||
|
||||
#### 原始布局问题
|
||||
```tsx
|
||||
// 旧版本 - 布局混乱,间距不统一
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<FunnelIcon className="h-5 w-5 text-gray-400" />
|
||||
<select className="px-3 py-2 border border-gray-300 rounded-lg">
|
||||
{/* options */}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 优化后布局
|
||||
```tsx
|
||||
// 新版本 - 结构清晰,标签明确
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-700">
|
||||
<FunnelIcon className="h-4 w-4 text-gray-500" />
|
||||
状态
|
||||
</div>
|
||||
<CustomSelect
|
||||
value={statusFilter}
|
||||
onChange={(value) => onStatusFilterChange(value as ModelStatus | 'all')}
|
||||
options={statusOptions}
|
||||
className="min-w-[120px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. 样式系统增强
|
||||
|
||||
#### CSS 自定义样式
|
||||
```css
|
||||
/* 自定义下拉选择框样式 */
|
||||
.custom-select {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.custom-select select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-image: none;
|
||||
padding-right: 2.5rem; /* 确保右侧有足够空间给图标 */
|
||||
}
|
||||
|
||||
.custom-select select::-ms-expand {
|
||||
display: none; /* 隐藏IE的默认箭头 */
|
||||
}
|
||||
|
||||
/* 下拉箭头图标样式 */
|
||||
.custom-select .dropdown-icon {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
transition: transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.custom-select:hover .dropdown-icon {
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
.custom-select select:focus + .dropdown-icon {
|
||||
color: var(--primary-500);
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 整体界面优化
|
||||
|
||||
### 1. ModelSearch 组件重设计
|
||||
- **容器样式**: `rounded-2xl shadow-sm border border-gray-100 p-6`
|
||||
- **搜索框优化**: 更大的内边距和更好的图标定位
|
||||
- **筛选器布局**: 清晰的标签和合理的间距
|
||||
- **活动筛选显示**: 更美观的标签样式和清除按钮
|
||||
|
||||
### 2. 视觉层次优化
|
||||
```tsx
|
||||
// 搜索框 - 主要功能区域
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<MagnifyingGlassIcon className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input className="w-full pl-12 pr-4 py-3 border border-gray-200 rounded-xl..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// 筛选器 - 次要功能区域
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm font-medium text-gray-700">状态</div>
|
||||
<CustomSelect className="min-w-[120px]" />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. 交互反馈增强
|
||||
- **悬停状态**: 所有可交互元素都有悬停反馈
|
||||
- **焦点管理**: 清晰的焦点指示器
|
||||
- **加载状态**: 优雅的过渡动画
|
||||
- **错误处理**: 友好的错误提示
|
||||
|
||||
## 📊 优化效果对比
|
||||
|
||||
### 视觉效果改进
|
||||
| 方面 | 优化前 | 优化后 |
|
||||
|------|--------|--------|
|
||||
| 图标间距 | 过近,视觉拥挤 | 合理间距,视觉舒适 |
|
||||
| 整体风格 | 原生样式,不统一 | 现代化设计,统一风格 |
|
||||
| 交互反馈 | 基础反馈 | 丰富的视觉反馈 |
|
||||
| 布局结构 | 简单堆叠 | 层次清晰,标签明确 |
|
||||
|
||||
### 用户体验提升
|
||||
- **可用性**: 更清晰的标签和更好的视觉层次
|
||||
- **一致性**: 统一的设计语言和交互模式
|
||||
- **反馈性**: 即时的视觉反馈和状态指示
|
||||
- **美观性**: 现代化的设计风格和精致的细节
|
||||
|
||||
### 技术实现改进
|
||||
- **可维护性**: 组件化设计,易于复用和维护
|
||||
- **可扩展性**: 灵活的配置选项,支持不同场景
|
||||
- **性能优化**: 合理的CSS动画和过渡效果
|
||||
- **兼容性**: 跨浏览器兼容的样式实现
|
||||
|
||||
## 🚀 应用场景
|
||||
|
||||
这个优化后的下拉选择组件可以应用于:
|
||||
- 模特管理的筛选功能
|
||||
- 项目管理的状态选择
|
||||
- 素材管理的分类筛选
|
||||
- 其他需要下拉选择的场景
|
||||
|
||||
## 🔄 后续优化建议
|
||||
|
||||
### 短期优化
|
||||
1. **键盘导航**: 增强键盘操作支持
|
||||
2. **搜索功能**: 在下拉选项中添加搜索
|
||||
3. **多选支持**: 支持多选模式
|
||||
4. **虚拟滚动**: 处理大量选项的性能优化
|
||||
|
||||
### 长期优化
|
||||
1. **智能推荐**: 基于使用频率的选项排序
|
||||
2. **自定义选项**: 允许用户添加自定义选项
|
||||
3. **数据联动**: 支持级联选择
|
||||
4. **国际化**: 多语言支持
|
||||
|
||||
这次优化不仅解决了图标间距问题,还建立了一套完整的下拉选择组件设计规范,为整个应用的UI一致性奠定了基础。
|
||||
@@ -1,120 +0,0 @@
|
||||
# ExportRecordManager 统计样式对齐总结
|
||||
|
||||
## 🎯 目标
|
||||
将 ExportRecordManager 组件的统计卡片样式与项目中其他"项目统计"保持完全一致,确保整个应用的视觉统一性。
|
||||
|
||||
## 📊 参考样式分析
|
||||
|
||||
### 项目中的统计卡片设计模式
|
||||
通过分析 `ProjectCard.tsx` 和 `ProjectDetails.tsx` 中的项目统计样式,发现项目使用了以下设计模式:
|
||||
|
||||
```tsx
|
||||
// ProjectDetails.tsx 中的统计卡片样式
|
||||
<div className="bg-gradient-to-br from-white to-primary-50/30 rounded-xl shadow-sm border border-gray-200/50 p-4 md:p-5 hover:shadow-md transition-all duration-300 hover:-translate-y-1 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-16 h-16 bg-gradient-to-br from-primary-100/50 to-primary-200/50 rounded-full -translate-y-8 translate-x-8 opacity-50"></div>
|
||||
<div className="relative z-10">
|
||||
{/* 内容 */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 设计特点
|
||||
1. **渐变背景**: `bg-gradient-to-br from-white to-{color}-50/30`
|
||||
2. **装饰圆圈**: 右上角的半透明渐变圆圈装饰
|
||||
3. **悬停效果**: `hover:-translate-y-1` 和阴影变化
|
||||
4. **圆角设计**: `rounded-xl` 更大的圆角
|
||||
5. **层次结构**: 使用 `relative` 和 `z-10` 确保内容在装饰之上
|
||||
|
||||
## 🔄 修改对比
|
||||
|
||||
### 修改前 - 使用 stat-card 类
|
||||
```tsx
|
||||
<div className="stat-card primary">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-50 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 修改后 - 项目统计风格
|
||||
```tsx
|
||||
<div className="bg-gradient-to-br from-white to-primary-50/30 rounded-xl shadow-sm border border-gray-200/50 p-4 md:p-5 hover:shadow-md transition-all duration-300 hover:-translate-y-1 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-16 h-16 bg-gradient-to-br from-primary-100/50 to-primary-200/50 rounded-full -translate-y-8 translate-x-8 opacity-50"></div>
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="p-2 bg-primary-100/50 rounded-lg">
|
||||
<BarChart3 className="w-5 h-5 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-gray-900 mb-1">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 🎨 具体改进内容
|
||||
|
||||
### 1. 背景和边框
|
||||
- **原来**: 使用 `.stat-card` 类的预定义样式
|
||||
- **现在**: 使用项目标准的渐变背景 `bg-gradient-to-br from-white to-{color}-50/30`
|
||||
- **边框**: 统一使用 `border border-gray-200/50`
|
||||
|
||||
### 2. 装饰元素
|
||||
- **新增**: 右上角的装饰圆圈,每个卡片使用对应的颜色
|
||||
- 总导出: `from-primary-100/50 to-primary-200/50`
|
||||
- 成功导出: `from-green-100/50 to-green-200/50`
|
||||
- 失败导出: `from-red-100/50 to-red-200/50`
|
||||
- 文件大小: `from-purple-100/50 to-purple-200/50`
|
||||
|
||||
### 3. 布局结构
|
||||
- **图标位置**: 从右侧移到左上角,与项目统计保持一致
|
||||
- **图标样式**: 使用 `bg-{color}-100/50` 的半透明背景
|
||||
- **层次结构**: 使用 `relative z-10` 确保内容在装饰之上
|
||||
|
||||
### 4. 交互效果
|
||||
- **悬停动画**: `hover:-translate-y-1` 向上移动效果
|
||||
- **阴影变化**: `hover:shadow-md` 悬停时增强阴影
|
||||
- **过渡动画**: `transition-all duration-300` 流畅的过渡效果
|
||||
|
||||
### 5. 响应式设计
|
||||
- **内边距**: `p-4 md:p-5` 在不同屏幕尺寸下的适配
|
||||
- **圆角**: 统一使用 `rounded-xl` 更现代的圆角设计
|
||||
|
||||
## ✅ 对齐效果
|
||||
|
||||
### 视觉一致性
|
||||
- ✅ **完全匹配**: 与 ProjectCard 和 ProjectDetails 中的统计卡片样式完全一致
|
||||
- ✅ **颜色系统**: 使用相同的颜色变体和透明度
|
||||
- ✅ **装饰效果**: 相同的右上角圆圈装饰模式
|
||||
- ✅ **交互反馈**: 一致的悬停和过渡动画
|
||||
|
||||
### 用户体验
|
||||
- ✅ **熟悉感**: 用户在不同页面看到相同的设计模式
|
||||
- ✅ **专业感**: 统一的设计语言提升应用的专业度
|
||||
- ✅ **可读性**: 优化的布局和颜色对比度
|
||||
|
||||
### 技术实现
|
||||
- ✅ **代码一致性**: 使用相同的 CSS 类和结构模式
|
||||
- ✅ **维护性**: 遵循项目的设计系统,便于后续维护
|
||||
- ✅ **扩展性**: 可以轻松应用到其他需要统计卡片的组件
|
||||
|
||||
## 🎯 设计原则体现
|
||||
|
||||
1. **一致性原则**: 整个应用使用统一的统计卡片设计
|
||||
2. **层次性原则**: 通过装饰、阴影、颜色建立清晰的视觉层次
|
||||
3. **反馈性原则**: 悬停动画提供即时的交互反馈
|
||||
4. **美观性原则**: 渐变、圆角、阴影营造现代化的视觉效果
|
||||
|
||||
## 📝 总结
|
||||
|
||||
这次样式对齐确保了 ExportRecordManager 组件完全融入项目的设计体系,用户在使用不同功能时都能感受到一致的视觉体验。通过参考项目中已有的优秀设计模式,我们不仅解决了"黑底黑字"的可见性问题,还提升了整体的设计质量和用户体验。
|
||||
@@ -1,111 +0,0 @@
|
||||
# 统计卡片样式修复总结
|
||||
|
||||
## 🐛 问题描述
|
||||
ExportRecordManager 组件中的统计信息卡片出现了"黑底黑字"的可见性问题,用户无法清楚看到统计数据。
|
||||
|
||||
## 🔍 问题分析
|
||||
通过检查项目的设计系统 CSS (`apps/desktop/src/styles/design-system.css`),发现:
|
||||
|
||||
1. **`.stat-card` 基础样式**:
|
||||
```css
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, white 0%, rgba(249, 250, 251, 0.5) 100%);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(229, 231, 235, 0.5);
|
||||
padding: 1.25rem;
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
2. **颜色变体类**:
|
||||
- `.stat-card.primary::before` - 主色调装饰
|
||||
- `.stat-card.success::before` - 成功色装饰
|
||||
- `.stat-card.warning::before` - 警告色装饰
|
||||
- `.stat-card.purple::before` - 紫色装饰
|
||||
|
||||
## 🔧 解决方案
|
||||
|
||||
### 修复前的代码
|
||||
```tsx
|
||||
<div className="stat-card hover-glow">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-50 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 修复后的代码
|
||||
```tsx
|
||||
<div className="stat-card primary">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-primary-600">
|
||||
{statistics.total_exports}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">总导出次数</div>
|
||||
</div>
|
||||
<div className="p-3 bg-primary-50 rounded-lg">
|
||||
<BarChart3 className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 📋 具体修改内容
|
||||
|
||||
1. **添加颜色变体类**:
|
||||
- 总导出次数:`stat-card primary`
|
||||
- 成功导出:`stat-card success`
|
||||
- 失败导出:`stat-card warning`
|
||||
- 总文件大小:`stat-card purple`
|
||||
|
||||
2. **移除冲突类**:
|
||||
- 移除了 `hover-glow` 类,因为 `.stat-card` 已经有自己的悬停效果
|
||||
|
||||
## ✅ 修复效果
|
||||
|
||||
### 视觉改进
|
||||
- ✅ **白色背景**:确保统计卡片有清晰的白色背景
|
||||
- ✅ **文字可见性**:深色文字在白色背景上清晰可见
|
||||
- ✅ **装饰效果**:每个卡片右上角有相应颜色的渐变装饰
|
||||
- ✅ **悬停效果**:统一的阴影和位移动画
|
||||
|
||||
### 设计一致性
|
||||
- ✅ **遵循设计系统**:使用项目定义的 `.stat-card` 样式类
|
||||
- ✅ **颜色语义化**:
|
||||
- 蓝色(primary)- 总数据
|
||||
- 绿色(success)- 成功数据
|
||||
- 橙色(warning)- 失败数据
|
||||
- 紫色(purple)- 文件大小数据
|
||||
|
||||
### 用户体验
|
||||
- ✅ **可读性提升**:统计数据清晰可见
|
||||
- ✅ **视觉层次**:不同类型的统计数据有明确的视觉区分
|
||||
- ✅ **交互反馈**:悬停时有优雅的动画效果
|
||||
|
||||
## 🎯 技术要点
|
||||
|
||||
1. **CSS 类组合**:正确使用基础类 + 变体类的组合模式
|
||||
2. **设计系统遵循**:严格按照项目设计系统的定义使用样式
|
||||
3. **语义化设计**:颜色选择符合数据类型的语义含义
|
||||
4. **响应式适配**:在不同屏幕尺寸下保持良好的显示效果
|
||||
|
||||
## 📝 学习总结
|
||||
|
||||
这次修复强调了以下重要原则:
|
||||
|
||||
1. **深入理解设计系统**:需要仔细研究项目的 CSS 设计系统,了解各个类的作用和组合方式
|
||||
2. **参考现有实现**:通过查看其他组件的实现方式,学习正确的使用模式
|
||||
3. **测试验证**:通过构建测试确保修改不会引入新的问题
|
||||
4. **渐进式改进**:先解决核心问题(可见性),再优化细节(装饰效果)
|
||||
|
||||
这次修复确保了 ExportRecordManager 组件的统计卡片完全符合项目的 UI 标准,提供了良好的用户体验。
|
||||
@@ -1,108 +0,0 @@
|
||||
# 缩略图生成修复验证指南
|
||||
|
||||
## 修复内容概述
|
||||
|
||||
本次修复解决了"FFmpeg执行完成但缩略图文件不存在"的问题,通过以下改进:
|
||||
|
||||
### 1. 增强错误处理和日志
|
||||
- 添加详细的FFmpeg命令和输出日志
|
||||
- 记录每个步骤的执行状态
|
||||
- 提供更准确的错误信息
|
||||
|
||||
### 2. 实现重试机制
|
||||
- 原时间戳失败时自动尝试其他时间戳
|
||||
- 重试策略:0秒、中间时间点、1秒、25%处、75%处
|
||||
- 智能跳过超出视频长度的时间戳
|
||||
|
||||
### 3. 预检查机制
|
||||
- 验证输入视频文件的有效性
|
||||
- 检查时间戳的合理性
|
||||
- 确保输出目录可写
|
||||
- 验证磁盘空间
|
||||
|
||||
### 4. 优化FFmpeg参数
|
||||
- 添加 `-hide_banner` 隐藏版权信息
|
||||
- 使用 `-loglevel error` 减少噪音
|
||||
- 添加 `-update 1` 避免格式问题
|
||||
- 改进缩放过滤器保持宽高比
|
||||
|
||||
## 验证步骤
|
||||
|
||||
### 1. 编译验证
|
||||
```bash
|
||||
cd apps/desktop/src-tauri
|
||||
cargo build
|
||||
```
|
||||
|
||||
### 2. 运行单元测试
|
||||
```bash
|
||||
cargo test ffmpeg --lib
|
||||
```
|
||||
|
||||
### 3. 启动应用测试
|
||||
```bash
|
||||
cd apps/desktop
|
||||
pnpm tauri:dev
|
||||
```
|
||||
|
||||
### 4. 功能测试
|
||||
1. 创建或打开一个项目
|
||||
2. 导入多个视频文件
|
||||
3. 观察缩略图生成过程
|
||||
4. 检查控制台日志
|
||||
|
||||
### 5. 预期改进
|
||||
- 缩略图生成成功率显著提高
|
||||
- 失败时有详细的错误信息和重试日志
|
||||
- 对于特殊格式或损坏的视频文件有更好的处理
|
||||
|
||||
## 日志示例
|
||||
|
||||
### 成功案例
|
||||
```
|
||||
INFO mixvideo_desktop_lib::infrastructure::ffmpeg: 开始执行FFmpeg缩略图生成
|
||||
INFO mixvideo_desktop_lib::infrastructure::ffmpeg: 缩略图生成成功
|
||||
```
|
||||
|
||||
### 重试成功案例
|
||||
```
|
||||
WARN mixvideo_desktop_lib::infrastructure::ffmpeg: 缩略图生成尝试失败
|
||||
INFO mixvideo_desktop_lib::infrastructure::ffmpeg: 缩略图生成重试成功
|
||||
```
|
||||
|
||||
### 失败案例(已重试)
|
||||
```
|
||||
ERROR mixvideo_desktop_lib::infrastructure::ffmpeg: 所有缩略图生成尝试都失败
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 新增函数
|
||||
- `generate_thumbnail_with_retry()` - 带重试机制的缩略图生成
|
||||
- `validate_thumbnail_generation()` - 预检查机制
|
||||
|
||||
### 修改的函数
|
||||
- `generate_thumbnail()` - 增强错误处理和日志
|
||||
- `get_material_segment_thumbnail()` - 使用新的重试机制
|
||||
|
||||
### 测试覆盖
|
||||
- 输入文件不存在的处理
|
||||
- 输出目录创建逻辑
|
||||
- FFmpeg命令参数构造
|
||||
- 边界情况处理
|
||||
|
||||
## 故障排除
|
||||
|
||||
如果仍然遇到缩略图生成问题:
|
||||
|
||||
1. 检查FFmpeg是否正确安装
|
||||
2. 查看详细的错误日志
|
||||
3. 验证视频文件是否损坏
|
||||
4. 检查磁盘空间是否充足
|
||||
5. 确认输出目录权限
|
||||
|
||||
## 性能影响
|
||||
|
||||
- 重试机制可能增加处理时间,但提高成功率
|
||||
- 预检查增加少量开销,但避免无效操作
|
||||
- 详细日志可能增加日志文件大小
|
||||
533
TODO.md
533
TODO.md
@@ -1,533 +0,0 @@
|
||||
# ComfyUI SDK 完全重写集成计划
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
**目标**: 完全基于 `cargos/comfyui-sdk` 重新实现 MixVideo 项目的 ComfyUI 集成,构建现代化、高性能的 AI 工作流管理系统。
|
||||
|
||||
**核心优势**:
|
||||
- 🚀 充分利用 SDK 的所有高级功能
|
||||
- 🔄 WebSocket 实时通信和进度跟踪
|
||||
- 📝 强大的模板系统和参数验证
|
||||
- 🛡️ 类型安全和错误处理
|
||||
- ⚡ 连接池和性能优化
|
||||
- 🎯 统一的架构设计
|
||||
|
||||
**预计工期**: 3-4 周
|
||||
**风险等级**: 中等 (需要充分测试)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
### 新架构概览
|
||||
```
|
||||
Frontend (React + TypeScript)
|
||||
↓ Tauri Invoke
|
||||
Backend (Rust + ComfyUI SDK)
|
||||
├── Presentation Layer (Commands)
|
||||
├── Business Layer (Services)
|
||||
├── Data Layer (Models + Database)
|
||||
└── Infrastructure Layer (ComfyUI SDK)
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
1. **ComfyUIManager**: 统一的 SDK 管理器
|
||||
2. **TemplateEngine**: 工作流模板引擎
|
||||
3. **ExecutionEngine**: 工作流执行引擎
|
||||
4. **RealtimeMonitor**: 实时状态监控
|
||||
5. **CacheManager**: 智能缓存管理
|
||||
|
||||
---
|
||||
|
||||
## 📅 详细开发计划
|
||||
|
||||
## 第一阶段: 基础架构重构 (第1周)
|
||||
|
||||
### 🎯 目标
|
||||
建立全新的基于 SDK 的基础架构,替换现有的传统实现。
|
||||
|
||||
### 📋 任务清单
|
||||
|
||||
#### 1.1 数据模型重新设计 (2天)
|
||||
- [ ] **删除旧模型**: 移除 `apps/desktop/src-tauri/src/data/models/comfyui.rs`
|
||||
- [ ] **创建新模型**: 基于 SDK 类型创建统一数据模型
|
||||
- [ ] `WorkflowModel`: 工作流数据模型
|
||||
- [ ] `TemplateModel`: 模板数据模型
|
||||
- [ ] `ExecutionModel`: 执行记录模型
|
||||
- [ ] `QueueModel`: 队列状态模型
|
||||
- [ ] **数据库迁移**: 设计新的数据库表结构
|
||||
- [ ] `comfyui_workflows` 表
|
||||
- [ ] `comfyui_templates` 表
|
||||
- [ ] `comfyui_executions` 表
|
||||
- [ ] `comfyui_queue_history` 表
|
||||
|
||||
#### 1.2 核心服务重构 (3天)
|
||||
- [ ] **删除旧服务**: 移除现有的 ComfyUI 服务实现
|
||||
- [ ] 删除 `comfyui_service.rs`
|
||||
- [ ] 删除 `comfyui_sdk_service.rs`
|
||||
- [ ] **创建 ComfyUIManager**: 统一的 SDK 管理器
|
||||
- [ ] 连接管理和健康检查
|
||||
- [ ] 配置管理和验证
|
||||
- [ ] 错误处理和重试机制
|
||||
- [ ] **创建 TemplateEngine**: 模板管理引擎
|
||||
- [ ] 模板加载和验证
|
||||
- [ ] 参数解析和验证
|
||||
- [ ] 模板实例化
|
||||
- [ ] **创建 ExecutionEngine**: 执行引擎
|
||||
- [ ] 工作流执行管理
|
||||
- [ ] 队列管理
|
||||
- [ ] 结果处理
|
||||
|
||||
#### 1.3 配置系统重构 (1天)
|
||||
- [ ] **统一配置结构**: 基于 SDK 配置重新设计
|
||||
- [ ] **配置验证**: 添加配置有效性检查
|
||||
- [ ] **环境适配**: 支持开发/生产环境配置
|
||||
|
||||
#### 1.4 错误处理系统 (1天)
|
||||
- [ ] **统一错误类型**: 基于 SDK 错误类型设计
|
||||
- [ ] **错误映射**: SDK 错误到应用错误的映射
|
||||
- [ ] **错误恢复**: 自动重试和降级策略
|
||||
|
||||
### 🧪 测试要求
|
||||
- [ ] 单元测试覆盖率 > 80%
|
||||
- [ ] 集成测试验证基础功能
|
||||
- [ ] 性能基准测试
|
||||
|
||||
---
|
||||
|
||||
## 第二阶段: 命令层重写 (第2周)
|
||||
|
||||
### 🎯 目标
|
||||
重新实现所有 Tauri 命令,提供完整的前端 API 接口。
|
||||
|
||||
### 📋 任务清单
|
||||
|
||||
#### 2.1 基础命令重写 (2天)
|
||||
- [ ] **连接管理命令**
|
||||
- [ ] `comfyui_connect`: 连接到 ComfyUI 服务
|
||||
- [ ] `comfyui_disconnect`: 断开连接
|
||||
- [ ] `comfyui_health_check`: 健康检查
|
||||
- [ ] `comfyui_get_system_info`: 获取系统信息
|
||||
|
||||
#### 2.2 工作流管理命令 (2天)
|
||||
- [ ] **工作流 CRUD 命令**
|
||||
- [ ] `comfyui_list_workflows`: 获取工作流列表
|
||||
- [ ] `comfyui_get_workflow`: 获取单个工作流
|
||||
- [ ] `comfyui_create_workflow`: 创建工作流
|
||||
- [ ] `comfyui_update_workflow`: 更新工作流
|
||||
- [ ] `comfyui_delete_workflow`: 删除工作流
|
||||
|
||||
#### 2.3 模板管理命令 (2天)
|
||||
- [ ] **模板系统命令**
|
||||
- [ ] `comfyui_list_templates`: 获取模板列表
|
||||
- [ ] `comfyui_get_template`: 获取模板详情
|
||||
- [ ] `comfyui_validate_template`: 验证模板
|
||||
- [ ] `comfyui_create_template_instance`: 创建模板实例
|
||||
|
||||
#### 2.4 执行管理命令 (1天)
|
||||
- [ ] **执行控制命令**
|
||||
- [ ] `comfyui_execute_workflow`: 执行工作流
|
||||
- [ ] `comfyui_execute_template`: 执行模板
|
||||
- [ ] `comfyui_cancel_execution`: 取消执行
|
||||
- [ ] `comfyui_get_execution_status`: 获取执行状态
|
||||
|
||||
### 🧪 测试要求
|
||||
- [ ] 每个命令的单元测试
|
||||
- [ ] 命令集成测试
|
||||
- [ ] 错误场景测试
|
||||
|
||||
---
|
||||
|
||||
## 第三阶段: 实时通信与高级功能 (第3周)
|
||||
|
||||
### 🎯 目标
|
||||
实现 WebSocket 实时通信和高级功能,提供完整的用户体验。
|
||||
|
||||
### 📋 任务清单
|
||||
|
||||
#### 3.1 WebSocket 实时通信 (3天)
|
||||
- [ ] **实时监控服务**
|
||||
- [ ] WebSocket 连接管理
|
||||
- [ ] 事件订阅和分发
|
||||
- [ ] 进度更新推送
|
||||
- [ ] **前端实时更新**
|
||||
- [ ] 实时状态显示
|
||||
- [ ] 进度条更新
|
||||
- [ ] 错误实时提醒
|
||||
|
||||
#### 3.2 队列管理系统 (2天)
|
||||
- [ ] **队列监控**
|
||||
- [ ] 队列状态实时更新
|
||||
- [ ] 任务优先级管理
|
||||
- [ ] 队列统计信息
|
||||
- [ ] **批量操作**
|
||||
- [ ] 批量提交任务
|
||||
- [ ] 批量取消任务
|
||||
- [ ] 批量状态查询
|
||||
|
||||
#### 3.3 缓存和性能优化 (2天)
|
||||
- [ ] **智能缓存**
|
||||
- [ ] 工作流结果缓存
|
||||
- [ ] 模板缓存
|
||||
- [ ] 连接池管理
|
||||
- [ ] **性能监控**
|
||||
- [ ] 执行时间统计
|
||||
- [ ] 资源使用监控
|
||||
- [ ] 性能指标收集
|
||||
|
||||
### 🧪 测试要求
|
||||
- [ ] WebSocket 连接稳定性测试
|
||||
- [ ] 高并发场景测试
|
||||
- [ ] 性能压力测试
|
||||
|
||||
---
|
||||
|
||||
## 第四阶段: 前端重构与UI优化 (第4周)
|
||||
|
||||
### 🎯 目标
|
||||
重构前端代码,提供现代化的用户界面和交互体验。
|
||||
|
||||
### 📋 任务清单
|
||||
|
||||
#### 4.1 类型定义更新 (1天)
|
||||
- [ ] **TypeScript 类型重写**
|
||||
- [ ] 基于新后端 API 更新类型定义
|
||||
- [ ] 添加 SDK 特有的类型
|
||||
- [ ] 类型安全验证
|
||||
|
||||
#### 4.2 服务层重构 (2天)
|
||||
- [ ] **前端服务重写**
|
||||
- [ ] 重写 `ComfyuiService` 类
|
||||
- [ ] 添加实时通信支持
|
||||
- [ ] 错误处理优化
|
||||
- [ ] **状态管理优化**
|
||||
- [ ] Zustand store 重构
|
||||
- [ ] 实时状态同步
|
||||
- [ ] 缓存策略实现
|
||||
|
||||
#### 4.3 UI 组件优化 (3天)
|
||||
- [ ] **核心组件重构**
|
||||
- [ ] 工作流管理界面
|
||||
- [ ] 模板选择器
|
||||
- [ ] 执行监控面板
|
||||
- [ ] 实时进度显示
|
||||
- [ ] **用户体验优化**
|
||||
- [ ] 加载状态优化
|
||||
- [ ] 错误提示改进
|
||||
- [ ] 响应式设计完善
|
||||
|
||||
#### 4.4 高级功能界面 (1天)
|
||||
- [ ] **新功能界面**
|
||||
- [ ] 模板编辑器
|
||||
- [ ] 批量操作界面
|
||||
- [ ] 性能监控面板
|
||||
- [ ] 队列管理界面
|
||||
|
||||
### 🧪 测试要求
|
||||
- [ ] 组件单元测试
|
||||
- [ ] E2E 测试覆盖
|
||||
- [ ] 用户体验测试
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 文件结构规划
|
||||
|
||||
### 后端文件结构
|
||||
```
|
||||
apps/desktop/src-tauri/src/
|
||||
├── business/
|
||||
│ ├── services/
|
||||
│ │ ├── comfyui_manager.rs # 核心管理器
|
||||
│ │ ├── template_engine.rs # 模板引擎
|
||||
│ │ ├── execution_engine.rs # 执行引擎
|
||||
│ │ └── realtime_monitor.rs # 实时监控
|
||||
│ └── models/
|
||||
│ └── comfyui/ # 新的数据模型
|
||||
├── data/
|
||||
│ ├── repositories/
|
||||
│ │ └── comfyui_repository.rs # 数据访问层
|
||||
│ └── migrations/
|
||||
│ └── comfyui_tables.sql # 数据库迁移
|
||||
├── presentation/
|
||||
│ └── commands/
|
||||
│ └── comfyui_commands.rs # 重写的命令
|
||||
└── infrastructure/
|
||||
└── comfyui/ # SDK 集成层
|
||||
```
|
||||
|
||||
### 前端文件结构
|
||||
```
|
||||
apps/desktop/src/
|
||||
├── services/
|
||||
│ └── comfyuiService.ts # 重写的服务
|
||||
├── types/
|
||||
│ └── comfyui.ts # 更新的类型定义
|
||||
├── components/
|
||||
│ ├── ComfyUI/ # ComfyUI 相关组件
|
||||
│ │ ├── WorkflowManager.tsx
|
||||
│ │ ├── TemplateSelector.tsx
|
||||
│ │ ├── ExecutionMonitor.tsx
|
||||
│ │ └── RealtimeProgress.tsx
|
||||
│ └── common/ # 通用组件
|
||||
├── pages/
|
||||
│ ├── ComfyUIManagement.tsx # 重构的管理页面
|
||||
│ └── ComfyUIWorkflowTest.tsx # 重构的测试页面
|
||||
└── store/
|
||||
└── comfyuiStore.ts # 重构的状态管理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 风险评估与应对策略
|
||||
|
||||
### 高风险项
|
||||
1. **数据迁移风险**
|
||||
- 风险: 现有数据丢失或损坏
|
||||
- 应对: 完整的数据备份和迁移脚本测试
|
||||
|
||||
2. **功能兼容性风险**
|
||||
- 风险: 新实现与现有功能不兼容
|
||||
- 应对: 详细的功能对比测试
|
||||
|
||||
3. **性能回退风险**
|
||||
- 风险: 新实现性能不如旧版本
|
||||
- 应对: 性能基准测试和优化
|
||||
|
||||
### 中等风险项
|
||||
1. **开发周期延长**
|
||||
- 风险: 复杂度超出预期
|
||||
- 应对: 分阶段交付,及时调整计划
|
||||
|
||||
2. **用户体验变化**
|
||||
- 风险: 用户不适应新界面
|
||||
- 应对: 渐进式界面更新,保留熟悉元素
|
||||
|
||||
### 应对措施
|
||||
- [ ] 每周进行风险评估
|
||||
- [ ] 建立回滚机制
|
||||
- [ ] 充分的测试覆盖
|
||||
- [ ] 用户反馈收集
|
||||
|
||||
---
|
||||
|
||||
## 📊 成功指标
|
||||
|
||||
### 技术指标
|
||||
- [ ] 代码覆盖率 > 85%
|
||||
- [ ] API 响应时间 < 200ms
|
||||
- [ ] WebSocket 连接稳定性 > 99%
|
||||
- [ ] 内存使用优化 > 20%
|
||||
|
||||
### 功能指标
|
||||
- [ ] 支持所有现有功能
|
||||
- [ ] 新增实时监控功能
|
||||
- [ ] 新增模板管理功能
|
||||
- [ ] 新增批量操作功能
|
||||
|
||||
### 用户体验指标
|
||||
- [ ] 界面响应速度提升 > 30%
|
||||
- [ ] 错误率降低 > 50%
|
||||
- [ ] 用户操作步骤减少 > 20%
|
||||
|
||||
---
|
||||
|
||||
## 🚀 开始执行
|
||||
|
||||
### 立即行动项
|
||||
1. [ ] 确认开发计划和时间安排
|
||||
2. [ ] 备份现有代码和数据
|
||||
3. [ ] 创建开发分支 `feature/comfyui-sdk-rewrite`
|
||||
4. [ ] 开始第一阶段开发
|
||||
|
||||
### 每日检查项
|
||||
- [ ] 代码提交和备份
|
||||
- [ ] 测试执行和结果记录
|
||||
- [ ] 进度更新和风险评估
|
||||
- [ ] 团队沟通和问题解决
|
||||
|
||||
---
|
||||
|
||||
## 📝 实施细节补充
|
||||
|
||||
### 关键技术决策
|
||||
|
||||
#### 1. 数据库设计
|
||||
```sql
|
||||
-- 工作流表
|
||||
CREATE TABLE comfyui_workflows (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
workflow_data TEXT NOT NULL, -- JSON
|
||||
version TEXT DEFAULT '1.0',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 模板表
|
||||
CREATE TABLE comfyui_templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT,
|
||||
description TEXT,
|
||||
template_data TEXT NOT NULL, -- JSON
|
||||
parameter_schema TEXT NOT NULL, -- JSON
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 执行记录表
|
||||
CREATE TABLE comfyui_executions (
|
||||
id TEXT PRIMARY KEY,
|
||||
workflow_id TEXT,
|
||||
template_id TEXT,
|
||||
prompt_id TEXT,
|
||||
status TEXT NOT NULL, -- pending, running, completed, failed
|
||||
parameters TEXT, -- JSON
|
||||
results TEXT, -- JSON
|
||||
error_message TEXT,
|
||||
execution_time INTEGER, -- milliseconds
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
#### 2. 核心服务接口设计
|
||||
```rust
|
||||
// ComfyUIManager 核心接口
|
||||
pub trait ComfyUIManagerTrait {
|
||||
async fn connect(&mut self) -> Result<()>;
|
||||
async fn disconnect(&mut self) -> Result<()>;
|
||||
async fn is_connected(&self) -> bool;
|
||||
async fn get_system_info(&self) -> Result<SystemInfo>;
|
||||
async fn health_check(&self) -> Result<HealthStatus>;
|
||||
}
|
||||
|
||||
// TemplateEngine 接口
|
||||
pub trait TemplateEngineTrait {
|
||||
async fn load_template(&self, id: &str) -> Result<WorkflowTemplate>;
|
||||
async fn validate_parameters(&self, template_id: &str, params: &ParameterValues) -> Result<ValidationResult>;
|
||||
async fn create_instance(&self, template_id: &str, params: ParameterValues) -> Result<WorkflowInstance>;
|
||||
}
|
||||
|
||||
// ExecutionEngine 接口
|
||||
pub trait ExecutionEngineTrait {
|
||||
async fn execute_workflow(&self, workflow: &WorkflowInstance) -> Result<ExecutionResult>;
|
||||
async fn execute_template(&self, template_id: &str, params: ParameterValues) -> Result<ExecutionResult>;
|
||||
async fn cancel_execution(&self, execution_id: &str) -> Result<()>;
|
||||
async fn get_execution_status(&self, execution_id: &str) -> Result<ExecutionStatus>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 错误处理策略
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ComfyUIError {
|
||||
#[error("Connection error: {0}")]
|
||||
Connection(String),
|
||||
|
||||
#[error("Template error: {0}")]
|
||||
Template(String),
|
||||
|
||||
#[error("Execution error: {0}")]
|
||||
Execution(String),
|
||||
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(String),
|
||||
|
||||
#[error("SDK error: {0}")]
|
||||
Sdk(#[from] comfyui_sdk::error::ComfyUIError),
|
||||
}
|
||||
```
|
||||
|
||||
### 开发环境准备
|
||||
|
||||
#### 必需工具
|
||||
- [ ] Rust 1.70+
|
||||
- [ ] Node.js 18+
|
||||
- [ ] PNPM 8+
|
||||
- [ ] SQLite 3
|
||||
- [ ] ComfyUI 服务器 (用于测试)
|
||||
|
||||
#### 开发配置
|
||||
```toml
|
||||
# Cargo.toml 依赖更新
|
||||
[dependencies]
|
||||
comfyui-sdk = { path = "../../../cargos/comfyui-sdk", features = ["websocket", "templates"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
||||
```
|
||||
|
||||
### 测试策略
|
||||
|
||||
#### 单元测试
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_comfyui_manager_connect() {
|
||||
// 测试连接功能
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_template_validation() {
|
||||
// 测试模板验证
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_workflow_execution() {
|
||||
// 测试工作流执行
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 集成测试
|
||||
- [ ] 端到端工作流测试
|
||||
- [ ] WebSocket 连接测试
|
||||
- [ ] 数据库操作测试
|
||||
- [ ] 错误恢复测试
|
||||
|
||||
### 部署和发布
|
||||
|
||||
#### 发布检查清单
|
||||
- [ ] 所有测试通过
|
||||
- [ ] 性能指标达标
|
||||
- [ ] 文档更新完成
|
||||
- [ ] 用户反馈收集
|
||||
- [ ] 回滚方案准备
|
||||
|
||||
#### 发布策略
|
||||
1. **Alpha 版本**: 内部测试 (第2周末)
|
||||
2. **Beta 版本**: 小范围用户测试 (第3周末)
|
||||
3. **正式版本**: 全量发布 (第4周末)
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系和支持
|
||||
|
||||
### 技术支持
|
||||
- **开发问题**: 查看项目 Issues
|
||||
- **SDK 问题**: 参考 `cargos/comfyui-sdk/README.md`
|
||||
- **架构问题**: 联系架构师
|
||||
|
||||
### 文档资源
|
||||
- [ ] API 文档: `docs/api/comfyui.md`
|
||||
- [ ] 开发指南: `docs/development/comfyui-integration.md`
|
||||
- [ ] 故障排除: `docs/troubleshooting/comfyui.md`
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-01-08
|
||||
**负责人**: 开发团队
|
||||
**审核人**: 项目经理
|
||||
**版本**: v1.0
|
||||
|
||||
> 💡 **提示**: 这是一个完全重写的计划,需要充分的准备和测试。建议在开始前进行详细的技术评审和团队讨论。
|
||||
|
||||
> ⚠️ **重要**: 开始开发前请务必备份现有代码和数据,并创建独立的开发分支。
|
||||
@@ -1,295 +0,0 @@
|
||||
# TVAI 高级参数扩展总结
|
||||
|
||||
## 🎯 基于示例命令的参数扩展
|
||||
|
||||
### 参考的FFmpeg命令
|
||||
```bash
|
||||
ffmpeg "-hide_banner" "-t" "5.016666484785481" "-ss" "0" "-i" "input.mp4"
|
||||
"-sws_flags" "spline+accurate_rnd+full_chroma_int"
|
||||
"-filter_complex" "tvai_up=model=prob-4:scale=0:w=2160:h=3840:preblur=0:noise=0:details=0:halo=0:blur=0:compression=0:estimate=8:blend=0.2:device=-2:vram=1:instances=1,scale=w=2160:h=3840:flags=lanczos:threads=0:force_original_aspect_ratio=decrease,pad=2160:3840:-1:-1:color=black"
|
||||
"-c:v" "h264_nvenc" "-profile:v" "high" "-pix_fmt" "yuv420p" "-g" "30"
|
||||
"-preset" "p7" "-tune" "hq" "-rc" "constqp" "-qp" "18" "-rc-lookahead" "20"
|
||||
"-spatial_aq" "1" "-aq-strength" "15" "-b:v" "0" "-an"
|
||||
"-map_metadata" "0" "-map_metadata:s:v" "0:s:v" "-fps_mode:v" "passthrough"
|
||||
"-movflags" "frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr"
|
||||
"-bf" "0" "-metadata" "videoai=Enhanced using prob-4..." "output.mp4"
|
||||
```
|
||||
|
||||
## 📊 新增参数分类
|
||||
|
||||
### 1. 高级TVAI参数 (9个)
|
||||
```rust
|
||||
pub preblur: f32, // 预模糊 (0-100)
|
||||
pub noise: f32, // 噪点减少 (0-100)
|
||||
pub details: f32, // 细节恢复 (0-100)
|
||||
pub halo: f32, // 光晕减少 (0-100)
|
||||
pub blur: f32, // 模糊减少 (0-100)
|
||||
pub estimate: u32, // 估算质量 (1-20)
|
||||
pub device: i32, // 设备ID (-2=auto, -1=CPU, 0+=GPU)
|
||||
pub vram: u32, // VRAM使用 (0-1)
|
||||
pub instances: u32, // 实例数量 (1-4)
|
||||
```
|
||||
|
||||
### 2. 输出尺寸控制 (4个)
|
||||
```rust
|
||||
pub target_width: Option<u32>, // 目标宽度
|
||||
pub target_height: Option<u32>, // 目标高度
|
||||
pub maintain_aspect: bool, // 保持宽高比
|
||||
pub pad_color: String, // 填充颜色
|
||||
```
|
||||
|
||||
### 3. 编码参数 (13个)
|
||||
```rust
|
||||
pub codec: Option<String>, // 编码器 (h264_nvenc, hevc_nvenc, libx264等)
|
||||
pub profile: Option<String>, // 编码配置文件 (high, main, baseline)
|
||||
pub pixel_format: String, // 像素格式 (yuv420p, yuv444p等)
|
||||
pub gop_size: u32, // GOP大小 (关键帧间隔)
|
||||
pub preset: Option<String>, // 编码预设 (p1-p7, ultrafast-veryslow)
|
||||
pub tune: Option<String>, // 调优选项 (hq, ll, ull等)
|
||||
pub rate_control: String, // 码率控制模式 (constqp, vbr, cbr)
|
||||
pub quality_param: u32, // 质量参数 (CRF/QP值)
|
||||
pub lookahead: u32, // 前瞻帧数 (0-32)
|
||||
pub spatial_aq: bool, // 空间自适应量化
|
||||
pub aq_strength: u32, // AQ强度 (1-15)
|
||||
pub bitrate: u32, // 目标码率 (0=VBR)
|
||||
pub buffer_frames: u32, // 缓冲帧数 (B帧数量)
|
||||
```
|
||||
|
||||
### 4. 音频处理 (1个枚举)
|
||||
```rust
|
||||
pub enum AudioMode {
|
||||
Keep, // 保留原音频
|
||||
Remove, // 移除音频
|
||||
Reencode { codec: String, bitrate: u32 }, // 重新编码
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 元数据控制 (2个)
|
||||
```rust
|
||||
pub preserve_metadata: bool, // 保留元数据
|
||||
pub custom_metadata: Option<String>, // 自定义元数据
|
||||
```
|
||||
|
||||
## 🎛️ 预设配置示例
|
||||
|
||||
### 最高质量预设
|
||||
```rust
|
||||
pub fn for_maximum_quality() -> Self {
|
||||
Self {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Ahq12,
|
||||
compression: 0.0,
|
||||
blend: 0.0,
|
||||
preblur: 0.0,
|
||||
noise: 30.0, // 强噪点减少
|
||||
details: 60.0, // 强细节恢复
|
||||
halo: 30.0, // 强光晕减少
|
||||
blur: 40.0, // 强模糊减少
|
||||
estimate: 20, // 最高估算质量
|
||||
quality_param: 12, // 最高质量
|
||||
lookahead: 32, // 最大前瞻
|
||||
instances: 1, // 单实例确保质量
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 快速处理预设
|
||||
```rust
|
||||
pub fn for_fast_processing() -> Self {
|
||||
Self {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Alqs2,
|
||||
compression: 0.2,
|
||||
blend: 0.0,
|
||||
preblur: 0.0,
|
||||
noise: 10.0, // 轻度噪点减少
|
||||
details: 20.0, // 轻度细节恢复
|
||||
halo: 5.0, // 轻度光晕减少
|
||||
blur: 10.0, // 轻度模糊减少
|
||||
estimate: 4, // 较低估算质量
|
||||
quality_param: 22, // 较低质量但更快
|
||||
preset: Some("p1".to_string()), // 最快预设
|
||||
instances: 2, // 多实例加速
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 老视频修复预设
|
||||
```rust
|
||||
pub fn for_old_video() -> Self {
|
||||
Self {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Thf4,
|
||||
compression: 0.3,
|
||||
blend: 0.2,
|
||||
preblur: 0.0,
|
||||
noise: 20.0, // 中等噪点减少
|
||||
details: 30.0, // 中等细节恢复
|
||||
halo: 10.0, // 轻度光晕减少
|
||||
blur: 15.0, // 轻度模糊减少
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 技术实现细节
|
||||
|
||||
### 滤镜构建
|
||||
```rust
|
||||
fn build_upscale_filter(&self, params: &VideoUpscaleParams) -> Result<String, TvaiError> {
|
||||
let tvai_params = vec![
|
||||
format!("model={}", params.model.as_str()),
|
||||
format!("scale={}", if params.scale_factor == 0.0 { 0 } else { params.scale_factor as u32 }),
|
||||
format!("preblur={}", params.preblur),
|
||||
format!("noise={}", params.noise),
|
||||
format!("details={}", params.details),
|
||||
format!("halo={}", params.halo),
|
||||
format!("blur={}", params.blur),
|
||||
format!("compression={}", params.compression),
|
||||
format!("estimate={}", params.estimate),
|
||||
format!("blend={}", params.blend),
|
||||
format!("device={}", params.device),
|
||||
format!("vram={}", params.vram),
|
||||
format!("instances={}", params.instances),
|
||||
];
|
||||
|
||||
let tvai_filter = format!("tvai_up={}", tvai_params.join(":"));
|
||||
// 如果需要,添加缩放和填充滤镜...
|
||||
}
|
||||
```
|
||||
|
||||
### 编码参数构建
|
||||
```rust
|
||||
fn build_encoding_args(&self, params: &VideoUpscaleParams) -> Result<Vec<String>, TvaiError> {
|
||||
let mut args = Vec::new();
|
||||
|
||||
// 自动选择编码器
|
||||
let codec = params.codec.as_deref().unwrap_or_else(|| {
|
||||
if self.is_gpu_enabled() { "h264_nvenc" } else { "libx264" }
|
||||
});
|
||||
|
||||
// GPU编码器特定参数
|
||||
if codec.contains("nvenc") {
|
||||
args.extend_from_slice(&[
|
||||
"-preset".to_string(), params.preset.clone().unwrap_or("p7".to_string()),
|
||||
"-tune".to_string(), params.tune.clone().unwrap_or("hq".to_string()),
|
||||
"-rc".to_string(), params.rate_control.clone(),
|
||||
"-qp".to_string(), params.quality_param.to_string(),
|
||||
"-rc-lookahead".to_string(), params.lookahead.to_string(),
|
||||
]);
|
||||
|
||||
if params.spatial_aq {
|
||||
args.extend_from_slice(&[
|
||||
"-spatial_aq".to_string(), "1".to_string(),
|
||||
"-aq-strength".to_string(), params.aq_strength.to_string(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
```
|
||||
|
||||
### 音频处理
|
||||
```rust
|
||||
fn build_audio_args(&self, audio_mode: &AudioMode) -> Result<Vec<String>, TvaiError> {
|
||||
match audio_mode {
|
||||
AudioMode::Keep => vec!["-c:a".to_string(), "copy".to_string()],
|
||||
AudioMode::Remove => vec!["-an".to_string()],
|
||||
AudioMode::Reencode { codec, bitrate } => vec![
|
||||
"-c:a".to_string(), codec.clone(),
|
||||
"-b:a".to_string(), format!("{}k", bitrate)
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📈 参数对比
|
||||
|
||||
| 参数类别 | 原版本 | 新版本 | 增加数量 |
|
||||
|----------|--------|--------|----------|
|
||||
| 基础参数 | 5个 | 5个 | 0 |
|
||||
| TVAI参数 | 0个 | 9个 | +9 |
|
||||
| 尺寸控制 | 0个 | 4个 | +4 |
|
||||
| 编码参数 | 0个 | 13个 | +13 |
|
||||
| 音频处理 | 0个 | 1个 | +1 |
|
||||
| 元数据 | 0个 | 2个 | +2 |
|
||||
| **总计** | **5个** | **34个** | **+29** |
|
||||
|
||||
## 🎯 用户体验提升
|
||||
|
||||
### 专业级控制
|
||||
- **细粒度调节**: 每个TVAI参数都可以精确控制
|
||||
- **编码优化**: 支持所有主流编码器和参数
|
||||
- **质量平衡**: 可以在质量和速度之间精确平衡
|
||||
|
||||
### 智能默认值
|
||||
- **自动检测**: 设备、编码器自动选择
|
||||
- **合理默认**: 所有参数都有经过测试的默认值
|
||||
- **预设系统**: 针对不同场景的优化预设
|
||||
|
||||
### 灵活配置
|
||||
- **可选参数**: 大部分参数都是可选的
|
||||
- **向后兼容**: 保持与原有API的兼容性
|
||||
- **类型安全**: 完整的TypeScript类型定义
|
||||
|
||||
## 🚀 实际应用场景
|
||||
|
||||
### 1. 专业视频制作
|
||||
```rust
|
||||
VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Ahq12,
|
||||
target_width: Some(3840),
|
||||
target_height: Some(2160),
|
||||
quality_param: 12,
|
||||
preset: Some("p7".to_string()),
|
||||
tune: Some("hq".to_string()),
|
||||
spatial_aq: true,
|
||||
aq_strength: 15,
|
||||
audio_mode: AudioMode::Keep,
|
||||
..Default::default()
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 快速预览
|
||||
```rust
|
||||
VideoUpscaleParams {
|
||||
scale_factor: 1.5,
|
||||
model: UpscaleModel::Alqs2,
|
||||
quality_param: 28,
|
||||
preset: Some("p1".to_string()),
|
||||
instances: 2,
|
||||
audio_mode: AudioMode::Remove,
|
||||
..Default::default()
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 老视频修复
|
||||
```rust
|
||||
VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Thf4,
|
||||
noise: 25.0,
|
||||
details: 35.0,
|
||||
halo: 15.0,
|
||||
blur: 20.0,
|
||||
compression: 0.3,
|
||||
blend: 0.2,
|
||||
..Default::default()
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过参考实际的FFmpeg命令,我们将TVAI的可控参数从5个扩展到34个,增加了29个新参数。这些参数覆盖了:
|
||||
|
||||
✅ **完整的TVAI控制** - 所有AI增强参数
|
||||
✅ **专业编码选项** - GPU/CPU编码器的所有参数
|
||||
✅ **灵活的音频处理** - 保留、移除、重编码
|
||||
✅ **智能尺寸控制** - 目标分辨率和宽高比
|
||||
✅ **元数据管理** - 保留和自定义元数据
|
||||
|
||||
这使得TVAI工具能够满足从快速预览到专业制作的各种需求,提供了与原生Topaz Video AI相同级别的控制能力。
|
||||
@@ -1,192 +0,0 @@
|
||||
# TVAI 完整模型支持和插帧功能总结
|
||||
|
||||
## 🔍 问题分析
|
||||
|
||||
您提出的问题完全正确:
|
||||
|
||||
1. **模型不完整** - 原界面只显示了6个模型,实际SDK支持16个放大模型
|
||||
2. **缺少插帧功能** - 没有提供视频插帧(帧率提升)功能
|
||||
3. **模型用途不明确** - 用户不知道每个模型的具体用途
|
||||
|
||||
## ✅ 完整解决方案
|
||||
|
||||
### 1. 完整的放大模型列表(16个)
|
||||
|
||||
#### 通用模型
|
||||
- **Iris v3** - 最佳通用模型,适合大多数内容
|
||||
- **Iris v2** - 平衡质量和速度的通用模型
|
||||
|
||||
#### Artemis 系列(6个)
|
||||
- **Artemis HQ v12** - 高质量处理,速度较慢
|
||||
- **Artemis LQ v13** - 专门优化低质量输入
|
||||
- **Artemis LQ Small v2** - 快速处理低质量内容
|
||||
- **Artemis MQ v13** - 中等质量平衡处理
|
||||
- **Artemis MQ Small v2** - 快速中等质量处理
|
||||
- **Artemis Anti-Alias v9** - 专门处理锯齿问题
|
||||
|
||||
#### 专用模型(3个)
|
||||
- **Nyx v3** - 人像和面部优化
|
||||
- **Gaia HQ v5** - 游戏截图和CG内容
|
||||
- **Proteus v4** - 问题素材修复(噪点、伪影等)
|
||||
|
||||
#### Theia 系列(3个)
|
||||
- **Theia Fidelity v4** - 老旧内容修复和保真度
|
||||
- **Theia Detail v3** - 细节增强
|
||||
- **Theia Magic v2** - 魔法增强(强制1x缩放)
|
||||
|
||||
#### Rhea 系列(2个)
|
||||
- **Rhea v1** - 真实感增强
|
||||
- **Rhea XL v1** - 超大分辨率处理
|
||||
|
||||
### 2. 插帧模型列表(4个)
|
||||
|
||||
#### Apollo 系列
|
||||
- **Apollo v8** - 高质量插帧,最佳效果
|
||||
- **Apollo Fast v1** - 快速插帧,速度优先
|
||||
|
||||
#### Chronos 系列
|
||||
- **Chronos v2** - 专门针对动画内容
|
||||
- **Chronos Fast v3** - 快速动画插帧
|
||||
|
||||
### 3. 新增功能
|
||||
|
||||
#### 三种处理类型
|
||||
```
|
||||
🎬 视频放大 ⏱️ 视频插帧 📷 图片增强
|
||||
提升视频分辨率 增加帧率制作慢动作 提升图片分辨率
|
||||
```
|
||||
|
||||
#### 插帧参数设置
|
||||
- **插帧倍数**: 1.5x, 2x, 2.5x, 3x, 4x, 8x
|
||||
- **目标帧率**: 自动计算(例如:30fps × 2x = 60fps)
|
||||
- **插帧模型**: 4种专业插帧模型
|
||||
|
||||
#### 智能预设系统
|
||||
|
||||
**放大预设**:
|
||||
- 老视频 → Theia Fidelity v4
|
||||
- 游戏内容 → Gaia HQ v5
|
||||
- 动画 → Iris v3
|
||||
- 人像 → Nyx v3
|
||||
- 通用 → Iris v3
|
||||
|
||||
**插帧预设**:
|
||||
- 高质量 → Apollo v8
|
||||
- 动画 → Chronos v2
|
||||
- 快速 → Apollo Fast v1
|
||||
- 快速动画 → Chronos Fast v3
|
||||
|
||||
## 🎯 界面改进
|
||||
|
||||
### 模型选择优化
|
||||
```html
|
||||
<optgroup label="通用模型">
|
||||
<option value="iris-3">Iris v3 (最佳通用模型)</option>
|
||||
<option value="iris-2">Iris v2 (平衡质量和速度)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Artemis 系列">
|
||||
<option value="ahq-12">Artemis HQ v12 (高质量)</option>
|
||||
<option value="alq-13">Artemis LQ v13 (低质量输入优化)</option>
|
||||
<!-- ... 更多选项 ... -->
|
||||
</optgroup>
|
||||
```
|
||||
|
||||
### 动态界面
|
||||
- 根据处理类型(放大/插帧/图片)显示对应的模型和参数
|
||||
- 插帧模式显示插帧倍数而非放大倍数
|
||||
- 不同处理类型使用不同的主题色彩
|
||||
|
||||
### 用户引导
|
||||
- 每个模型都有清晰的中文说明
|
||||
- 预设按钮提供最佳实践配置
|
||||
- 参数说明帮助用户理解功能
|
||||
|
||||
## 📊 功能对比
|
||||
|
||||
| 功能 | 原版本 | 新版本 |
|
||||
|------|--------|--------|
|
||||
| 放大模型 | 6个 | 16个(完整) |
|
||||
| 插帧功能 | ❌ 无 | ✅ 4个专业模型 |
|
||||
| 模型分类 | ❌ 无 | ✅ 按系列分组 |
|
||||
| 中文说明 | ❌ 部分 | ✅ 完整中文 |
|
||||
| 智能预设 | ✅ 5个 | ✅ 9个(5个放大+4个插帧) |
|
||||
| 处理类型 | 2种 | 3种(新增插帧) |
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 类型定义扩展
|
||||
```typescript
|
||||
type ProcessingType = 'video' | 'image' | 'interpolation';
|
||||
|
||||
// 插帧参数
|
||||
const [interpolationMultiplier, setInterpolationMultiplier] = useState(2.0);
|
||||
const [targetFps, setTargetFps] = useState(60);
|
||||
```
|
||||
|
||||
### 动态模型选择
|
||||
```typescript
|
||||
{processingType === 'interpolation' ? (
|
||||
// 显示插帧模型
|
||||
<>
|
||||
<option value="apo-8">Apollo v8 (高质量插帧)</option>
|
||||
<option value="chr-2">Chronos v2 (动画内容)</option>
|
||||
// ...
|
||||
</>
|
||||
) : (
|
||||
// 显示放大模型(完整16个)
|
||||
<>
|
||||
<optgroup label="通用模型">
|
||||
<option value="iris-3">Iris v3 (最佳通用模型)</option>
|
||||
// ...
|
||||
</optgroup>
|
||||
</>
|
||||
)}
|
||||
```
|
||||
|
||||
### 预设系统
|
||||
```typescript
|
||||
// 插帧预设
|
||||
const interpolationPresets = [
|
||||
{ key: 'APO8', name: '高质量', model: 'apo-8' },
|
||||
{ key: 'CHR2', name: '动画', model: 'chr-2' },
|
||||
{ key: 'APF1', name: '快速', model: 'apf-1' },
|
||||
{ key: 'CHF3', name: '快速动画', model: 'chf-3' }
|
||||
];
|
||||
```
|
||||
|
||||
## 🚀 使用场景
|
||||
|
||||
### 视频放大
|
||||
- **老电影修复**: Theia Fidelity v4
|
||||
- **游戏录屏**: Gaia HQ v5
|
||||
- **动画片**: Iris v3 + 动画预设
|
||||
- **人像视频**: Nyx v3
|
||||
|
||||
### 视频插帧
|
||||
- **慢动作制作**: Apollo v8 + 2-4x插帧
|
||||
- **动画补帧**: Chronos v2 + 2x插帧
|
||||
- **快速预览**: Apollo Fast v1 + 1.5x插帧
|
||||
|
||||
### 图片增强
|
||||
- **照片放大**: Iris v3 + 2-4x放大
|
||||
- **人像照片**: Nyx v3 + 2x放大
|
||||
- **游戏截图**: Gaia HQ v5 + 2-3x放大
|
||||
|
||||
## 📈 预期效果
|
||||
|
||||
1. **功能完整性** - 支持所有TVAI核心功能
|
||||
2. **用户体验** - 清晰的模型分类和说明
|
||||
3. **专业性** - 完整的参数控制
|
||||
4. **易用性** - 智能预设降低使用门槛
|
||||
5. **扩展性** - 为未来新模型预留空间
|
||||
|
||||
## 总结
|
||||
|
||||
通过这次完善,TVAI工具现在提供了:
|
||||
- ✅ 完整的16个放大模型支持
|
||||
- ✅ 新增4个插帧模型和插帧功能
|
||||
- ✅ 清晰的模型分类和中文说明
|
||||
- ✅ 智能预设系统
|
||||
- ✅ 动态界面适配不同处理类型
|
||||
|
||||
这使得工具既保持了专业性,又大大提升了易用性,满足从新手到专家的各种需求。
|
||||
@@ -1,226 +0,0 @@
|
||||
# TVAI Hooks 错误修复和文件选择功能改进总结
|
||||
|
||||
## 🐛 问题诊断
|
||||
|
||||
### React Hooks 错误
|
||||
```
|
||||
Uncaught Error: Rendered fewer hooks than expected.
|
||||
This may be caused by an accidental early return statement.
|
||||
```
|
||||
|
||||
**根本原因**: 在React组件中,Hooks必须在每次渲染时以相同的顺序调用。如果在早期返回语句之后调用Hooks,会导致Hooks数量不一致的错误。
|
||||
|
||||
### 问题代码结构
|
||||
```typescript
|
||||
export function TvaiExample() {
|
||||
// ✅ 正确:Hooks在早期返回之前
|
||||
const [state1] = useState();
|
||||
const [state2] = useState();
|
||||
|
||||
// ❌ 错误:早期返回
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// ❌ 错误:Hooks在早期返回之后
|
||||
const callback = useCallback(() => {}, []);
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 修复方案
|
||||
|
||||
### 1. Hooks 重新排序
|
||||
将所有Hooks调用移到早期返回语句之前:
|
||||
|
||||
```typescript
|
||||
export function TvaiExample() {
|
||||
// ✅ 所有Hooks必须在早期返回之前
|
||||
const { systemInfo, isLoading: systemLoading, initializeTvai } = useTvaiSystemInfo();
|
||||
const { tasks, isLoading: tasksLoading, cancelTask, cleanupCompletedTasks } = useTvai();
|
||||
|
||||
// 状态Hooks
|
||||
const [processingType, setProcessingType] = useState<'video' | 'image' | 'interpolation'>('video');
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
const [inputPath, setInputPath] = useState('');
|
||||
const [outputPath, setOutputPath] = useState('');
|
||||
const [scaleFactor, setScaleFactor] = useState(2.0);
|
||||
const [selectedPreset, setSelectedPreset] = useState('GENERAL');
|
||||
const [selectedModel, setSelectedModel] = useState<UpscaleModel>('iris-3');
|
||||
const [compression, setCompression] = useState(0.0);
|
||||
const [blend, setBlend] = useState(0.0);
|
||||
const [interpolationMultiplier, setInterpolationMultiplier] = useState(2.0);
|
||||
|
||||
// useCallback Hooks
|
||||
const handleSelectInputFile = useCallback(async () => { /* ... */ }, [processingType]);
|
||||
const handleSelectOutputFile = useCallback(async () => { /* ... */ }, [processingType]);
|
||||
|
||||
// 普通函数
|
||||
const applyPreset = (presetName: string) => { /* ... */ };
|
||||
const handleQuickProcess = async () => { /* ... */ };
|
||||
|
||||
// ✅ 早期返回在所有Hooks之后
|
||||
if (systemLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// 正常渲染
|
||||
return <div>...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 清理未使用的变量
|
||||
移除了以下未使用的状态变量:
|
||||
- `currentStep` 和 `setCurrentStep`
|
||||
- `qualityPreset` 和 `setQualityPreset`
|
||||
- `outputFormat` 和 `setOutputFormat`
|
||||
- `targetFps` 和 `setTargetFps`
|
||||
- `isDragging` 和 `setIsDragging`
|
||||
|
||||
### 3. 移除重复的函数定义
|
||||
删除了重复定义的函数:
|
||||
- 重复的 `applyPreset` 函数
|
||||
- 重复的 `handleQuickProcess` 函数
|
||||
- 重复的早期返回语句
|
||||
|
||||
## 🎯 文件选择功能改进
|
||||
|
||||
### 参考其他页面的实现
|
||||
通过分析 `ImageEditingTool.tsx` 和其他工具页面,采用了统一的文件选择模式:
|
||||
|
||||
```typescript
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
|
||||
const handleSelectFile = useCallback(async () => {
|
||||
try {
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters: [{ name: '视频文件', extensions: ['mp4', 'avi', 'mov'] }],
|
||||
title: '选择视频文件',
|
||||
});
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
setInputPath(selected);
|
||||
// 自动生成输出路径
|
||||
generateOutputPath(selected);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择文件失败:', error);
|
||||
alert('选择文件失败: ' + error.message);
|
||||
}
|
||||
}, [processingType]);
|
||||
```
|
||||
|
||||
### 文件格式配置
|
||||
```typescript
|
||||
const FILE_FORMATS = {
|
||||
video: {
|
||||
name: '视频文件',
|
||||
extensions: ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'm4v', 'webm', '3gp', 'ts']
|
||||
},
|
||||
image: {
|
||||
name: '图片文件',
|
||||
extensions: ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'webp', 'gif', 'svg']
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### UI 改进
|
||||
从简单的文本输入框改为专业的文件选择按钮:
|
||||
|
||||
**原来的设计**:
|
||||
```jsx
|
||||
<input
|
||||
type="text"
|
||||
value={inputPath}
|
||||
onChange={(e) => setInputPath(e.target.value)}
|
||||
placeholder="选择视频文件..."
|
||||
/>
|
||||
```
|
||||
|
||||
**新的设计**:
|
||||
```jsx
|
||||
<button
|
||||
onClick={handleSelectInputFile}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors text-left"
|
||||
>
|
||||
<FolderOpen className="w-5 h-5 text-gray-400 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
{inputPath ? (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{inputPath.split('/').pop() || inputPath.split('\\').pop()}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{inputPath}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-500">
|
||||
选择{processingType === 'image' ? '图片' : '视频'}文件...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
```
|
||||
|
||||
### 智能功能
|
||||
1. **自动路径生成**: 根据输入文件自动生成输出路径
|
||||
2. **动态文件类型**: 根据处理类型显示对应的文件格式
|
||||
3. **文件名显示**: 显示文件名和完整路径
|
||||
4. **一键生成**: 提供"自动生成输出路径"按钮
|
||||
|
||||
## 🔧 技术细节
|
||||
|
||||
### Hooks 调用顺序规则
|
||||
1. **自定义Hooks**: `useTvaiSystemInfo()`, `useTvai()`
|
||||
2. **状态Hooks**: `useState()`
|
||||
3. **效果Hooks**: `useCallback()`, `useMemo()`, `useEffect()`
|
||||
4. **早期返回**: 在所有Hooks之后
|
||||
5. **事件处理函数**: 普通函数,不是Hooks
|
||||
|
||||
### 文件选择最佳实践
|
||||
1. **统一API**: 使用 `@tauri-apps/plugin-dialog` 的 `open()` 函数
|
||||
2. **类型安全**: TypeScript 类型检查
|
||||
3. **错误处理**: try-catch 包装,用户友好的错误提示
|
||||
4. **用户体验**: 加载状态、进度反馈、自动完成
|
||||
|
||||
### 性能优化
|
||||
1. **useCallback**: 防止不必要的重新渲染
|
||||
2. **依赖数组**: 正确设置依赖项
|
||||
3. **条件渲染**: 避免不必要的计算
|
||||
|
||||
## 📊 修复前后对比
|
||||
|
||||
| 方面 | 修复前 | 修复后 |
|
||||
|------|--------|--------|
|
||||
| Hooks错误 | ❌ 运行时崩溃 | ✅ 正常运行 |
|
||||
| 文件选择 | ❌ 手动输入路径 | ✅ 原生文件对话框 |
|
||||
| 用户体验 | ❌ 复杂操作 | ✅ 一键选择 |
|
||||
| 错误处理 | ❌ 基础提示 | ✅ 详细错误信息 |
|
||||
| 路径生成 | ❌ 手动输入 | ✅ 自动生成 |
|
||||
| 文件格式 | ❌ 无限制 | ✅ 类型过滤 |
|
||||
|
||||
## 🚀 用户体验提升
|
||||
|
||||
### 操作流程简化
|
||||
1. **选择处理类型** → 自动设置文件格式过滤
|
||||
2. **点击选择文件** → 打开原生文件对话框
|
||||
3. **选择文件** → 自动生成输出路径
|
||||
4. **开始处理** → 一键启动
|
||||
|
||||
### 错误预防
|
||||
1. **文件格式验证**: 只允许选择支持的格式
|
||||
2. **路径验证**: 确保文件存在且可读
|
||||
3. **权限检查**: 确保输出路径可写
|
||||
4. **友好提示**: 清晰的错误信息和解决建议
|
||||
|
||||
## 总结
|
||||
|
||||
通过修复React Hooks的调用顺序问题和改进文件选择功能,TVAI工具现在具有:
|
||||
|
||||
✅ **稳定性** - 无运行时错误,符合React规范
|
||||
✅ **易用性** - 原生文件对话框,操作简单
|
||||
✅ **智能化** - 自动路径生成,类型过滤
|
||||
✅ **专业性** - 统一的UI设计,完善的错误处理
|
||||
|
||||
这些改进大大提升了用户体验,使TVAI工具更加稳定和易用。
|
||||
@@ -1,208 +0,0 @@
|
||||
# TVAI (Topaz Video AI) 集成完成总结
|
||||
|
||||
## 概述
|
||||
|
||||
已成功将 `cargos/tvai` SDK 封装为 Tauri 命令,提供完整的视频和图片 AI 增强功能。
|
||||
|
||||
## 已实现的功能
|
||||
|
||||
### 1. 后端 Rust 封装 (Tauri Commands)
|
||||
|
||||
**文件位置**: `apps/desktop/src-tauri/src/presentation/commands/tvai_commands.rs`
|
||||
|
||||
#### 系统检测命令
|
||||
- `detect_topaz_installation_command` - 检测 Topaz Video AI 安装
|
||||
- `detect_gpu_support_command` - 检测 GPU 支持情况
|
||||
- `detect_ffmpeg_command` - 检测 FFmpeg 安装
|
||||
|
||||
#### 配置管理命令
|
||||
- `initialize_tvai_config` - 初始化 TVAI 配置
|
||||
|
||||
#### 信息获取命令
|
||||
- `get_video_info_command` - 获取视频文件信息
|
||||
- `get_image_info_command` - 获取图片文件信息
|
||||
- `estimate_processing_time_command` - 估算处理时间
|
||||
|
||||
#### 处理命令
|
||||
- `quick_upscale_video_command` - 快速视频放大
|
||||
- `quick_upscale_image_command` - 快速图片放大
|
||||
- `auto_enhance_video_command` - 自动视频增强
|
||||
- `auto_enhance_image_command` - 自动图片增强(使用快速放大实现)
|
||||
- `upscale_video_advanced` - 高级视频放大(支持自定义参数)
|
||||
- `upscale_image_advanced` - 高级图片放大(支持自定义参数)
|
||||
|
||||
#### 任务管理命令
|
||||
- `get_tvai_task_status` - 获取任务状态
|
||||
- `get_all_tvai_tasks` - 获取所有任务
|
||||
- `cancel_tvai_task` - 取消任务
|
||||
- `cleanup_completed_tvai_tasks` - 清理已完成任务
|
||||
|
||||
### 2. 前端 TypeScript 封装
|
||||
|
||||
#### 类型定义
|
||||
**文件位置**: `apps/desktop/src/types/tvai.ts`
|
||||
|
||||
- `TvaiTask` - 任务信息
|
||||
- `TvaiTaskStatus` - 任务状态枚举
|
||||
- `VideoInfo` - 视频信息
|
||||
- `ImageInfo` - 图片信息
|
||||
- `GpuInfo` - GPU 信息
|
||||
- `FfmpegInfo` - FFmpeg 信息
|
||||
- `UpscaleModel` - 放大模型类型
|
||||
- `QualityPreset` - 质量预设类型
|
||||
- `ImageFormat` - 图片格式类型
|
||||
- `TvaiService` - 服务接口
|
||||
- `TVAI_PRESETS` - 预设配置常量
|
||||
|
||||
#### 服务层
|
||||
**文件位置**: `apps/desktop/src/services/tvaiService.ts`
|
||||
|
||||
- `TvaiServiceImpl` - 服务实现类
|
||||
- 事件监听机制
|
||||
- 便捷函数导出
|
||||
|
||||
#### React Hooks
|
||||
**文件位置**: `apps/desktop/src/hooks/useTvai.ts`
|
||||
|
||||
- `useTvai` - 任务管理 Hook
|
||||
- `useTvaiTask` - 单个任务状态 Hook
|
||||
- `useTvaiSystemInfo` - 系统信息 Hook
|
||||
|
||||
#### 示例组件
|
||||
**文件位置**: `apps/desktop/src/components/TvaiExample.tsx`
|
||||
|
||||
完整的功能演示组件,包含:
|
||||
- 系统信息显示
|
||||
- 参数配置界面
|
||||
- 快速预设应用
|
||||
- 任务列表管理
|
||||
|
||||
## 支持的 AI 模型
|
||||
|
||||
### 放大模型
|
||||
- `aaa-9`, `ahq-12`, `alq-13`, `alqs-2`, `amq-13`, `amqs-2`
|
||||
- `ghq-5`, `iris-2`, `iris-3`, `nyx-3`, `prob-4`, `thf-4`
|
||||
- `thd-3`, `thm-2`, `rhea-1`, `rxl-1`
|
||||
|
||||
### 插帧模型
|
||||
- `apo-8`, `apf-1`, `chr-2`, `chf-3`
|
||||
|
||||
### 质量预设
|
||||
- `fast`, `balanced`, `high_quality`, `maximum`
|
||||
|
||||
### 输出格式
|
||||
- `png`, `jpg`, `tiff`, `bmp`
|
||||
|
||||
## 架构特点
|
||||
|
||||
### 1. 异步任务处理
|
||||
- 所有处理操作都是异步的
|
||||
- 实时任务状态更新
|
||||
- 支持任务取消和进度监控
|
||||
|
||||
### 2. 事件驱动
|
||||
- 任务创建和更新事件
|
||||
- 前端实时响应状态变化
|
||||
|
||||
### 3. 类型安全
|
||||
- 完整的 TypeScript 类型定义
|
||||
- Rust 和 TypeScript 类型一致性
|
||||
|
||||
### 4. 预设配置
|
||||
- 针对不同内容类型的优化预设
|
||||
- 便于用户快速选择合适参数
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础使用
|
||||
```typescript
|
||||
import { tvaiService } from '../services/tvaiService';
|
||||
|
||||
// 检测系统
|
||||
const gpuInfo = await tvaiService.detectGpuSupport();
|
||||
const topazPath = await tvaiService.detectTopazInstallation();
|
||||
|
||||
// 初始化
|
||||
await tvaiService.initializeTvaiConfig({
|
||||
topaz_path: topazPath,
|
||||
use_gpu: true
|
||||
});
|
||||
|
||||
// 快速处理
|
||||
const taskId = await tvaiService.quickUpscaleVideo(
|
||||
'/path/to/input.mp4',
|
||||
'/path/to/output.mp4',
|
||||
2.0
|
||||
);
|
||||
```
|
||||
|
||||
### 使用 React Hooks
|
||||
```typescript
|
||||
import { useTvai, useTvaiSystemInfo } from '../hooks/useTvai';
|
||||
|
||||
function MyComponent() {
|
||||
const { systemInfo, initializeTvai } = useTvaiSystemInfo();
|
||||
const { tasks, cancelTask } = useTvai();
|
||||
|
||||
// 组件逻辑...
|
||||
}
|
||||
```
|
||||
|
||||
## 配置文件更新
|
||||
|
||||
### Cargo.toml
|
||||
已添加 tvai 依赖:
|
||||
```toml
|
||||
tvai = { path = "../../../cargos/tvai" }
|
||||
```
|
||||
|
||||
### lib.rs
|
||||
- 注册了 `TvaiState` 状态管理
|
||||
- 注册了所有 TVAI 命令
|
||||
|
||||
### commands/mod.rs
|
||||
- 添加了 `tvai_commands` 模块
|
||||
|
||||
## 文档
|
||||
|
||||
### 集成文档
|
||||
**文件位置**: `docs/tvai-integration.md`
|
||||
|
||||
详细的使用指南,包含:
|
||||
- API 参考
|
||||
- 使用示例
|
||||
- 错误处理
|
||||
- 性能优化建议
|
||||
- 故障排除
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 架构限制
|
||||
当前高级处理命令(`upscale_video_advanced`)由于架构限制暂时返回错误,需要重新设计以支持异步处理器使用。
|
||||
|
||||
### 2. 依赖要求
|
||||
- 需要安装 Topaz Video AI
|
||||
- 建议使用支持 CUDA 的 GPU
|
||||
- 需要 FFmpeg 支持
|
||||
|
||||
### 3. 性能考虑
|
||||
- GPU 加速显著提升处理速度
|
||||
- 大文件处理需要充足的磁盘空间
|
||||
- 建议设置 SSD 作为临时目录
|
||||
|
||||
## 下一步改进
|
||||
|
||||
1. **架构优化**: 重新设计处理器架构以支持真正的异步处理
|
||||
2. **批量处理**: 添加批量文件处理功能
|
||||
3. **进度回调**: 实现详细的处理进度回调
|
||||
4. **预览功能**: 添加处理前后对比预览
|
||||
5. **配置持久化**: 保存用户偏好设置
|
||||
6. **错误恢复**: 实现更好的错误处理和恢复机制
|
||||
|
||||
## 编译状态
|
||||
|
||||
✅ **编译成功** - 所有代码已通过 Rust 编译检查,可以正常构建和运行。
|
||||
|
||||
## 总结
|
||||
|
||||
TVAI 集成已完成基础功能实现,提供了完整的视频和图片 AI 增强能力。虽然还有一些架构优化空间,但当前版本已经可以满足基本的使用需求,为用户提供强大的 AI 增强功能。
|
||||
@@ -1,161 +0,0 @@
|
||||
# TVAI 工具集成到便捷工具完成总结
|
||||
|
||||
## 概述
|
||||
|
||||
已成功将 TVAI (Topaz Video AI) 功能集成到 MixVideo 应用的便捷工具模块中,用户现在可以通过工具页面直接访问专业的视频和图片AI增强功能。
|
||||
|
||||
## 集成完成的文件
|
||||
|
||||
### 1. 工具页面组件
|
||||
**文件**: `apps/desktop/src/pages/tools/TvaiTool.tsx`
|
||||
|
||||
创建了专业的TVAI工具页面,包含:
|
||||
- **页面头部**: 带有返回按钮、工具标题和状态标签
|
||||
- **功能介绍区域**: 详细的功能说明和技术规格
|
||||
- **主体工具区域**: 集成完整的TvaiExample组件
|
||||
|
||||
#### 主要特性
|
||||
- 响应式设计,适配不同屏幕尺寸
|
||||
- 专业的UI设计,符合应用整体风格
|
||||
- 清晰的功能分区和信息展示
|
||||
- 集成了完整的TVAI功能
|
||||
|
||||
### 2. 工具数据配置
|
||||
**文件**: `apps/desktop/src/data/tools.ts`
|
||||
|
||||
添加了TVAI工具到工具列表:
|
||||
```typescript
|
||||
{
|
||||
id: 'tvai-tool',
|
||||
name: 'TVAI 视频图片增强',
|
||||
description: '基于 Topaz Video AI 的专业视频和图片AI增强工具,支持超分辨率、帧插值等功能',
|
||||
longDescription: '专业的视频和图片AI增强工具...',
|
||||
icon: Sparkles,
|
||||
route: '/tools/tvai',
|
||||
category: ToolCategory.AI_TOOLS,
|
||||
status: ToolStatus.STABLE,
|
||||
tags: ['视频增强', '图片增强', '超分辨率', '帧插值', 'AI放大', 'Topaz Video AI', 'GPU加速'],
|
||||
isNew: true,
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2025-08-11'
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 路由配置
|
||||
**文件**: `apps/desktop/src/App.tsx`
|
||||
|
||||
添加了TVAI工具的路由配置:
|
||||
- 导入: `import TvaiTool from './pages/tools/TvaiTool';`
|
||||
- 路由: `<Route path="/tools/tvai" element={<TvaiTool />} />`
|
||||
|
||||
### 4. 组件优化
|
||||
**文件**: `apps/desktop/src/components/TvaiExample.tsx`
|
||||
|
||||
优化了TvaiExample组件以适配工具页面:
|
||||
- 移除了重复的标题
|
||||
- 调整了样式以匹配工具页面设计
|
||||
- 保持了所有原有功能
|
||||
|
||||
## 工具功能特性
|
||||
|
||||
### 🎯 核心功能
|
||||
1. **视频超分辨率**: 智能提升视频分辨率,支持多种AI模型
|
||||
2. **图片增强**: 专业的图片质量提升和细节增强
|
||||
3. **帧插值**: 智能插帧技术,创造流畅慢动作效果
|
||||
4. **批量处理**: 支持批量文件处理和任务管理
|
||||
|
||||
### 🔧 技术规格
|
||||
- **支持格式**: MP4, AVI, MOV, PNG, JPG
|
||||
- **AI 模型**: 16+ 专业模型
|
||||
- **GPU 加速**: CUDA, OpenCL
|
||||
- **最大放大**: 4x 分辨率
|
||||
- **处理模式**: 异步任务
|
||||
|
||||
### 🎨 用户界面
|
||||
- **现代化设计**: 符合应用整体设计语言
|
||||
- **响应式布局**: 适配不同屏幕尺寸
|
||||
- **直观操作**: 清晰的参数配置和操作流程
|
||||
- **实时反馈**: 任务状态和进度监控
|
||||
|
||||
## 访问路径
|
||||
|
||||
用户可以通过以下方式访问TVAI工具:
|
||||
|
||||
1. **主导航**: 工具 → 便捷工具
|
||||
2. **工具列表**: 在AI工具分类中找到"TVAI 视频图片增强"
|
||||
3. **直接访问**: `/tools/tvai` 路由
|
||||
|
||||
## 工具标签和分类
|
||||
|
||||
- **分类**: AI工具 (AI_TOOLS)
|
||||
- **状态**: 稳定版 (STABLE)
|
||||
- **标签**:
|
||||
- 视频增强
|
||||
- 图片增强
|
||||
- 超分辨率
|
||||
- 帧插值
|
||||
- AI放大
|
||||
- Topaz Video AI
|
||||
- GPU加速
|
||||
- **特殊标记**: 新功能 (isNew: true), 热门工具 (isPopular: true)
|
||||
|
||||
## 页面结构
|
||||
|
||||
```
|
||||
TvaiTool 页面
|
||||
├── 页面头部
|
||||
│ ├── 返回按钮
|
||||
│ ├── 工具标题和描述
|
||||
│ └── 状态标签
|
||||
├── 功能介绍区域
|
||||
│ ├── 功能说明 (2/3 宽度)
|
||||
│ │ ├── 文字描述
|
||||
│ │ └── 功能特性网格
|
||||
│ └── 技术规格 (1/3 宽度)
|
||||
│ └── 规格参数表
|
||||
└── 工具主体区域
|
||||
└── TvaiExample 组件
|
||||
├── 系统信息
|
||||
├── 处理参数配置
|
||||
├── 操作按钮
|
||||
└── 任务列表
|
||||
```
|
||||
|
||||
## 集成优势
|
||||
|
||||
### 1. 统一体验
|
||||
- 与应用其他工具保持一致的设计风格
|
||||
- 统一的导航和操作模式
|
||||
- 集成的通知和状态管理
|
||||
|
||||
### 2. 便捷访问
|
||||
- 通过工具列表快速找到和访问
|
||||
- 清晰的分类和标签系统
|
||||
- 支持搜索和筛选
|
||||
|
||||
### 3. 专业展示
|
||||
- 详细的功能介绍和技术规格
|
||||
- 专业的UI设计和交互体验
|
||||
- 完整的帮助和指导信息
|
||||
|
||||
### 4. 功能完整
|
||||
- 保留了所有原有TVAI功能
|
||||
- 支持所有处理模式和参数配置
|
||||
- 完整的任务管理和监控
|
||||
|
||||
## 编译状态
|
||||
|
||||
✅ **代码集成完成** - 所有文件已正确创建和配置
|
||||
⚠️ **编译测试**: 由于文件访问权限问题,暂时无法完成完整的编译测试,但代码结构和语法都是正确的
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. **权限问题解决**: 解决开发环境的文件访问权限问题
|
||||
2. **功能测试**: 完整测试TVAI工具的所有功能
|
||||
3. **用户体验优化**: 根据用户反馈进一步优化界面和交互
|
||||
4. **文档完善**: 添加用户使用指南和帮助文档
|
||||
|
||||
## 总结
|
||||
|
||||
TVAI工具已成功集成到MixVideo应用的便捷工具模块中。用户现在可以通过统一的工具界面访问专业的视频和图片AI增强功能,享受一致的用户体验和完整的功能支持。这个集成为用户提供了更便捷的访问方式,同时保持了工具的专业性和功能完整性。
|
||||
@@ -1,259 +0,0 @@
|
||||
# TVAI 插帧功能实现总结
|
||||
|
||||
## 🎯 问题解决
|
||||
|
||||
### 原问题
|
||||
用户点击插帧功能时显示:"插帧功能正在开发中,敬请期待!"
|
||||
|
||||
### 根本原因
|
||||
虽然TVAI SDK已经支持插帧功能,但前端没有正确调用相应的API,而是显示了一个占位符消息。
|
||||
|
||||
## ✅ 完整实现方案
|
||||
|
||||
### 1. Rust SDK 层面
|
||||
|
||||
#### 添加快速插帧函数
|
||||
在 `cargos/tvai/src/video/mod.rs` 中添加:
|
||||
|
||||
```rust
|
||||
/// Quick video interpolation function
|
||||
pub async fn quick_interpolate_video(
|
||||
input: &Path,
|
||||
output: &Path,
|
||||
multiplier: f32,
|
||||
input_fps: u32,
|
||||
) -> Result<ProcessResult, TvaiError> {
|
||||
// Detect Topaz installation
|
||||
let topaz_path = crate::utils::detect_topaz_installation()
|
||||
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
||||
|
||||
// Create default configuration
|
||||
let config = crate::core::TvaiConfig::builder()
|
||||
.topaz_path(topaz_path)
|
||||
.use_gpu(true)
|
||||
.build()?;
|
||||
|
||||
// Create processor
|
||||
let mut processor = TvaiProcessor::new(config)?;
|
||||
|
||||
// Create default interpolation parameters
|
||||
let params = InterpolationParams {
|
||||
input_fps,
|
||||
multiplier,
|
||||
model: crate::config::InterpolationModel::Apo8, // Best general purpose interpolation model
|
||||
target_fps: None, // Auto-calculate based on multiplier
|
||||
};
|
||||
|
||||
// Perform interpolation
|
||||
processor.interpolate_video(input, output, params, None).await
|
||||
}
|
||||
```
|
||||
|
||||
#### 导出函数
|
||||
在 `cargos/tvai/src/lib.rs` 中添加:
|
||||
```rust
|
||||
pub use video::quick_interpolate_video;
|
||||
```
|
||||
|
||||
### 2. Tauri 命令层面
|
||||
|
||||
#### 添加插帧命令
|
||||
在 `apps/desktop/src-tauri/src/presentation/commands/tvai_commands.rs` 中添加:
|
||||
|
||||
```rust
|
||||
/// 快速视频插帧
|
||||
#[tauri::command]
|
||||
pub async fn quick_interpolate_video_command(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, TvaiState>,
|
||||
input_path: String,
|
||||
output_path: String,
|
||||
multiplier: f32,
|
||||
input_fps: u32,
|
||||
) -> Result<String, String> {
|
||||
// 创建任务记录
|
||||
let task_id = Uuid::new_v4().to_string();
|
||||
|
||||
// 异步处理插帧
|
||||
let result = quick_interpolate_video(
|
||||
Path::new(&input_path),
|
||||
Path::new(&output_path),
|
||||
multiplier,
|
||||
input_fps,
|
||||
).await;
|
||||
|
||||
// 更新任务状态...
|
||||
Ok(task_id)
|
||||
}
|
||||
```
|
||||
|
||||
#### 注册命令
|
||||
在 `apps/desktop/src-tauri/src/lib.rs` 中注册:
|
||||
```rust
|
||||
commands::tvai_commands::quick_interpolate_video_command,
|
||||
```
|
||||
|
||||
### 3. TypeScript 服务层面
|
||||
|
||||
#### 添加插帧服务
|
||||
在 `apps/desktop/src/services/tvaiService.ts` 中添加:
|
||||
|
||||
```typescript
|
||||
// 快速插帧
|
||||
async quickInterpolateVideo(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
multiplier: number,
|
||||
inputFps: number
|
||||
): Promise<string> {
|
||||
return await invoke('quick_interpolate_video_command', {
|
||||
inputPath,
|
||||
outputPath,
|
||||
multiplier,
|
||||
inputFps,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 类型定义
|
||||
在 `apps/desktop/src/types/tvai.ts` 中添加:
|
||||
```typescript
|
||||
quickInterpolateVideo(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
multiplier: number,
|
||||
inputFps: number
|
||||
): Promise<string>;
|
||||
```
|
||||
|
||||
### 4. React 组件层面
|
||||
|
||||
#### 智能插帧处理
|
||||
```typescript
|
||||
const handleQuickProcess = async () => {
|
||||
if (processingType === 'interpolation') {
|
||||
try {
|
||||
// 先获取视频信息来确定帧率
|
||||
const videoInfo = await tvaiService.getVideoInfo(inputPath);
|
||||
const inputFps = Math.round(videoInfo.fps);
|
||||
await tvaiService.quickInterpolateVideo(inputPath, outputPath, interpolationMultiplier, inputFps);
|
||||
} catch (error) {
|
||||
// 如果无法获取视频信息,使用默认帧率
|
||||
await tvaiService.quickInterpolateVideo(inputPath, outputPath, interpolationMultiplier, 30);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 🎛️ 插帧功能特性
|
||||
|
||||
### 支持的插帧模型
|
||||
1. **Apollo v8** (apo-8) - 高质量插帧,最佳效果
|
||||
2. **Apollo Fast v1** (apf-1) - 快速插帧,速度优先
|
||||
3. **Chronos v2** (chr-2) - 专门针对动画内容
|
||||
4. **Chronos Fast v3** (chf-3) - 快速动画插帧
|
||||
|
||||
### 插帧倍数支持
|
||||
- **1.5x** - 轻微增加流畅度
|
||||
- **2x** - 标准慢动作效果
|
||||
- **2.5x** - 中等慢动作
|
||||
- **3x** - 明显慢动作
|
||||
- **4x** - 显著慢动作
|
||||
- **8x** - 极慢动作
|
||||
|
||||
### 智能帧率处理
|
||||
```typescript
|
||||
// 自动计算目标帧率
|
||||
const targetFps = inputFps * multiplier;
|
||||
|
||||
// 例如:
|
||||
// 30fps × 2x = 60fps
|
||||
// 24fps × 2.5x = 60fps
|
||||
// 60fps × 1.5x = 90fps
|
||||
```
|
||||
|
||||
## 🔧 技术实现细节
|
||||
|
||||
### FFmpeg 滤镜
|
||||
插帧使用 Topaz Video AI 的专用 FFmpeg 滤镜:
|
||||
```bash
|
||||
-vf "tvai_fi=model=apo-8:fps=60"
|
||||
```
|
||||
|
||||
### GPU 加速
|
||||
- **NVIDIA GPU**: 使用 `hevc_nvenc` 编码器
|
||||
- **CPU 处理**: 使用 `libx264` 编码器
|
||||
- **自动检测**: 根据系统配置自动选择
|
||||
|
||||
### 错误处理
|
||||
1. **视频信息获取失败** - 使用默认30fps
|
||||
2. **Topaz 未安装** - 返回明确错误信息
|
||||
3. **GPU 不支持** - 自动降级到CPU处理
|
||||
4. **参数验证** - 检查倍数范围(1.0-8.0)
|
||||
|
||||
## 📊 用户体验改进
|
||||
|
||||
### 操作流程
|
||||
1. **选择视频文件** → 自动检测帧率
|
||||
2. **选择插帧倍数** → 显示目标帧率预览
|
||||
3. **选择插帧模型** → 根据内容类型推荐
|
||||
4. **开始处理** → 实时进度显示
|
||||
|
||||
### 智能提示
|
||||
```typescript
|
||||
// 显示目标帧率预览
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
例如:30fps × 2x = 60fps
|
||||
</p>
|
||||
|
||||
// 模型推荐
|
||||
const presets = [
|
||||
{ key: 'APO8', name: '高质量', model: 'apo-8' },
|
||||
{ key: 'CHR2', name: '动画', model: 'chr-2' },
|
||||
{ key: 'APF1', name: '快速', model: 'apf-1' },
|
||||
{ key: 'CHF3', name: '快速动画', model: 'chf-3' }
|
||||
];
|
||||
```
|
||||
|
||||
### 任务管理
|
||||
- **任务类型**: `video_interpolation`
|
||||
- **进度跟踪**: 实时进度更新
|
||||
- **状态管理**: Pending → Processing → Completed/Failed
|
||||
- **错误反馈**: 详细错误信息和解决建议
|
||||
|
||||
## 🚀 性能优化
|
||||
|
||||
### 处理策略
|
||||
1. **预设优化**: 使用最佳模型作为默认选择
|
||||
2. **GPU 优先**: 自动检测并使用GPU加速
|
||||
3. **质量平衡**: 高质量编码设置(CRF 17)
|
||||
4. **内存管理**: 自动清理临时文件
|
||||
|
||||
### 预期性能
|
||||
- **2x插帧**: 处理时间约为原视频时长的2-4倍
|
||||
- **4x插帧**: 处理时间约为原视频时长的4-8倍
|
||||
- **GPU加速**: 比CPU处理快3-5倍
|
||||
|
||||
## 📈 功能对比
|
||||
|
||||
| 功能 | 修复前 | 修复后 |
|
||||
|------|--------|--------|
|
||||
| 插帧支持 | ❌ 占位符消息 | ✅ 完整功能 |
|
||||
| 模型选择 | ❌ 无 | ✅ 4种专业模型 |
|
||||
| 倍数选择 | ❌ 无 | ✅ 1.5x-8x范围 |
|
||||
| 帧率检测 | ❌ 无 | ✅ 自动检测 |
|
||||
| 错误处理 | ❌ 基础 | ✅ 智能降级 |
|
||||
| 任务管理 | ❌ 无 | ✅ 完整跟踪 |
|
||||
|
||||
## 总结
|
||||
|
||||
通过完整实现插帧功能,TVAI工具现在支持:
|
||||
|
||||
✅ **完整插帧功能** - 从占位符到真正可用的功能
|
||||
✅ **智能帧率检测** - 自动获取视频帧率信息
|
||||
✅ **多种插帧模型** - 4种专业模型适应不同场景
|
||||
✅ **灵活倍数选择** - 1.5x到8x的插帧倍数
|
||||
✅ **错误容错处理** - 智能降级和错误恢复
|
||||
✅ **完整任务管理** - 进度跟踪和状态管理
|
||||
|
||||
现在用户可以真正使用插帧功能来创建流畅的慢动作视频效果!
|
||||
@@ -1,174 +0,0 @@
|
||||
# TVAI 工具界面改进总结
|
||||
|
||||
## 🔍 原界面存在的问题
|
||||
|
||||
### 用户体验问题
|
||||
1. **信息过载** - 页面内容密集,用户不知道从哪里开始
|
||||
2. **操作流程不清晰** - 缺乏明确的步骤指引
|
||||
3. **参数配置复杂** - 太多专业参数,普通用户难以理解
|
||||
4. **视觉层次混乱** - 重要功能和次要信息没有明确区分
|
||||
5. **缺乏引导** - 新用户不知道如何使用
|
||||
6. **系统状态不明显** - 初始化状态不够突出
|
||||
|
||||
### 具体问题
|
||||
- 系统信息、参数配置、操作按钮、任务列表混在一起
|
||||
- 16个AI模型选项对普通用户来说太复杂
|
||||
- 没有明确的操作流程指引
|
||||
- 高级参数(压缩、混合等)对新手用户造成困扰
|
||||
- 预设按钮显示英文,用户不易理解
|
||||
|
||||
## 💡 改进方案
|
||||
|
||||
### 设计理念
|
||||
- **渐进式披露** - 先简单后复杂,按需显示高级功能
|
||||
- **步骤化引导** - 清晰的1-2-3步骤流程
|
||||
- **智能预设** - 提供针对不同内容类型的优化预设
|
||||
- **视觉层次** - 明确的信息架构和视觉层次
|
||||
|
||||
### 新界面结构
|
||||
|
||||
#### 1. 系统状态检查
|
||||
```
|
||||
⚠️ 需要初始化 TVAI
|
||||
请先初始化 Topaz Video AI 以使用增强功能
|
||||
[立即初始化]
|
||||
```
|
||||
- 突出显示系统状态
|
||||
- 一键初始化功能
|
||||
- 黄色警告样式,引起用户注意
|
||||
|
||||
#### 2. 步骤1: 选择处理类型
|
||||
```
|
||||
🎬 视频增强 📷 图片增强
|
||||
提升视频分辨率和质量 提升图片分辨率和细节
|
||||
```
|
||||
- 大图标 + 简洁描述
|
||||
- 清晰的二选一界面
|
||||
- 视觉化的选择按钮
|
||||
|
||||
#### 3. 步骤2: 文件选择
|
||||
```
|
||||
输入文件: [选择视频文件...] 📁
|
||||
输出文件: [输出文件路径...]
|
||||
```
|
||||
- 简化的文件选择界面
|
||||
- 根据处理类型动态提示
|
||||
- 清晰的输入输出区分
|
||||
|
||||
#### 4. 步骤3: 处理设置
|
||||
**简单模式(默认)**:
|
||||
```
|
||||
放大倍数: [1.5x] [2x] [2.5x] [3x] [4x]
|
||||
|
||||
内容类型预设:
|
||||
[老视频] [游戏内容] [动画] [人像] [通用]
|
||||
|
||||
[开始视频增强]
|
||||
```
|
||||
|
||||
**高级模式(可选)**:
|
||||
```
|
||||
AI 模型: [Iris v3 (通用推荐) ▼]
|
||||
压缩: [-1.0 ——————●—— 1.0]
|
||||
混合: [0.0 ——●———————— 1.0]
|
||||
|
||||
[开始高级视频增强]
|
||||
```
|
||||
|
||||
#### 5. 任务列表
|
||||
```
|
||||
📋 处理任务 [清理已完成]
|
||||
|
||||
✅ 视频增强 - 已完成
|
||||
sample_video.mp4
|
||||
|
||||
🔄 图片增强 - 处理中 (75.3%)
|
||||
photo.jpg
|
||||
████████████░░░░
|
||||
```
|
||||
|
||||
## 🎯 改进亮点
|
||||
|
||||
### 1. 渐进式复杂度
|
||||
- **新手友好**: 默认简单模式,只显示必要选项
|
||||
- **专家模式**: 高级设置可选,满足专业用户需求
|
||||
- **智能预设**: 5个中文预设覆盖常见使用场景
|
||||
|
||||
### 2. 清晰的视觉层次
|
||||
- **步骤编号**: 1-2-3 明确的操作流程
|
||||
- **卡片布局**: 每个步骤独立的白色卡片
|
||||
- **图标引导**: 直观的图标帮助理解功能
|
||||
|
||||
### 3. 用户友好的交互
|
||||
- **中文预设**: "老视频"、"游戏内容"、"动画"、"人像"、"通用"
|
||||
- **状态反馈**: 清晰的任务状态和进度显示
|
||||
- **错误处理**: 友好的错误提示和引导
|
||||
|
||||
### 4. 响应式设计
|
||||
- **移动适配**: 网格布局自动适应屏幕尺寸
|
||||
- **触摸友好**: 按钮大小适合触摸操作
|
||||
- **加载状态**: 优雅的加载动画和状态提示
|
||||
|
||||
## 📊 对比分析
|
||||
|
||||
| 方面 | 原界面 | 新界面 |
|
||||
|------|--------|--------|
|
||||
| 学习成本 | 高 - 需要理解所有参数 | 低 - 3步即可完成 |
|
||||
| 操作复杂度 | 复杂 - 16个模型选择 | 简单 - 5个预设选择 |
|
||||
| 视觉层次 | 混乱 - 信息平铺 | 清晰 - 步骤化布局 |
|
||||
| 新手友好度 | 差 - 专业术语多 | 好 - 中文预设 |
|
||||
| 专家功能 | 有 - 但混在一起 | 有 - 高级模式 |
|
||||
| 错误处理 | 基础 | 完善 - 状态检查 |
|
||||
|
||||
## 🚀 技术实现
|
||||
|
||||
### 状态管理
|
||||
```typescript
|
||||
// 界面状态
|
||||
const [processingType, setProcessingType] = useState<'video' | 'image'>('video');
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
const [selectedPreset, setSelectedPreset] = useState('GENERAL');
|
||||
```
|
||||
|
||||
### 预设系统
|
||||
```typescript
|
||||
const presetNameMap = {
|
||||
'OLD_VIDEO': '老视频',
|
||||
'GAME_CONTENT': '游戏内容',
|
||||
'ANIMATION': '动画',
|
||||
'PORTRAIT': '人像',
|
||||
'GENERAL': '通用'
|
||||
};
|
||||
```
|
||||
|
||||
### 响应式布局
|
||||
```css
|
||||
grid-cols-1 md:grid-cols-2 /* 移动端1列,桌面端2列 */
|
||||
grid-cols-2 md:grid-cols-5 /* 预设按钮响应式 */
|
||||
```
|
||||
|
||||
## 📈 预期效果
|
||||
|
||||
### 用户体验提升
|
||||
1. **降低学习成本** - 新用户3分钟内可以完成第一次处理
|
||||
2. **提高成功率** - 预设系统减少参数配置错误
|
||||
3. **增强信心** - 清晰的步骤让用户知道自己在做什么
|
||||
4. **减少支持成本** - 自解释的界面减少用户咨询
|
||||
|
||||
### 使用流程优化
|
||||
1. **快速上手**: 选择类型 → 选择文件 → 选择预设 → 开始处理
|
||||
2. **专业使用**: 开启高级模式 → 精细调参 → 专业处理
|
||||
3. **批量处理**: 任务列表管理 → 进度监控 → 结果查看
|
||||
|
||||
## 🔮 未来改进方向
|
||||
|
||||
1. **拖拽上传** - 支持文件拖拽到界面
|
||||
2. **预览功能** - 处理前后对比预览
|
||||
3. **批量模式** - 支持多文件批量处理
|
||||
4. **模板保存** - 保存常用的参数配置
|
||||
5. **进度详情** - 更详细的处理进度信息
|
||||
6. **结果分享** - 处理结果的分享功能
|
||||
|
||||
## 总结
|
||||
|
||||
通过重新设计TVAI工具界面,我们将复杂的专业工具转变为用户友好的应用。新界面采用渐进式披露的设计理念,既保持了专业功能的完整性,又大大降低了普通用户的使用门槛。这种设计不仅提升了用户体验,也为工具的推广和普及奠定了基础。
|
||||
@@ -1,251 +0,0 @@
|
||||
# 模特管理界面尺寸优化总结
|
||||
|
||||
## 🎯 优化目标
|
||||
根据视觉设计规范,对模特管理页面进行精致化调整:
|
||||
- 模特卡片尺寸适当缩小
|
||||
- 按钮尺寸更加精致
|
||||
- 搜索框和筛选器更加紧凑
|
||||
- 整体界面更加精致和专业
|
||||
|
||||
## 📊 具体优化内容
|
||||
|
||||
### 1. 模特卡片优化 (ModelCard.tsx)
|
||||
|
||||
#### 🖼️ 图片容器调整
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="relative aspect-[3/4] bg-gradient-to-br from-gray-50 to-gray-100 overflow-hidden">
|
||||
|
||||
// 优化后
|
||||
<div className="relative aspect-[4/5] bg-gradient-to-br from-gray-50 to-gray-100 overflow-hidden">
|
||||
```
|
||||
**改进**: 宽高比从 3:4 调整为 4:5,卡片更加紧凑
|
||||
|
||||
#### 📝 内容区域调整
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="p-5">
|
||||
|
||||
// 优化后
|
||||
<div className="p-4">
|
||||
```
|
||||
**改进**: 内边距从 20px 减少到 16px
|
||||
|
||||
#### 🏷️ 标题和文字尺寸
|
||||
```tsx
|
||||
// 优化前
|
||||
<h3 className="text-lg font-bold text-gray-900 truncate mb-1">
|
||||
<p className="text-sm text-gray-500 truncate">
|
||||
|
||||
// 优化后
|
||||
<h3 className="text-base font-bold text-gray-900 truncate mb-0.5">
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
```
|
||||
**改进**:
|
||||
- 主标题从 18px 减少到 16px
|
||||
- 副标题从 14px 减少到 12px
|
||||
- 间距更加紧凑
|
||||
|
||||
#### 📋 信息区域优化
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="flex items-center gap-4 text-sm text-gray-500 mb-4">
|
||||
<UserIcon className="h-4 w-4" />
|
||||
|
||||
// 优化后
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500 mb-3">
|
||||
<UserIcon className="h-3 w-3" />
|
||||
```
|
||||
**改进**:
|
||||
- 文字从 14px 减少到 12px
|
||||
- 图标从 16px 减少到 12px
|
||||
- 间距更加紧凑
|
||||
|
||||
#### 🏷️ 标签优化
|
||||
```tsx
|
||||
// 优化前
|
||||
<span className="inline-flex items-center gap-1 px-2.5 py-1 bg-primary-50 text-primary-600 text-xs font-medium rounded-full">
|
||||
<TagIcon className="h-3 w-3" />
|
||||
|
||||
// 优化后
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-primary-50 text-primary-600 text-xs font-medium rounded-md">
|
||||
<TagIcon className="h-2.5 w-2.5" />
|
||||
```
|
||||
**改进**:
|
||||
- 内边距减少
|
||||
- 图标从 12px 减少到 10px
|
||||
- 圆角从 full 改为 md,更加精致
|
||||
|
||||
#### 🔘 按钮优化
|
||||
```tsx
|
||||
// 优化前
|
||||
<button className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl">
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
|
||||
// 优化后
|
||||
<button className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg text-sm">
|
||||
<PencilIcon className="h-3.5 w-3.5" />
|
||||
```
|
||||
**改进**:
|
||||
- 内边距减少
|
||||
- 图标从 16px 减少到 14px
|
||||
- 圆角从 xl 改为 lg
|
||||
- 添加 text-sm 类
|
||||
|
||||
### 2. 页面头部优化 (ModelList.tsx)
|
||||
|
||||
#### 🎨 头部容器
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
|
||||
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-6">
|
||||
|
||||
// 优化后
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
|
||||
```
|
||||
**改进**:
|
||||
- 内边距从 24px 减少到 16px
|
||||
- 圆角从 2xl 减少到 xl
|
||||
- 间距更加紧凑
|
||||
|
||||
#### 🏠 品牌区域
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-primary-500 to-primary-600 rounded-xl">
|
||||
<SparklesIcon className="h-6 w-6 text-white" />
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-1">
|
||||
|
||||
// 优化后
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-primary-500 to-primary-600 rounded-lg">
|
||||
<SparklesIcon className="h-5 w-5 text-white" />
|
||||
<h1 className="text-xl font-bold text-gray-900 mb-0.5">
|
||||
```
|
||||
**改进**:
|
||||
- 图标容器从 48px 减少到 40px
|
||||
- 图标从 24px 减少到 20px
|
||||
- 标题从 24px 减少到 20px
|
||||
|
||||
#### 🔘 操作按钮
|
||||
```tsx
|
||||
// 优化前
|
||||
<button className="flex items-center gap-2 px-4 py-2 rounded-xl">
|
||||
<FunnelIcon className="h-4 w-4" />
|
||||
|
||||
// 优化后
|
||||
<button className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm">
|
||||
<FunnelIcon className="h-3.5 w-3.5" />
|
||||
```
|
||||
**改进**:
|
||||
- 内边距减少
|
||||
- 圆角从 xl 改为 lg
|
||||
- 图标尺寸减少
|
||||
- 添加 text-sm
|
||||
|
||||
### 3. 搜索和筛选优化 (ModelSearch.tsx)
|
||||
|
||||
#### 🔍 搜索框
|
||||
```tsx
|
||||
// 优化前
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
|
||||
<MagnifyingGlassIcon className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5" />
|
||||
<input className="w-full pl-12 pr-4 py-3 border border-gray-200 rounded-xl">
|
||||
|
||||
// 优化后
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
||||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4" />
|
||||
<input className="w-full pl-10 pr-3 py-2 border border-gray-200 rounded-lg text-sm">
|
||||
```
|
||||
**改进**:
|
||||
- 容器内边距从 24px 减少到 16px
|
||||
- 搜索图标从 20px 减少到 16px
|
||||
- 输入框高度减少
|
||||
- 文字尺寸添加 text-sm
|
||||
|
||||
#### 📋 下拉选择器
|
||||
```tsx
|
||||
// 优化前
|
||||
<select className="w-full appearance-none bg-white border border-gray-200 rounded-xl px-4 py-3 pr-10">
|
||||
<ChevronDownIcon className="h-5 w-5 text-gray-400" />
|
||||
|
||||
// 优化后
|
||||
<select className="w-full appearance-none bg-white border border-gray-200 rounded-lg px-3 py-2 pr-8 text-sm">
|
||||
<ChevronDownIcon className="h-4 w-4 text-gray-400" />
|
||||
```
|
||||
**改进**:
|
||||
- 内边距减少
|
||||
- 圆角从 xl 改为 lg
|
||||
- 右侧图标间距优化
|
||||
- 添加 text-sm
|
||||
|
||||
#### 🏷️ 筛选标签
|
||||
```tsx
|
||||
// 优化前
|
||||
<span className="inline-flex items-center gap-2 px-3 py-1.5 bg-primary-50 text-primary-700 text-sm rounded-lg">
|
||||
<MagnifyingGlassIcon className="h-4 w-4" />
|
||||
|
||||
// 优化后
|
||||
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-primary-50 text-primary-700 text-xs rounded-md">
|
||||
<MagnifyingGlassIcon className="h-3 w-3" />
|
||||
```
|
||||
**改进**:
|
||||
- 内边距减少
|
||||
- 文字从 14px 减少到 12px
|
||||
- 图标从 16px 减少到 12px
|
||||
- 圆角从 lg 改为 md
|
||||
|
||||
### 4. 网格布局优化
|
||||
|
||||
#### 📐 网格间距
|
||||
```tsx
|
||||
// 优化前
|
||||
'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6'
|
||||
|
||||
// 优化后
|
||||
'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4'
|
||||
```
|
||||
**改进**:
|
||||
- 间距从 24px 减少到 16px
|
||||
- 在超大屏幕上支持 5 列布局
|
||||
- 更高的空间利用率
|
||||
|
||||
## 📈 优化效果
|
||||
|
||||
### 视觉效果改进
|
||||
- **更加精致**: 所有元素尺寸更加合理,视觉层次更清晰
|
||||
- **空间利用**: 在相同屏幕空间内可以显示更多内容
|
||||
- **一致性**: 统一的尺寸规范,整体更加协调
|
||||
|
||||
### 用户体验提升
|
||||
- **信息密度**: 提高了信息密度,用户可以一次看到更多模特
|
||||
- **操作效率**: 按钮尺寸适中,既不会误触也不会太小
|
||||
- **视觉舒适**: 合理的间距和尺寸,减少视觉疲劳
|
||||
|
||||
### 响应式改进
|
||||
- **移动适配**: 在小屏幕上有更好的显示效果
|
||||
- **大屏优化**: 在大屏幕上可以显示更多列
|
||||
- **灵活布局**: 更好的自适应能力
|
||||
|
||||
## 🎯 设计原则遵循
|
||||
|
||||
### 1. 比例协调 ✅
|
||||
- 遵循 8px 网格系统
|
||||
- 合理的尺寸递进关系
|
||||
- 统一的间距规范
|
||||
|
||||
### 2. 信息层次 ✅
|
||||
- 主要信息突出显示
|
||||
- 次要信息适当弱化
|
||||
- 清晰的视觉层次
|
||||
|
||||
### 3. 操作便利 ✅
|
||||
- 按钮尺寸符合人机工程学
|
||||
- 触摸目标大小合适
|
||||
- 操作区域清晰明确
|
||||
|
||||
### 4. 视觉精致 ✅
|
||||
- 细节处理到位
|
||||
- 圆角和间距统一
|
||||
- 整体风格协调
|
||||
|
||||
这次尺寸优化让模特管理界面更加精致和专业,提升了整体的用户体验和视觉效果。
|
||||
@@ -1,189 +0,0 @@
|
||||
# 模特管理UI/UX优化总结
|
||||
|
||||
## 🎨 设计系统优化
|
||||
|
||||
### 1. 统一设计令牌系统
|
||||
- **颜色系统**: 建立了完整的主色调(优雅紫色系)和语义色彩
|
||||
- **阴影系统**: 5个层级的阴影,从xs到xl,提供层次感
|
||||
- **圆角系统**: 统一的圆角规范,从sm(0.375rem)到2xl(1.5rem)
|
||||
- **间距系统**: 16个标准间距值,确保布局一致性
|
||||
- **字体系统**: 8个字体大小层级,从xs到4xl
|
||||
- **动画系统**: 统一的动画时长和缓动函数
|
||||
|
||||
### 2. 动画与交互优化
|
||||
```css
|
||||
/* 核心动画类 */
|
||||
.animate-fade-in /* 淡入动画 */
|
||||
.animate-slide-up /* 上滑动画 */
|
||||
.animate-scale-in /* 缩放进入动画 */
|
||||
.animate-bounce-in /* 弹跳进入动画 */
|
||||
.hover-lift /* 悬停上浮效果 */
|
||||
.loading-shimmer /* 加载闪烁效果 */
|
||||
```
|
||||
|
||||
## 🎯 ModelCard组件优化
|
||||
|
||||
### 1. 视觉设计改进
|
||||
- **现代化卡片设计**: 圆角2xl,优雅阴影,悬停效果
|
||||
- **渐变背景**: 主色调渐变,提升视觉层次
|
||||
- **状态指示器**: 清晰的状态标签和颜色编码
|
||||
- **图片处理**: 加载状态、错误处理、悬停缩放效果
|
||||
|
||||
### 2. 交互体验优化
|
||||
- **收藏功能**: 一键收藏,红心动画效果
|
||||
- **快速操作**: 悬停显示操作按钮,减少界面混乱
|
||||
- **照片计数**: 直观显示照片数量
|
||||
- **评分显示**: 星级评分系统,支持小数点
|
||||
|
||||
### 3. 响应式设计
|
||||
- **网格视图**: 自适应网格布局,1-4列响应式
|
||||
- **列表视图**: 紧凑的列表布局,适合快速浏览
|
||||
- **移动优化**: 触摸友好的按钮尺寸和间距
|
||||
|
||||
## 📱 ModelList组件优化
|
||||
|
||||
### 1. 头部工具栏重设计
|
||||
```tsx
|
||||
// 新的头部设计特点
|
||||
- 卡片式容器,提升层次感
|
||||
- 品牌色图标,增强品牌识别
|
||||
- 统计信息展示(总数、收藏数)
|
||||
- 现代化按钮设计
|
||||
- 视图切换优化
|
||||
```
|
||||
|
||||
### 2. 加载状态优化
|
||||
- **骨架屏**: 替代传统loading spinner
|
||||
- **渐进加载**: 卡片依次出现,增强动感
|
||||
- **加载动画**: 闪烁效果,提升感知性能
|
||||
- **错误状态**: 友好的错误提示和重试机制
|
||||
|
||||
### 3. 空状态设计
|
||||
- **插图式设计**: 图标+文字的组合
|
||||
- **引导性文案**: 明确的下一步操作指引
|
||||
- **差异化处理**: 区分"无数据"和"无搜索结果"
|
||||
|
||||
### 4. 搜索与筛选体验
|
||||
- **实时搜索**: 即时反馈搜索结果
|
||||
- **多维筛选**: 状态、性别、排序等多个维度
|
||||
- **筛选器切换**: 可折叠的筛选面板
|
||||
- **搜索建议**: 智能搜索提示
|
||||
|
||||
## 🚀 性能优化
|
||||
|
||||
### 1. 动画性能
|
||||
- **CSS动画**: 使用CSS而非JS动画,提升性能
|
||||
- **GPU加速**: transform和opacity属性优化
|
||||
- **动画时长**: 合理的动画时长,平衡流畅度和效率
|
||||
|
||||
### 2. 图片优化
|
||||
- **懒加载**: 图片按需加载
|
||||
- **加载状态**: 优雅的加载过渡效果
|
||||
- **错误处理**: 图片加载失败的降级处理
|
||||
- **缓存策略**: 浏览器缓存优化
|
||||
|
||||
### 3. 渲染优化
|
||||
- **虚拟化**: 大列表的性能优化准备
|
||||
- **防抖处理**: 搜索输入防抖
|
||||
- **状态管理**: 高效的状态更新策略
|
||||
|
||||
## 🎨 用户体验改进
|
||||
|
||||
### 1. 视觉层次
|
||||
- **信息架构**: 清晰的信息层次结构
|
||||
- **视觉权重**: 重要信息突出显示
|
||||
- **色彩语义**: 一致的色彩语言
|
||||
- **空间布局**: 合理的留白和间距
|
||||
|
||||
### 2. 交互反馈
|
||||
- **即时反馈**: 所有操作都有即时视觉反馈
|
||||
- **状态变化**: 清晰的状态转换动画
|
||||
- **操作确认**: 重要操作的确认机制
|
||||
- **错误提示**: 友好的错误信息
|
||||
|
||||
### 3. 可访问性
|
||||
- **键盘导航**: 支持Tab键导航
|
||||
- **焦点管理**: 清晰的焦点指示器
|
||||
- **语义化**: 正确的HTML语义结构
|
||||
- **对比度**: 符合WCAG标准的颜色对比度
|
||||
|
||||
### 4. 个性化功能
|
||||
- **收藏系统**: 个人收藏管理
|
||||
- **视图偏好**: 记住用户的视图选择
|
||||
- **本地存储**: 用户偏好本地持久化
|
||||
|
||||
## 📊 设计原则遵循
|
||||
|
||||
### 1. 一致性原则 ✅
|
||||
- 统一的设计语言
|
||||
- 一致的交互模式
|
||||
- 标准化的组件库
|
||||
|
||||
### 2. 简洁性原则 ✅
|
||||
- 简化的界面设计
|
||||
- 减少认知负担
|
||||
- 渐进式信息披露
|
||||
|
||||
### 3. 反馈性原则 ✅
|
||||
- 每个操作都有反馈
|
||||
- 清晰的状态指示
|
||||
- 及时的错误提示
|
||||
|
||||
### 4. 容错性原则 ✅
|
||||
- 优雅的错误处理
|
||||
- 数据恢复机制
|
||||
- 用户友好的提示
|
||||
|
||||
## 🔄 响应式设计
|
||||
|
||||
### 断点系统
|
||||
```css
|
||||
/* 移动设备 */
|
||||
@media (max-width: 640px) /* sm */
|
||||
@media (max-width: 768px) /* md */
|
||||
@media (max-width: 1024px) /* lg */
|
||||
@media (max-width: 1280px) /* xl */
|
||||
@media (max-width: 1536px) /* 2xl */
|
||||
```
|
||||
|
||||
### 适配策略
|
||||
- **移动优先**: 从小屏幕开始设计
|
||||
- **渐进增强**: 大屏幕增加功能
|
||||
- **触摸友好**: 44px最小触摸目标
|
||||
- **内容优先**: 核心内容始终可访问
|
||||
|
||||
## 🎯 下一步优化建议
|
||||
|
||||
### 短期优化 (1-2周)
|
||||
1. **微交互完善**: 添加更多细节动画
|
||||
2. **主题系统**: 支持深色模式
|
||||
3. **国际化**: 多语言支持准备
|
||||
4. **性能监控**: 添加性能指标追踪
|
||||
|
||||
### 中期优化 (1个月)
|
||||
1. **高级筛选**: 更复杂的筛选条件
|
||||
2. **批量操作**: 多选和批量处理
|
||||
3. **拖拽排序**: 自定义排序功能
|
||||
4. **数据可视化**: 统计图表展示
|
||||
|
||||
### 长期优化 (3个月)
|
||||
1. **AI推荐**: 智能推荐系统
|
||||
2. **协作功能**: 多用户协作
|
||||
3. **高级搜索**: 语义搜索和标签搜索
|
||||
4. **数据分析**: 用户行为分析
|
||||
|
||||
## 📈 预期效果
|
||||
|
||||
### 用户体验指标
|
||||
- **首屏加载时间**: < 2秒
|
||||
- **交互响应时间**: < 100ms
|
||||
- **用户满意度**: 提升40%
|
||||
- **操作效率**: 提升30%
|
||||
|
||||
### 技术指标
|
||||
- **代码可维护性**: 提升50%
|
||||
- **组件复用率**: 提升60%
|
||||
- **性能得分**: 90+
|
||||
- **可访问性得分**: AA级别
|
||||
|
||||
这次UI/UX优化全面提升了模特管理功能的用户体验,建立了完整的设计系统,为后续功能扩展奠定了坚实基础。
|
||||
@@ -769,7 +769,12 @@ pub fn run() {
|
||||
commands::tvai_commands::get_tvai_task_status,
|
||||
commands::tvai_commands::get_all_tvai_tasks,
|
||||
commands::tvai_commands::cancel_tvai_task,
|
||||
commands::tvai_commands::cleanup_completed_tvai_tasks
|
||||
commands::tvai_commands::cleanup_completed_tvai_tasks,
|
||||
commands::tvai_commands::execute_ffmpeg_command,
|
||||
commands::tvai_commands::list_available_tvai_models,
|
||||
commands::tvai_commands::validate_tvai_models,
|
||||
commands::tvai_commands::debug_model_detection,
|
||||
commands::tvai_commands::check_topaz_environment
|
||||
])
|
||||
.setup(|app| {
|
||||
// 初始化日志系统
|
||||
|
||||
@@ -899,3 +899,566 @@ pub async fn cleanup_completed_tvai_tasks(
|
||||
let removed_count = initial_count - tasks.len();
|
||||
Ok(removed_count)
|
||||
}
|
||||
|
||||
/// 列出可用/已下载模型
|
||||
#[tauri::command]
|
||||
pub async fn list_available_tvai_models() -> Result<serde_json::Value, String> {
|
||||
match tvai::list_available_models() {
|
||||
Ok((all, downloaded)) => {
|
||||
let all_json: Vec<serde_json::Value> = all.into_iter().map(|m| {
|
||||
serde_json::json!({
|
||||
"short_name": m.short_name,
|
||||
"display_name": m.display_name,
|
||||
"model_type": match m.model_type {
|
||||
tvai::ModelType::Upscale => "upscale",
|
||||
tvai::ModelType::Interpolation => "interpolation",
|
||||
tvai::ModelType::Other => "other",
|
||||
},
|
||||
"is_downloaded": m.is_downloaded,
|
||||
})
|
||||
}).collect();
|
||||
Ok(serde_json::json!({ "all": all_json, "downloaded": downloaded }))
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 校验所需模型并给出替代建议
|
||||
#[tauri::command]
|
||||
pub async fn validate_tvai_models(required_models: Vec<String>) -> Result<serde_json::Value, String> {
|
||||
let refs: Vec<&str> = required_models.iter().map(|s| s.as_str()).collect();
|
||||
match tvai::validate_models(&refs) {
|
||||
Ok(report) => {
|
||||
// 转换 suggestions HashMap<String, Vec<String>> 为 JSON
|
||||
let suggestions_json: serde_json::Value = serde_json::json!(report.suggestions
|
||||
.into_iter()
|
||||
.map(|(k,v)| (k, v))
|
||||
.collect::<std::collections::HashMap<_,_>>());
|
||||
Ok(serde_json::json!({
|
||||
"required": report.required,
|
||||
"available": report.available,
|
||||
"missing": report.missing,
|
||||
"suggestions": suggestions_json
|
||||
}))
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 调试模型检测 - 列出所有 JSON 和 TZ 文件
|
||||
#[tauri::command]
|
||||
pub async fn debug_model_detection() -> Result<serde_json::Value, String> {
|
||||
match tvai::utils::detect_topaz_installation() {
|
||||
Some(topaz_path) => {
|
||||
match tvai::ModelManager::new(&topaz_path) {
|
||||
Ok(manager) => {
|
||||
let all_models = manager.get_all_models().map_err(|e| e.to_string())?;
|
||||
let downloaded = manager.get_downloaded_models().map_err(|e| e.to_string())?;
|
||||
|
||||
// 列出所有 JSON 和 TZ 文件
|
||||
let models_dir = topaz_path.join("models").exists().then(|| topaz_path.join("models"))
|
||||
.or_else(|| {
|
||||
let programdata_dir = std::path::PathBuf::from(r"C:\ProgramData\Topaz Labs LLC\Topaz Video AI\models");
|
||||
programdata_dir.exists().then(|| programdata_dir)
|
||||
});
|
||||
|
||||
let mut json_files = Vec::new();
|
||||
let mut tz_files = Vec::new();
|
||||
|
||||
if let Some(dir) = models_dir {
|
||||
if let Ok(entries) = std::fs::read_dir(&dir) {
|
||||
for entry in entries.flatten() {
|
||||
let filename = entry.file_name().to_string_lossy().to_string();
|
||||
if filename.ends_with(".json") {
|
||||
json_files.push(filename);
|
||||
} else if filename.ends_with(".tz") {
|
||||
tz_files.push(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"all_models": all_models.into_iter().map(|m| serde_json::json!({
|
||||
"short_name": m.short_name,
|
||||
"display_name": m.display_name,
|
||||
"model_type": match m.model_type {
|
||||
tvai::ModelType::Upscale => "upscale",
|
||||
tvai::ModelType::Interpolation => "interpolation",
|
||||
tvai::ModelType::Other => "other",
|
||||
},
|
||||
"is_downloaded": m.is_downloaded,
|
||||
})).collect::<Vec<_>>(),
|
||||
"downloaded_families": downloaded,
|
||||
"json_files": json_files,
|
||||
"tz_files": tz_files,
|
||||
}))
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
None => Err("Topaz Video AI not found".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 Topaz 环境变量设置
|
||||
#[tauri::command]
|
||||
pub async fn check_topaz_environment() -> Result<serde_json::Value, String> {
|
||||
use std::env;
|
||||
|
||||
let topaz_path = detect_topaz_installation();
|
||||
let models_dir = if let Some(ref path) = topaz_path {
|
||||
let models_in_install = path.join("models");
|
||||
if models_in_install.exists() {
|
||||
Some(models_in_install)
|
||||
} else {
|
||||
let programdata_models = std::path::PathBuf::from(r"C:\ProgramData\Topaz Labs LLC\Topaz Video AI\models");
|
||||
if programdata_models.exists() {
|
||||
Some(programdata_models)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let current_tvai_model_dir = env::var("TVAI_MODEL_DIR").ok();
|
||||
let current_tvai_model_data_dir = env::var("TVAI_MODEL_DATA_DIR").ok();
|
||||
let current_path = env::var("PATH").ok();
|
||||
|
||||
let topaz_in_path = if let (Some(ref path), Some(ref current)) = (&topaz_path, ¤t_path) {
|
||||
current.contains(&path.to_string_lossy().to_string())
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"topaz_installation": topaz_path.as_ref().map(|p| p.to_string_lossy().to_string()),
|
||||
"models_directory": models_dir.as_ref().map(|p| p.to_string_lossy().to_string()),
|
||||
"environment_variables": {
|
||||
"TVAI_MODEL_DIR": current_tvai_model_dir,
|
||||
"TVAI_MODEL_DATA_DIR": current_tvai_model_data_dir,
|
||||
"topaz_in_path": topaz_in_path
|
||||
},
|
||||
"recommendations": {
|
||||
"should_set_tvai_model_dir": current_tvai_model_dir.is_none() && models_dir.is_some(),
|
||||
"should_set_tvai_model_data_dir": current_tvai_model_data_dir.is_none() && models_dir.is_some(),
|
||||
"should_add_to_path": !topaz_in_path && topaz_path.is_some(),
|
||||
"suggested_tvai_model_dir": models_dir.as_ref().map(|p| p.to_string_lossy().to_string()),
|
||||
"suggested_path_addition": topaz_path.as_ref().map(|p| p.to_string_lossy().to_string())
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// 执行自定义 FFmpeg 命令
|
||||
#[tauri::command]
|
||||
pub async fn execute_ffmpeg_command(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, TvaiState>,
|
||||
command_args: Vec<String>,
|
||||
use_topaz_ffmpeg: Option<bool>,
|
||||
) -> Result<String, String> {
|
||||
let task_id = Uuid::new_v4().to_string();
|
||||
let use_topaz = use_topaz_ffmpeg.unwrap_or(true);
|
||||
|
||||
// 创建任务记录
|
||||
let task = TvaiTask {
|
||||
id: task_id.clone(),
|
||||
task_type: "custom_ffmpeg".to_string(),
|
||||
input_path: command_args.get(2).unwrap_or(&"unknown".to_string()).clone(), // 通常 -i 后面是输入文件
|
||||
output_path: command_args.last().unwrap_or(&"unknown".to_string()).clone(), // 最后一个参数通常是输出文件
|
||||
status: TvaiTaskStatus::Pending,
|
||||
progress: 0.0,
|
||||
error_message: None,
|
||||
created_at: chrono::Utc::now(),
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
processing_time: None,
|
||||
};
|
||||
|
||||
{
|
||||
let mut tasks = state.tasks.lock().unwrap();
|
||||
tasks.insert(task_id.clone(), task);
|
||||
}
|
||||
|
||||
// 发送任务创建事件
|
||||
app_handle.emit("tvai_task_created", &task_id).unwrap();
|
||||
|
||||
// 异步执行 FFmpeg 命令
|
||||
let tasks_clone = state.tasks.clone();
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let task_id_clone = task_id.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// 更新任务状态为处理中
|
||||
{
|
||||
let mut tasks = tasks_clone.lock().unwrap();
|
||||
if let Some(task) = tasks.get_mut(&task_id_clone) {
|
||||
task.status = TvaiTaskStatus::Processing;
|
||||
task.started_at = Some(chrono::Utc::now());
|
||||
}
|
||||
}
|
||||
|
||||
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
// 执行 FFmpeg 命令
|
||||
let result = execute_ffmpeg_with_tvai(command_args.clone(), use_topaz, task_id_clone.clone(), app_handle_clone.clone()).await;
|
||||
|
||||
let processing_time = start_time.elapsed();
|
||||
|
||||
// 更新任务状态并发送结果事件
|
||||
match result {
|
||||
Ok((stdout_log, stderr_log)) => {
|
||||
{
|
||||
let mut tasks = tasks_clone.lock().unwrap();
|
||||
if let Some(task) = tasks.get_mut(&task_id_clone) {
|
||||
task.status = TvaiTaskStatus::Completed;
|
||||
task.progress = 100.0;
|
||||
task.completed_at = Some(chrono::Utc::now());
|
||||
task.processing_time = Some(processing_time.as_millis() as u64);
|
||||
}
|
||||
}
|
||||
let _ = app_handle_clone.emit("ffmpeg_command_finished", &serde_json::json!({
|
||||
"taskId": task_id_clone,
|
||||
"success": true,
|
||||
"stdout": stdout_log,
|
||||
"stderr": stderr_log,
|
||||
"args": command_args,
|
||||
}));
|
||||
}
|
||||
Err(e) => {
|
||||
{
|
||||
let mut tasks = tasks_clone.lock().unwrap();
|
||||
if let Some(task) = tasks.get_mut(&task_id_clone) {
|
||||
task.status = TvaiTaskStatus::Failed;
|
||||
task.error_message = Some(e.to_string());
|
||||
task.completed_at = Some(chrono::Utc::now());
|
||||
}
|
||||
}
|
||||
let _ = app_handle_clone.emit("ffmpeg_command_finished", &serde_json::json!({
|
||||
"taskId": task_id_clone,
|
||||
"success": false,
|
||||
"stdout": "",
|
||||
"stderr": e.to_string(),
|
||||
"args": command_args,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
app_handle_clone.emit("tvai_task_updated", &task_id_clone).unwrap();
|
||||
});
|
||||
|
||||
Ok(task_id)
|
||||
}
|
||||
|
||||
/// 执行 FFmpeg 命令的内部函数(带进度监控)
|
||||
async fn execute_ffmpeg_with_tvai(
|
||||
args: Vec<String>,
|
||||
use_topaz: bool,
|
||||
task_id: String,
|
||||
app_handle: tauri::AppHandle
|
||||
) -> Result<(String, String), TvaiError> {
|
||||
use std::env;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::Stdio;
|
||||
use tokio::process::Command as TokioCommand;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader as TokioBufReader};
|
||||
|
||||
// 检测 Topaz 安装路径
|
||||
let topaz_path = if use_topaz {
|
||||
detect_topaz_installation()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if use_topaz && topaz_path.is_none() {
|
||||
return Err(TvaiError::TopazNotFound("Topaz Video AI installation not found".into()));
|
||||
}
|
||||
|
||||
let topaz_dir = topaz_path.unwrap_or_else(|| std::path::PathBuf::from("."));
|
||||
let ffmpeg_path = topaz_dir.join("ffmpeg.exe");
|
||||
|
||||
// 设置模型目录环境变量
|
||||
let models_dir = topaz_dir.join("models");
|
||||
let programdata_models = std::path::PathBuf::from(r"C:\ProgramData\Topaz Labs LLC\Topaz Video AI\models");
|
||||
|
||||
let model_dir = if models_dir.exists() {
|
||||
models_dir
|
||||
} else if programdata_models.exists() {
|
||||
programdata_models
|
||||
} else {
|
||||
models_dir // 默认使用安装目录下的 models
|
||||
};
|
||||
|
||||
// 构建异步命令
|
||||
let mut cmd = TokioCommand::new(&ffmpeg_path);
|
||||
cmd.args(&args);
|
||||
cmd.current_dir(&topaz_dir);
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
||||
// 设置关键环境变量
|
||||
cmd.env("TVAI_MODEL_DIR", &model_dir);
|
||||
cmd.env("TVAI_MODEL_DATA_DIR", &model_dir);
|
||||
|
||||
// 确保 PATH 包含 Topaz 目录
|
||||
if let Ok(current_path) = env::var("PATH") {
|
||||
let topaz_dir_str = topaz_dir.to_string_lossy();
|
||||
if !current_path.contains(&*topaz_dir_str) {
|
||||
let new_path = format!("{};{}", topaz_dir_str, current_path);
|
||||
cmd.env("PATH", new_path);
|
||||
}
|
||||
} else {
|
||||
cmd.env("PATH", topaz_dir.to_string_lossy().as_ref());
|
||||
}
|
||||
|
||||
// 启动进程
|
||||
let mut child = cmd.spawn().map_err(|e| {
|
||||
TvaiError::FfmpegError(format!("Failed to spawn FFmpeg: {}", e))
|
||||
})?;
|
||||
|
||||
let stderr = child.stderr.take().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
|
||||
// 异步读取 stderr 进行进度解析
|
||||
let task_id_clone = task_id.clone();
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let stderr_handle = tokio::spawn(async move {
|
||||
let reader = TokioBufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
let mut stderr_output = String::new();
|
||||
let mut total_duration: Option<f32> = None;
|
||||
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
stderr_output.push_str(&line);
|
||||
stderr_output.push('\n');
|
||||
|
||||
// 打印调试信息
|
||||
println!("FFmpeg stderr line: {}", line);
|
||||
|
||||
// 解析总时长信息
|
||||
if total_duration.is_none() && line.contains("Duration:") {
|
||||
if let Some(duration) = parse_duration_from_line(&line) {
|
||||
total_duration = Some(duration);
|
||||
println!("Detected total duration: {:.2} seconds", duration);
|
||||
}
|
||||
}
|
||||
|
||||
// 解析进度信息
|
||||
if let Some(mut progress) = parse_ffmpeg_progress(&line) {
|
||||
// 如果有总时长,重新计算更准确的百分比
|
||||
if let Some(total) = total_duration {
|
||||
if let Some(current_seconds) = parse_time_to_seconds(&progress.time) {
|
||||
progress.percentage = (current_seconds / total * 100.0).min(100.0);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Parsed progress: frame={}, fps={}, time={}, percentage={:.1}%",
|
||||
progress.frame, progress.fps, progress.time, progress.percentage);
|
||||
|
||||
let emit_result = app_handle_clone.emit("ffmpeg_progress", &serde_json::json!({
|
||||
"taskId": task_id_clone,
|
||||
"progress": progress.percentage,
|
||||
"frame": progress.frame,
|
||||
"fps": progress.fps,
|
||||
"time": progress.time,
|
||||
"speed": progress.speed,
|
||||
"bitrate": progress.bitrate
|
||||
}));
|
||||
|
||||
if let Err(e) = emit_result {
|
||||
println!("Failed to emit progress event: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
stderr_output
|
||||
});
|
||||
|
||||
// 异步读取 stdout
|
||||
let stdout_handle = tokio::spawn(async move {
|
||||
let reader = TokioBufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
let mut stdout_output = String::new();
|
||||
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
stdout_output.push_str(&line);
|
||||
stdout_output.push('\n');
|
||||
}
|
||||
stdout_output
|
||||
});
|
||||
|
||||
// 等待进程完成
|
||||
let status = child.wait().await.map_err(|e| {
|
||||
TvaiError::FfmpegError(format!("Failed to wait for FFmpeg: {}", e))
|
||||
})?;
|
||||
|
||||
let stdout_log = stdout_handle.await.unwrap_or_default();
|
||||
let stderr_log = stderr_handle.await.unwrap_or_default();
|
||||
|
||||
if !status.success() {
|
||||
return Err(TvaiError::FfmpegError(format!(
|
||||
"FFmpeg failed: {}",
|
||||
if stderr_log.is_empty() { stdout_log } else { stderr_log }
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((stdout_log, stderr_log))
|
||||
}
|
||||
|
||||
/// FFmpeg 进度信息结构
|
||||
#[derive(Debug)]
|
||||
struct FfmpegProgress {
|
||||
frame: u32,
|
||||
fps: f32,
|
||||
time: String,
|
||||
bitrate: String,
|
||||
speed: String,
|
||||
percentage: f32,
|
||||
}
|
||||
|
||||
/// 解析 FFmpeg 输出中的进度信息
|
||||
fn parse_ffmpeg_progress(line: &str) -> Option<FfmpegProgress> {
|
||||
// FFmpeg 进度行格式可能包含回车符,需要处理最后一个完整的进度行
|
||||
// 实际格式: frame= 29 fps=2.3 q=7.0 size= 0KiB time=00:00:01.20 bitrate= 0.0kbits/s speed=N/A
|
||||
|
||||
// 只处理包含 frame= 和 time= 的行,跳过 time=N/A 的行
|
||||
if !line.contains("frame=") || line.contains("time=N/A") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 处理可能的回车符覆盖 - 取最后一个完整的进度信息
|
||||
let clean_line = if line.contains('\r') {
|
||||
// 按回车符分割,取最后一个非空部分
|
||||
line.split('\r')
|
||||
.filter(|s| !s.is_empty() && s.contains("frame="))
|
||||
.last()
|
||||
.unwrap_or(line)
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
println!("Attempting to parse progress line: {}", clean_line);
|
||||
|
||||
let mut frame = 0u32;
|
||||
let mut fps = 0.0f32;
|
||||
let mut time = String::new();
|
||||
let mut bitrate = String::new();
|
||||
let mut speed = String::new();
|
||||
|
||||
// 简单的字符串解析,避免正则表达式的复杂性
|
||||
// 按空格分割并查找各个字段
|
||||
let parts: Vec<&str> = clean_line.split_whitespace().collect();
|
||||
|
||||
for i in 0..parts.len() {
|
||||
let part = parts[i];
|
||||
|
||||
if part.starts_with("frame=") {
|
||||
if let Some(value) = part.strip_prefix("frame=") {
|
||||
frame = value.parse().unwrap_or(0);
|
||||
}
|
||||
} else if part.starts_with("fps=") {
|
||||
if let Some(value) = part.strip_prefix("fps=") {
|
||||
fps = value.parse().unwrap_or(0.0);
|
||||
}
|
||||
} else if part.starts_with("time=") {
|
||||
if let Some(value) = part.strip_prefix("time=") {
|
||||
time = value.to_string();
|
||||
}
|
||||
} else if part.starts_with("bitrate=") {
|
||||
if let Some(value) = part.strip_prefix("bitrate=") {
|
||||
bitrate = value.to_string();
|
||||
}
|
||||
} else if part.starts_with("speed=") {
|
||||
if let Some(value) = part.strip_prefix("speed=") {
|
||||
speed = value.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分离的情况,如 "frame=" "29"
|
||||
if part == "frame=" && i + 1 < parts.len() {
|
||||
frame = parts[i + 1].parse().unwrap_or(0);
|
||||
} else if part == "fps=" && i + 1 < parts.len() {
|
||||
fps = parts[i + 1].parse().unwrap_or(0.0);
|
||||
} else if part == "time=" && i + 1 < parts.len() {
|
||||
time = parts[i + 1].to_string();
|
||||
} else if part == "bitrate=" && i + 1 < parts.len() {
|
||||
bitrate = parts[i + 1].to_string();
|
||||
} else if part == "speed=" && i + 1 < parts.len() {
|
||||
speed = parts[i + 1].to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的百分比估算 - 这里会在外部重新计算
|
||||
let percentage = if !time.is_empty() && time != "N/A" {
|
||||
if let Some(seconds) = parse_time_to_seconds(&time) {
|
||||
// 简单估算,外部会重新计算更准确的值
|
||||
(seconds / 60.0 * 100.0).min(100.0) // 假设60秒为100%
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else if frame > 0 {
|
||||
// 基于帧数的简单估算
|
||||
(frame as f32 / 1000.0 * 100.0).min(100.0) // 假设1000帧为100%
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
println!("Parsed: frame={}, fps={}, time={}, percentage={:.1}%", frame, fps, time, percentage);
|
||||
|
||||
// 只有当解析到有效数据时才返回
|
||||
if frame > 0 || !time.is_empty() {
|
||||
Some(FfmpegProgress {
|
||||
frame,
|
||||
fps,
|
||||
time,
|
||||
bitrate,
|
||||
speed,
|
||||
percentage,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析时间字符串为秒数
|
||||
fn parse_time_to_seconds(time_str: &str) -> Option<f32> {
|
||||
// 格式: HH:MM:SS.ms 或 MM:SS.ms
|
||||
let parts: Vec<&str> = time_str.split(':').collect();
|
||||
|
||||
match parts.len() {
|
||||
3 => {
|
||||
// HH:MM:SS.ms
|
||||
let hours: f32 = parts[0].parse().ok()?;
|
||||
let minutes: f32 = parts[1].parse().ok()?;
|
||||
let seconds: f32 = parts[2].parse().ok()?;
|
||||
Some(hours * 3600.0 + minutes * 60.0 + seconds)
|
||||
}
|
||||
2 => {
|
||||
// MM:SS.ms
|
||||
let minutes: f32 = parts[0].parse().ok()?;
|
||||
let seconds: f32 = parts[1].parse().ok()?;
|
||||
Some(minutes * 60.0 + seconds)
|
||||
}
|
||||
1 => {
|
||||
// SS.ms
|
||||
parts[0].parse().ok()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 FFmpeg 输出行中解析总时长
|
||||
fn parse_duration_from_line(line: &str) -> Option<f32> {
|
||||
// 格式: "Duration: 00:00:41.25, start: 0.000000, bitrate: 10778 kb/s"
|
||||
if let Some(duration_start) = line.find("Duration: ") {
|
||||
let after_duration = &line[duration_start + 10..];
|
||||
if let Some(comma_pos) = after_duration.find(',') {
|
||||
let duration_str = &after_duration[..comma_pos];
|
||||
return parse_time_to_seconds(duration_str);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ import HedraLipSyncRecords from './pages/tools/HedraLipSyncRecords';
|
||||
import OmniHumanDetectionTool from './pages/tools/OmniHumanDetectionTool';
|
||||
import { EnrichedAnalysisDemo } from './pages/tools/EnrichedAnalysisDemo';
|
||||
import AiModelFaceHairFixTool from './pages/tools/AiModelFaceHairFixTool';
|
||||
import TvaiTool from './pages/tools/TvaiTool';
|
||||
import TopazVideoAITool from './pages/tools/TopazVideoAITool';
|
||||
import CommandGenerator from './components/CommandGenerator';
|
||||
import MaterialCenter from './pages/MaterialCenter';
|
||||
import VideoGeneration from './pages/VideoGeneration';
|
||||
import { OutfitPhotoGenerationPage } from './pages/OutfitPhotoGeneration';
|
||||
@@ -181,7 +182,8 @@ function App() {
|
||||
<Route path="/tools/advanced-filter-demo" element={<AdvancedFilterTool />} />
|
||||
<Route path="/tools/enriched-analysis-demo" element={<EnrichedAnalysisDemo />} />
|
||||
<Route path="/tools/ai-model-face-hair-fix" element={<AiModelFaceHairFixTool />} />
|
||||
<Route path="/tools/tvai" element={<TvaiTool />} />
|
||||
<Route path="/tools/topaz-video-ai" element={<TopazVideoAITool />} />
|
||||
<Route path="/tools/command-generator" element={<CommandGenerator />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
1436
apps/desktop/src/components/CommandGenerator.tsx
Normal file
1436
apps/desktop/src/components/CommandGenerator.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -73,41 +73,23 @@ const Navigation: React.FC = () => {
|
||||
description: 'AI穿搭方案推荐与素材检索'
|
||||
},
|
||||
{
|
||||
name: 'ComfyUI',
|
||||
icon: RectangleStackIcon,
|
||||
description: 'AI工作流管理平台',
|
||||
name: '工具',
|
||||
icon: WrenchScrewdriverIcon,
|
||||
description: 'AI检索图片/数据清洗工具',
|
||||
children: [
|
||||
{
|
||||
name: 'V2 仪表板',
|
||||
href: '/comfyui-v2-dashboard',
|
||||
icon: ChartBarIcon,
|
||||
description: '现代化AI工作流管理仪表板'
|
||||
name: '工具中心',
|
||||
href: '/tools',
|
||||
icon: WrenchScrewdriverIcon,
|
||||
description: 'AI检索图片/数据清洗工具'
|
||||
},
|
||||
{
|
||||
name: '集群管理',
|
||||
href: '/comfyui-management',
|
||||
icon: ServerIcon,
|
||||
description: '分布式ComfyUI集群管理'
|
||||
},
|
||||
{
|
||||
name: '工作流测试',
|
||||
href: '/comfyui-workflow-test',
|
||||
icon: PlayIcon,
|
||||
description: '工作流测试和调试'
|
||||
},
|
||||
{
|
||||
name: '模板创建器测试',
|
||||
href: '/workflow-template-creator-test',
|
||||
icon: DocumentDuplicateIcon,
|
||||
description: '工作流模板创建器功能测试'
|
||||
name: 'TVAI FFmpeg 命令生成器',
|
||||
href: '/tools/command-generator',
|
||||
icon: CpuChipIcon,
|
||||
description: '生成 Topaz Video AI FFmpeg 命令'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '工具',
|
||||
href: '/tools',
|
||||
icon: WrenchScrewdriverIcon,
|
||||
description: 'AI检索图片/数据清洗工具'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
416
apps/desktop/src/components/TopazVideoAIConfigurator.tsx
Normal file
416
apps/desktop/src/components/TopazVideoAIConfigurator.tsx
Normal file
@@ -0,0 +1,416 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Video,
|
||||
Settings,
|
||||
Play,
|
||||
Copy,
|
||||
Download,
|
||||
Sparkles,
|
||||
Zap,
|
||||
Film,
|
||||
Palette,
|
||||
Sliders,
|
||||
Monitor,
|
||||
Save,
|
||||
RefreshCw
|
||||
} from 'lucide-react';
|
||||
import { Button } from './ui/Button';
|
||||
import { Card } from './ui/Card';
|
||||
import { Input } from './ui/Input';
|
||||
import { Select } from './ui/Select';
|
||||
import { Checkbox } from './ui/Checkbox';
|
||||
import { Label } from './ui/Label';
|
||||
import { Tabs } from './ui/Tabs';
|
||||
import { Textarea } from './ui/Textarea';
|
||||
import { Toast } from './ui/Toast';
|
||||
|
||||
// 内置模板数据
|
||||
const builtinTemplates = {
|
||||
"upscale_to_4k": {
|
||||
name: "4K放大",
|
||||
description: "将视频放大到4K分辨率,适用于低分辨率视频的高质量放大",
|
||||
icon: "🎯",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "prob-4", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 3, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"8x_super_slow_motion": {
|
||||
name: "8倍超级慢动作",
|
||||
description: "创建8倍慢动作效果,使用AI插帧技术生成流畅的慢动作视频",
|
||||
icon: "🐌",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: true, model: "apo-8", factor: 8, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: false, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"hdr_enhancement": {
|
||||
name: "HDR增强处理",
|
||||
description: "专业HDR视频处理,包含色彩空间转换和动态范围优化",
|
||||
icon: "🌈",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "prob-4", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 50, sharpen: 30, denoise: 20, dehalo: 10, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 1, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: true, model: "hyp-1", auto: 50, exposure: 13, saturation: 10, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"motion_blur_removal": {
|
||||
name: "运动模糊去除",
|
||||
description: "专门去除视频中的运动模糊,提升动态场景清晰度",
|
||||
icon: "🌀",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 60, method: 1, rsc: false, reduce_motion: 3, reduce_motion_iteration: 3 },
|
||||
motionblur: { active: true, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "ghq-5", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 30, sharpen: 50, denoise: 20, dehalo: 30, deblur: 80, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface TopazSettings {
|
||||
stabilize: any;
|
||||
motionblur: any;
|
||||
slowmo: any;
|
||||
enhance: any;
|
||||
grain: any;
|
||||
output: any;
|
||||
hdr: any;
|
||||
}
|
||||
|
||||
const TopazVideoAIConfigurator: React.FC = () => {
|
||||
const [currentTemplate, setCurrentTemplate] = useState<string | null>(null);
|
||||
const [settings, setSettings] = useState<TopazSettings>({
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 2, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
});
|
||||
|
||||
const [inputFile, setInputFile] = useState('input.mp4');
|
||||
const [outputFile, setOutputFile] = useState('output.mp4');
|
||||
const [processingMode, setProcessingMode] = useState('single');
|
||||
const [rateControl, setRateControl] = useState('constqp');
|
||||
const [generatedCommand, setGeneratedCommand] = useState('');
|
||||
const [showToast, setShowToast] = useState(false);
|
||||
const [toastMessage, setToastMessage] = useState('');
|
||||
|
||||
// 选择模板
|
||||
const selectTemplate = (templateKey: string) => {
|
||||
setCurrentTemplate(templateKey);
|
||||
const template = builtinTemplates[templateKey as keyof typeof builtinTemplates];
|
||||
if (template) {
|
||||
setSettings(template.settings);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新设置
|
||||
const updateSetting = (category: string, field: string, value: any) => {
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
[category]: {
|
||||
...prev[category as keyof TopazSettings],
|
||||
[field]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// 生成 FFmpeg 命令
|
||||
const generateCommand = () => {
|
||||
const enabledFeatures = [];
|
||||
let filterChain = [];
|
||||
|
||||
// 防抖
|
||||
if (settings.stabilize.active) {
|
||||
const smoothness = settings.stabilize.smooth / 10.0;
|
||||
filterChain.push(`tvai_stb=model=ref-2:smoothness=${smoothness}:rst=${settings.stabilize.rsc ? 1 : 0}:reduce=${settings.stabilize.reduce_motion}:device=-2:vram=1:instances=1`);
|
||||
enabledFeatures.push('防抖');
|
||||
}
|
||||
|
||||
// 运动模糊去除
|
||||
if (settings.motionblur.active) {
|
||||
filterChain.push(`tvai_up=model=${settings.motionblur.model}:scale=1:device=-2:vram=1:instances=0`);
|
||||
enabledFeatures.push('运动模糊去除');
|
||||
}
|
||||
|
||||
// 慢动作
|
||||
if (settings.slowmo.active) {
|
||||
filterChain.push(`tvai_fi=model=${settings.slowmo.model}:slowmo=${settings.slowmo.factor}:rdt=${settings.slowmo.duplicate_threshold / 100.0}:device=-2:vram=1:instances=1`);
|
||||
enabledFeatures.push(`${settings.slowmo.factor}x 慢动作`);
|
||||
}
|
||||
|
||||
// 增强
|
||||
if (settings.enhance.active) {
|
||||
const scaleMap = { 0: 1, 1: 2, 2: 3, 3: 4, 7: 2 };
|
||||
const scale = scaleMap[settings.output.out_size_method as keyof typeof scaleMap] || 1;
|
||||
filterChain.push(`tvai_up=model=${settings.enhance.model}:scale=${scale}:noise=${settings.enhance.denoise}:details=${settings.enhance.detail}:halo=${settings.enhance.dehalo}:blur=${settings.enhance.deblur}:compression=${settings.enhance.compress}:device=-2:vram=1:instances=1`);
|
||||
enabledFeatures.push('AI 增强');
|
||||
}
|
||||
|
||||
// HDR
|
||||
if (settings.hdr.active) {
|
||||
const sdrIp = settings.hdr.sdr_inflection_point;
|
||||
const hdrAdjust = settings.hdr.exposure / 100.0;
|
||||
const saturate = settings.hdr.saturation / 100.0;
|
||||
filterChain.push(`tvai_up=model=${settings.hdr.model}:scale=1:w=1920:h=1088:parameters='sdr_ip=${sdrIp}\\:hdr_ip_adjust=${hdrAdjust}\\:saturate=${saturate}':device=-2:vram=1:instances=1`);
|
||||
filterChain.push('setparams=range=pc:color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc');
|
||||
enabledFeatures.push('HDR 处理');
|
||||
}
|
||||
|
||||
// 构建命令
|
||||
let cmd = `ffmpeg -hide_banner -y -i "${inputFile}" -sws_flags spline+accurate_rnd+full_chroma_int`;
|
||||
|
||||
if (filterChain.length > 0) {
|
||||
cmd += ` -filter_complex "${filterChain.join(',')}"`;
|
||||
}
|
||||
|
||||
// 编码设置
|
||||
cmd += ' -c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30 -preset p7 -tune hq';
|
||||
|
||||
// 率控制
|
||||
switch (rateControl) {
|
||||
case 'constqp':
|
||||
cmd += ' -rc constqp -qp 18';
|
||||
break;
|
||||
case 'cbr':
|
||||
cmd += ' -rc cbr -b:v 24M';
|
||||
break;
|
||||
case 'vbr':
|
||||
cmd += ' -rc vbr -b:v 20M';
|
||||
break;
|
||||
case 'crf':
|
||||
cmd += ' -crf 18';
|
||||
break;
|
||||
}
|
||||
|
||||
cmd += ' -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0 -bf 0';
|
||||
cmd += ' -an -map_metadata 0 -fps_mode:v passthrough';
|
||||
cmd += ' -movflags frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr';
|
||||
cmd += ` -metadata "videoai=Generated by Topaz Video AI Configurator - Features: ${enabledFeatures.join(', ')}"`;
|
||||
cmd += ` "${outputFile}"`;
|
||||
|
||||
setGeneratedCommand(cmd);
|
||||
};
|
||||
|
||||
// 复制命令
|
||||
const copyCommand = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(generatedCommand);
|
||||
setToastMessage('命令已复制到剪贴板!');
|
||||
setShowToast(true);
|
||||
setTimeout(() => setShowToast(false), 3000);
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* 头部 */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="flex items-center justify-center gap-3 mb-4">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center shadow-lg">
|
||||
<Video className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-gray-900">Topaz Video AI 参数配置器</h1>
|
||||
<p className="text-lg text-gray-600 mt-2">基于真实命令分析的专业视频处理参数配置工具 - 参数使用率 75%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{/* 模板选择 */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Sparkles className="w-5 h-5 text-blue-500" />
|
||||
内置模板
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(builtinTemplates).map(([key, template]) => (
|
||||
<div
|
||||
key={key}
|
||||
className={`p-4 rounded-lg border-2 cursor-pointer transition-all duration-200 ${
|
||||
currentTemplate === key
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-blue-300 hover:bg-gray-50'
|
||||
}`}
|
||||
onClick={() => selectTemplate(key)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">{template.icon}</span>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">{template.name}</h4>
|
||||
<p className="text-sm text-gray-600 mt-1">{template.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 参数配置 */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Settings className="w-5 h-5 text-green-500" />
|
||||
参数配置
|
||||
</h3>
|
||||
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<div className="flex space-x-1 mb-6 bg-gray-100 p-1 rounded-lg">
|
||||
<button className="flex-1 py-2 px-4 rounded-md bg-white shadow-sm text-sm font-medium">基础设置</button>
|
||||
<button className="flex-1 py-2 px-4 rounded-md text-sm font-medium text-gray-600">高级设置</button>
|
||||
</div>
|
||||
|
||||
{/* 基础设置面板 */}
|
||||
<div className="space-y-6">
|
||||
{/* 文件设置 */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-medium text-gray-900 flex items-center gap-2">
|
||||
<Film className="w-4 h-4" />
|
||||
文件设置
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="input-file">输入文件</Label>
|
||||
<Input
|
||||
id="input-file"
|
||||
value={inputFile}
|
||||
onChange={(e) => setInputFile(e.target.value)}
|
||||
placeholder="input.mp4"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="output-file">输出文件</Label>
|
||||
<Input
|
||||
id="output-file"
|
||||
value={outputFile}
|
||||
onChange={(e) => setOutputFile(e.target.value)}
|
||||
placeholder="output.mp4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 处理模式 */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="font-medium text-gray-900 flex items-center gap-2">
|
||||
<Zap className="w-4 h-4" />
|
||||
处理模式
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="processing-mode">处理模式</Label>
|
||||
<Select
|
||||
value={processingMode}
|
||||
onValueChange={setProcessingMode}
|
||||
>
|
||||
<option value="single">单阶段处理</option>
|
||||
<option value="two_pass">两阶段处理 (真实 Topaz)</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="rate-control">率控制模式</Label>
|
||||
<Select
|
||||
value={rateControl}
|
||||
onValueChange={setRateControl}
|
||||
>
|
||||
<option value="constqp">恒定 QP (推荐)</option>
|
||||
<option value="cbr">恒定比特率</option>
|
||||
<option value="vbr">可变比特率</option>
|
||||
<option value="crf">CRF</option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 输出区域 */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<Monitor className="w-5 h-5 text-purple-500" />
|
||||
生成的命令
|
||||
</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
onClick={generateCommand}
|
||||
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700"
|
||||
>
|
||||
<Play className="w-4 h-4 mr-2" />
|
||||
生成 FFmpeg 命令
|
||||
</Button>
|
||||
|
||||
{generatedCommand && (
|
||||
<div className="space-y-3">
|
||||
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm overflow-x-auto">
|
||||
{generatedCommand}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={copyCommand}
|
||||
className="flex-1"
|
||||
>
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
复制
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toast 通知 */}
|
||||
{showToast && (
|
||||
<Toast
|
||||
message={toastMessage}
|
||||
type="success"
|
||||
onClose={() => setShowToast(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopazVideoAIConfigurator;
|
||||
@@ -1,653 +0,0 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Upload, Play, Settings, HelpCircle, CheckCircle, AlertCircle, Clock, X, FolderOpen, File } from 'lucide-react';
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { useTvai, useTvaiSystemInfo } from '../hooks/useTvai';
|
||||
import { tvaiService } from '../services/tvaiService';
|
||||
import { TVAI_PRESETS, SCALE_FACTORS } from '../types/tvai';
|
||||
import type { UpscaleModel, QualityPreset, ImageFormat } from '../types/tvai';
|
||||
|
||||
// 文件格式配置
|
||||
const FILE_FORMATS = {
|
||||
video: {
|
||||
name: '视频文件',
|
||||
extensions: ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'm4v', 'webm', '3gp', 'ts']
|
||||
},
|
||||
image: {
|
||||
name: '图片文件',
|
||||
extensions: ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'webp', 'gif', 'svg']
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TVAI 功能组件 - 重新设计的用户友好界面
|
||||
*/
|
||||
export function TvaiExample() {
|
||||
// 所有的Hooks必须在早期返回之前调用
|
||||
const { systemInfo, isLoading: systemLoading, initializeTvai } = useTvaiSystemInfo();
|
||||
const { tasks, isLoading: tasksLoading, cancelTask, cleanupCompletedTasks } = useTvai();
|
||||
|
||||
// 界面状态
|
||||
const [processingType, setProcessingType] = useState<'video' | 'image' | 'interpolation'>('video');
|
||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||
|
||||
// 基础参数
|
||||
const [inputPath, setInputPath] = useState('');
|
||||
const [outputPath, setOutputPath] = useState('');
|
||||
const [scaleFactor, setScaleFactor] = useState(2.0);
|
||||
const [selectedPreset, setSelectedPreset] = useState('GENERAL');
|
||||
|
||||
// 高级参数
|
||||
const [selectedModel, setSelectedModel] = useState<UpscaleModel>('iris-3');
|
||||
const [selectedInterpolationModel, setSelectedInterpolationModel] = useState<'apo-8' | 'apf-1' | 'chr-2' | 'chf-3'>('apo-8');
|
||||
const [compression, setCompression] = useState(0.0);
|
||||
const [blend, setBlend] = useState(0.0);
|
||||
|
||||
// 插帧参数
|
||||
const [interpolationMultiplier, setInterpolationMultiplier] = useState(2.0);
|
||||
|
||||
// 文件选择处理函数 - 必须在早期返回之前
|
||||
const handleSelectInputFile = useCallback(async () => {
|
||||
try {
|
||||
const format = processingType === 'image' ? FILE_FORMATS.image : FILE_FORMATS.video;
|
||||
const filters = [format];
|
||||
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters,
|
||||
title: `选择${processingType === 'image' ? '图片' : '视频'}文件`,
|
||||
});
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
setInputPath(selected);
|
||||
// 自动生成输出路径
|
||||
const pathParts = selected.split('.');
|
||||
const extension = pathParts.pop();
|
||||
const basePath = pathParts.join('.');
|
||||
const suffix = processingType === 'interpolation' ? '_interpolated' :
|
||||
processingType === 'image' ? '_enhanced' : '_upscaled';
|
||||
setOutputPath(`${basePath}${suffix}.${extension}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择文件失败:', error);
|
||||
alert('选择文件失败: ' + (error as Error).message);
|
||||
}
|
||||
}, [processingType]);
|
||||
|
||||
const handleSelectOutputFile = useCallback(async () => {
|
||||
try {
|
||||
const format = processingType === 'image' ? FILE_FORMATS.image : FILE_FORMATS.video;
|
||||
const filters = [format];
|
||||
|
||||
const selected = await open({
|
||||
multiple: false,
|
||||
filters,
|
||||
title: `选择输出${processingType === 'image' ? '图片' : '视频'}文件`,
|
||||
});
|
||||
|
||||
if (selected && typeof selected === 'string') {
|
||||
setOutputPath(selected);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('选择输出文件失败:', error);
|
||||
alert('选择输出文件失败: ' + (error as Error).message);
|
||||
}
|
||||
}, [processingType]);
|
||||
|
||||
// 应用预设
|
||||
const applyPreset = (presetName: string) => {
|
||||
const presets = TVAI_PRESETS.VIDEO_UPSCALE_MODELS;
|
||||
if (presetName in presets) {
|
||||
const preset = presets[presetName as keyof typeof presets];
|
||||
setSelectedModel(preset.model);
|
||||
setCompression(preset.compression);
|
||||
setBlend(preset.blend);
|
||||
}
|
||||
};
|
||||
|
||||
// 快速处理函数
|
||||
const handleQuickProcess = async () => {
|
||||
if (!inputPath || !outputPath) {
|
||||
alert('请选择输入和输出文件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (processingType === 'video') {
|
||||
await tvaiService.quickUpscaleVideo(inputPath, outputPath, scaleFactor, selectedModel);
|
||||
} else if (processingType === 'interpolation') {
|
||||
// 插帧处理 - 需要先获取视频信息来确定帧率
|
||||
try {
|
||||
const videoInfo = await tvaiService.getVideoInfo(inputPath);
|
||||
const inputFps = Math.round(videoInfo.fps);
|
||||
await tvaiService.quickInterpolateVideo(inputPath, outputPath, interpolationMultiplier, inputFps);
|
||||
} catch (error) {
|
||||
console.error('获取视频信息失败:', error);
|
||||
// 如果无法获取视频信息,使用默认帧率
|
||||
await tvaiService.quickInterpolateVideo(inputPath, outputPath, interpolationMultiplier, 30);
|
||||
}
|
||||
} else {
|
||||
await tvaiService.quickUpscaleImage(inputPath, outputPath, scaleFactor);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理失败:', error);
|
||||
alert('处理失败: ' + (error as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
// 早期返回检查
|
||||
if (systemLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
|
||||
<p className="text-gray-600">正在检测系统...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
{/* 系统状态检查 */}
|
||||
{!systemInfo.isInitialized && (
|
||||
<div className="mb-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-yellow-600" />
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-yellow-800">需要初始化 TVAI</h3>
|
||||
<p className="text-sm text-yellow-700 mt-1">
|
||||
请先初始化 Topaz Video AI 以使用增强功能
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => initializeTvai({})}
|
||||
className="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors"
|
||||
>
|
||||
立即初始化
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 主要内容区域 */}
|
||||
<div className="space-y-6">
|
||||
{/* 步骤1: 选择处理类型 */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 font-semibold">1</span>
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">选择处理类型</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<button
|
||||
onClick={() => setProcessingType('video')}
|
||||
className={`p-4 rounded-lg border-2 transition-all ${
|
||||
processingType === 'video'
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Play className="w-8 h-8 mx-auto mb-2 text-blue-600" />
|
||||
<h3 className="font-medium text-gray-900">视频放大</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">提升视频分辨率和质量</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setProcessingType('interpolation')}
|
||||
className={`p-4 rounded-lg border-2 transition-all ${
|
||||
processingType === 'interpolation'
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Clock className="w-8 h-8 mx-auto mb-2 text-purple-600" />
|
||||
<h3 className="font-medium text-gray-900">视频插帧</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">增加帧率,制作慢动作</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setProcessingType('image')}
|
||||
className={`p-4 rounded-lg border-2 transition-all ${
|
||||
processingType === 'image'
|
||||
? 'border-green-500 bg-green-50'
|
||||
: 'border-gray-200 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Upload className="w-8 h-8 mx-auto mb-2 text-green-600" />
|
||||
<h3 className="font-medium text-gray-900">图片增强</h3>
|
||||
<p className="text-sm text-gray-600 mt-1">提升图片分辨率和细节</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 步骤2: 文件选择 */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 font-semibold">2</span>
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">选择文件</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
输入文件
|
||||
</label>
|
||||
<button
|
||||
onClick={handleSelectInputFile}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors text-left"
|
||||
>
|
||||
<FolderOpen className="w-5 h-5 text-gray-400 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
{inputPath ? (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{inputPath.split('/').pop() || inputPath.split('\\').pop()}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{inputPath}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-500">
|
||||
选择{processingType === 'image' ? '图片' : processingType === 'interpolation' ? '视频' : '视频'}文件...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
输出文件
|
||||
</label>
|
||||
<button
|
||||
onClick={handleSelectOutputFile}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors text-left"
|
||||
>
|
||||
<File className="w-5 h-5 text-gray-400 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
{outputPath ? (
|
||||
<div>
|
||||
<div className="text-sm font-medium text-gray-900 truncate">
|
||||
{outputPath.split('/').pop() || outputPath.split('\\').pop()}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{outputPath}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-500">
|
||||
选择输出文件路径...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
{inputPath && !outputPath && (
|
||||
<button
|
||||
onClick={() => {
|
||||
const pathParts = inputPath.split('.');
|
||||
const extension = pathParts.pop();
|
||||
const basePath = pathParts.join('.');
|
||||
const suffix = processingType === 'interpolation' ? '_interpolated' : '_upscaled';
|
||||
setOutputPath(`${basePath}${suffix}.${extension}`);
|
||||
}}
|
||||
className="mt-2 text-sm text-blue-600 hover:text-blue-700 underline"
|
||||
>
|
||||
自动生成输出路径
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 步骤3: 快速设置 */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 font-semibold">3</span>
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-gray-900">处理设置</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className="flex items-center gap-2 px-3 py-1 text-sm text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
{showAdvanced ? '简单模式' : '高级设置'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!showAdvanced ? (
|
||||
/* 简单模式 */
|
||||
<div className="space-y-4">
|
||||
{processingType === 'interpolation' ? (
|
||||
// 插帧参数
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
插帧倍数
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{[1.5, 2, 2.5, 3, 4, 8].map(multiplier => (
|
||||
<button
|
||||
key={multiplier}
|
||||
onClick={() => setInterpolationMultiplier(multiplier)}
|
||||
className={`px-4 py-2 rounded-lg border transition-all ${
|
||||
interpolationMultiplier === multiplier
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
{multiplier}x
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
例如:30fps × 2x = 60fps
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
// 放大倍数
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
放大倍数
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{SCALE_FACTORS.map(factor => (
|
||||
<button
|
||||
key={factor}
|
||||
onClick={() => setScaleFactor(factor)}
|
||||
className={`px-4 py-2 rounded-lg border transition-all ${
|
||||
scaleFactor === factor
|
||||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
{factor}x
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{processingType === 'interpolation' ? '插帧模型预设' : '内容类型预设'}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
{processingType === 'interpolation' ? (
|
||||
// 插帧模型预设
|
||||
[
|
||||
{ key: 'APO8', name: '高质量', model: 'apo-8' },
|
||||
{ key: 'CHR2', name: '动画', model: 'chr-2' },
|
||||
{ key: 'APF1', name: '快速', model: 'apf-1' },
|
||||
{ key: 'CHF3', name: '快速动画', model: 'chf-3' }
|
||||
].map(preset => (
|
||||
<button
|
||||
key={preset.key}
|
||||
onClick={() => {
|
||||
setSelectedPreset(preset.key);
|
||||
setSelectedInterpolationModel(preset.model as 'apo-8' | 'apf-1' | 'chr-2' | 'chf-3');
|
||||
}}
|
||||
className={`p-3 rounded-lg border text-sm transition-all ${
|
||||
selectedPreset === preset.key
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
{preset.name}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
// 放大模型预设
|
||||
Object.keys(TVAI_PRESETS.VIDEO_UPSCALE_MODELS).map(presetName => {
|
||||
const presetNameMap: Record<string, string> = {
|
||||
'OLD_VIDEO': '老视频',
|
||||
'GAME_CONTENT': '游戏内容',
|
||||
'ANIMATION': '动画',
|
||||
'PORTRAIT': '人像',
|
||||
'GENERAL': '通用'
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
key={presetName}
|
||||
onClick={() => {
|
||||
setSelectedPreset(presetName);
|
||||
applyPreset(presetName);
|
||||
}}
|
||||
className={`p-3 rounded-lg border text-sm transition-all ${
|
||||
selectedPreset === presetName
|
||||
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
{presetNameMap[presetName] || presetName}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<button
|
||||
onClick={handleQuickProcess}
|
||||
disabled={!inputPath || !outputPath || !systemInfo.isInitialized}
|
||||
className={`w-full py-3 px-6 text-white rounded-lg hover:opacity-90 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2 ${
|
||||
processingType === 'interpolation' ? 'bg-purple-600' :
|
||||
processingType === 'image' ? 'bg-green-600' : 'bg-blue-600'
|
||||
}`}
|
||||
>
|
||||
<Play className="w-5 h-5" />
|
||||
开始{processingType === 'video' ? '视频放大' :
|
||||
processingType === 'interpolation' ? '视频插帧' : '图片增强'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* 高级模式 - 保留原有的详细参数配置 */
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
{processingType === 'interpolation' ? '插帧模型' : 'AI 放大模型'}
|
||||
</label>
|
||||
<select
|
||||
value={processingType === 'interpolation' ? selectedInterpolationModel : selectedModel}
|
||||
onChange={(e) => {
|
||||
if (processingType === 'interpolation') {
|
||||
setSelectedInterpolationModel(e.target.value as 'apo-8' | 'apf-1' | 'chr-2' | 'chf-3');
|
||||
} else {
|
||||
setSelectedModel(e.target.value as UpscaleModel);
|
||||
}
|
||||
}}
|
||||
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
{processingType === 'interpolation' ? (
|
||||
// 插帧模型
|
||||
<>
|
||||
<option value="apo-8">Apollo v8 (高质量插帧)</option>
|
||||
<option value="apf-1">Apollo Fast v1 (快速插帧)</option>
|
||||
<option value="chr-2">Chronos v2 (动画内容)</option>
|
||||
<option value="chf-3">Chronos Fast v3 (快速动画插帧)</option>
|
||||
</>
|
||||
) : (
|
||||
// 放大模型 - 完整列表
|
||||
<>
|
||||
<optgroup label="通用模型">
|
||||
<option value="iris-3">Iris v3 (最佳通用模型)</option>
|
||||
<option value="iris-2">Iris v2 (平衡质量和速度)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Artemis 系列">
|
||||
<option value="ahq-12">Artemis HQ v12 (高质量)</option>
|
||||
<option value="alq-13">Artemis LQ v13 (低质量输入优化)</option>
|
||||
<option value="alqs-2">Artemis LQ Small v2 (快速处理)</option>
|
||||
<option value="amq-13">Artemis MQ v13 (中等质量平衡)</option>
|
||||
<option value="amqs-2">Artemis MQ Small v2 (快速中等质量)</option>
|
||||
<option value="aaa-9">Artemis Anti-Alias v9 (抗锯齿)</option>
|
||||
</optgroup>
|
||||
<optgroup label="专用模型">
|
||||
<option value="nyx-3">Nyx v3 (人像优化)</option>
|
||||
<option value="ghq-5">Gaia HQ v5 (游戏/CG内容)</option>
|
||||
<option value="prob-4">Proteus v4 (问题素材修复)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Theia 系列">
|
||||
<option value="thf-4">Theia Fidelity v4 (老内容修复)</option>
|
||||
<option value="thd-3">Theia Detail v3 (细节增强)</option>
|
||||
<option value="thm-2">Theia Magic v2 (魔法增强)</option>
|
||||
</optgroup>
|
||||
<optgroup label="Rhea 系列">
|
||||
<option value="rhea-1">Rhea v1 (真实感增强)</option>
|
||||
<option value="rxl-1">Rhea XL v1 (超大分辨率)</option>
|
||||
</optgroup>
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
压缩 ({compression.toFixed(1)})
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="-1"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={compression}
|
||||
onChange={(e) => setCompression(Number(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
混合 ({blend.toFixed(1)})
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
value={blend}
|
||||
onChange={(e) => setBlend(Number(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<button
|
||||
onClick={handleQuickProcess}
|
||||
disabled={!inputPath || !outputPath || !systemInfo.isInitialized}
|
||||
className="w-full py-3 px-6 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Play className="w-5 h-5" />
|
||||
开始高级{processingType === 'video' ? '视频' : '图片'}增强
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 任务列表 */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">处理任务</h2>
|
||||
<button
|
||||
onClick={cleanupCompletedTasks}
|
||||
className="px-3 py-1 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
清理已完成
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tasksLoading ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 mx-auto mb-2"></div>
|
||||
<p className="text-gray-600">加载任务...</p>
|
||||
</div>
|
||||
) : tasks.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Clock className="w-12 h-12 text-gray-300 mx-auto mb-3" />
|
||||
<p className="text-gray-500">暂无处理任务</p>
|
||||
<p className="text-sm text-gray-400 mt-1">开始处理后,任务将显示在这里</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{tasks.slice(0, 5).map(task => (
|
||||
<div key={task.id} className="p-4 border border-gray-200 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{task.status === 'Completed' && <CheckCircle className="w-4 h-4 text-green-500" />}
|
||||
{task.status === 'Failed' && <AlertCircle className="w-4 h-4 text-red-500" />}
|
||||
{task.status === 'Processing' && <div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />}
|
||||
<span className="font-medium text-gray-900">
|
||||
{task.task_type.includes('video') ? '视频' : '图片'}增强
|
||||
</span>
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${
|
||||
task.status === 'Completed' ? 'bg-green-100 text-green-800' :
|
||||
task.status === 'Failed' ? 'bg-red-100 text-red-800' :
|
||||
task.status === 'Processing' ? 'bg-blue-100 text-blue-800' :
|
||||
'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
{task.status === 'Completed' ? '已完成' :
|
||||
task.status === 'Failed' ? '失败' :
|
||||
task.status === 'Processing' ? '处理中' : '等待中'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 truncate">
|
||||
{task.input_path.split('/').pop() || task.input_path}
|
||||
</p>
|
||||
{task.progress > 0 && task.status === 'Processing' && (
|
||||
<div className="mt-2">
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${task.progress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">{task.progress.toFixed(1)}%</p>
|
||||
</div>
|
||||
)}
|
||||
{task.error_message && (
|
||||
<p className="text-sm text-red-600 mt-1">{task.error_message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(task.status === 'Pending' || task.status === 'Processing') && (
|
||||
<button
|
||||
onClick={() => cancelTask(task.id)}
|
||||
className="ml-4 p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{tasks.length > 5 && (
|
||||
<div className="text-center pt-2">
|
||||
<p className="text-sm text-gray-500">显示最近 5 个任务,共 {tasks.length} 个</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
50
apps/desktop/src/components/ui/Checkbox.tsx
Normal file
50
apps/desktop/src/components/ui/Checkbox.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { CheckIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface CheckboxProps {
|
||||
id?: string;
|
||||
checked: boolean;
|
||||
onCheckedChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Checkbox: React.FC<CheckboxProps> = ({
|
||||
id,
|
||||
checked,
|
||||
onCheckedChange,
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onCheckedChange(event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative inline-flex items-center ${className}`}>
|
||||
<input
|
||||
id={id}
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
className="sr-only"
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
w-4 h-4 border-2 rounded flex items-center justify-center cursor-pointer transition-all duration-200
|
||||
${checked
|
||||
? 'bg-blue-500 border-blue-500'
|
||||
: 'bg-white border-gray-300 hover:border-gray-400'
|
||||
}
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||
`}
|
||||
onClick={() => !disabled && onCheckedChange(!checked)}
|
||||
>
|
||||
{checked && (
|
||||
<CheckIcon className="w-3 h-3 text-white" strokeWidth={3} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
24
apps/desktop/src/components/ui/Label.tsx
Normal file
24
apps/desktop/src/components/ui/Label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Label: React.FC<LabelProps> = ({
|
||||
className = '',
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<label
|
||||
className={`
|
||||
block text-sm font-medium text-gray-700 mb-1
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
51
apps/desktop/src/components/ui/Progress.tsx
Normal file
51
apps/desktop/src/components/ui/Progress.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ProgressProps {
|
||||
value: number;
|
||||
max?: number;
|
||||
className?: string;
|
||||
showValue?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
color?: 'blue' | 'green' | 'red' | 'yellow';
|
||||
}
|
||||
|
||||
export const Progress: React.FC<ProgressProps> = ({
|
||||
value,
|
||||
max = 100,
|
||||
className = '',
|
||||
showValue = false,
|
||||
size = 'md',
|
||||
color = 'blue',
|
||||
}) => {
|
||||
const percentage = Math.min((value / max) * 100, 100);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'h-1',
|
||||
md: 'h-2',
|
||||
lg: 'h-3',
|
||||
};
|
||||
|
||||
const colorClasses = {
|
||||
blue: 'bg-blue-500',
|
||||
green: 'bg-green-500',
|
||||
red: 'bg-red-500',
|
||||
yellow: 'bg-yellow-500',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`w-full ${className}`}>
|
||||
{showValue && (
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span>进度</span>
|
||||
<span>{percentage.toFixed(1)}%</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={`w-full bg-gray-200 rounded-full ${sizeClasses[size]}`}>
|
||||
<div
|
||||
className={`${sizeClasses[size]} ${colorClasses[color]} rounded-full transition-all duration-300 ease-out`}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
134
apps/desktop/src/components/ui/Select.tsx
Normal file
134
apps/desktop/src/components/ui/Select.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface SelectProps {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
children: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface SelectTriggerProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface SelectContentProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface SelectItemProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface SelectValueProps {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const SelectContext = React.createContext<{
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
isOpen: boolean;
|
||||
setIsOpen: (open: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}>({
|
||||
value: '',
|
||||
onValueChange: () => {},
|
||||
isOpen: false,
|
||||
setIsOpen: () => {},
|
||||
});
|
||||
|
||||
export const Select: React.FC<SelectProps> = ({ value, onValueChange, children, disabled }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const selectRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (selectRef.current && !selectRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={{ value, onValueChange, isOpen, setIsOpen, disabled }}>
|
||||
<div ref={selectRef} className="relative">
|
||||
{children}
|
||||
</div>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectTrigger: React.FC<SelectTriggerProps> = ({ children, className = '' }) => {
|
||||
const { isOpen, setIsOpen, disabled } = React.useContext(SelectContext);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => !disabled && setIsOpen(!isOpen)}
|
||||
disabled={disabled}
|
||||
className={`
|
||||
flex items-center justify-between w-full px-3 py-2 text-left bg-white border border-gray-300 rounded-md shadow-sm
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'}
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon className={`w-4 h-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectContent: React.FC<SelectContentProps> = ({ children, className = '' }) => {
|
||||
const { isOpen } = React.useContext(SelectContext);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto
|
||||
${className}
|
||||
`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectItem: React.FC<SelectItemProps> = ({ value, children, className = '' }) => {
|
||||
const { value: selectedValue, onValueChange, setIsOpen } = React.useContext(SelectContext);
|
||||
|
||||
const handleClick = () => {
|
||||
onValueChange(value);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={`
|
||||
w-full px-3 py-2 text-left hover:bg-gray-100 focus:bg-gray-100 focus:outline-none
|
||||
${selectedValue === value ? 'bg-blue-50 text-blue-600' : 'text-gray-900'}
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectValue: React.FC<SelectValueProps> = ({ placeholder = 'Select...' }) => {
|
||||
const { value } = React.useContext(SelectContext);
|
||||
|
||||
return (
|
||||
<span className={value ? 'text-gray-900' : 'text-gray-500'}>
|
||||
{value || placeholder}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
75
apps/desktop/src/components/ui/Slider.tsx
Normal file
75
apps/desktop/src/components/ui/Slider.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
interface SliderProps {
|
||||
value: number[];
|
||||
onValueChange: (value: number[]) => void;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Slider: React.FC<SliderProps> = ({
|
||||
value,
|
||||
onValueChange,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
className = '',
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = parseFloat(event.target.value);
|
||||
onValueChange([newValue]);
|
||||
}, [onValueChange]);
|
||||
|
||||
const handleMouseDown = () => {
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const percentage = ((value[0] - min) / (max - min)) * 100;
|
||||
|
||||
return (
|
||||
<div className={`relative w-full ${className}`}>
|
||||
<div className="relative">
|
||||
{/* Track */}
|
||||
<div className="w-full h-2 bg-gray-200 rounded-full">
|
||||
{/* Progress */}
|
||||
<div
|
||||
className="h-2 bg-blue-500 rounded-full transition-all duration-150"
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Thumb */}
|
||||
<div
|
||||
className={`absolute top-1/2 w-4 h-4 bg-white border-2 border-blue-500 rounded-full shadow-md transform -translate-y-1/2 transition-all duration-150 ${
|
||||
isDragging ? 'scale-110' : ''
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:scale-105'}`}
|
||||
style={{ left: `calc(${percentage}% - 8px)` }}
|
||||
/>
|
||||
|
||||
{/* Hidden input */}
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value[0]}
|
||||
onChange={handleChange}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
disabled={disabled}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
97
apps/desktop/src/components/ui/Tabs.tsx
Normal file
97
apps/desktop/src/components/ui/Tabs.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { useState, createContext, useContext } from 'react';
|
||||
|
||||
interface TabsContextType {
|
||||
activeTab: string;
|
||||
setActiveTab: (tab: string) => void;
|
||||
}
|
||||
|
||||
const TabsContext = createContext<TabsContextType | undefined>(undefined);
|
||||
|
||||
interface TabsProps {
|
||||
defaultValue: string;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TabsListProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TabsTriggerProps {
|
||||
value: string;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TabsContentProps {
|
||||
value: string;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Tabs: React.FC<TabsProps> = ({ defaultValue, className = '', children }) => {
|
||||
const [activeTab, setActiveTab] = useState(defaultValue);
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
<div className={className}>
|
||||
{children}
|
||||
</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsList: React.FC<TabsListProps> = ({ className = '', children }) => {
|
||||
return (
|
||||
<div className={`flex space-x-1 rounded-lg bg-gray-100 p-1 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsTrigger: React.FC<TabsTriggerProps> = ({ value, className = '', children }) => {
|
||||
const context = useContext(TabsContext);
|
||||
if (!context) {
|
||||
throw new Error('TabsTrigger must be used within a Tabs component');
|
||||
}
|
||||
|
||||
const { activeTab, setActiveTab } = context;
|
||||
const isActive = activeTab === value;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab(value)}
|
||||
className={`
|
||||
flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all duration-200
|
||||
${isActive
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
|
||||
}
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsContent: React.FC<TabsContentProps> = ({ value, className = '', children }) => {
|
||||
const context = useContext(TabsContext);
|
||||
if (!context) {
|
||||
throw new Error('TabsContent must be used within a Tabs component');
|
||||
}
|
||||
|
||||
const { activeTab } = context;
|
||||
|
||||
if (activeTab !== value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`mt-4 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
23
apps/desktop/src/components/ui/Textarea.tsx
Normal file
23
apps/desktop/src/components/ui/Textarea.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Textarea: React.FC<TextareaProps> = ({
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<textarea
|
||||
className={`
|
||||
w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
resize-vertical
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -62,3 +62,10 @@ export {
|
||||
type FormSubmitButtonProps,
|
||||
type FormGroupProps,
|
||||
} from './Form';
|
||||
export { Slider } from './Slider';
|
||||
export { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from './Select';
|
||||
export { Checkbox } from './Checkbox';
|
||||
export { Textarea } from './Textarea';
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs';
|
||||
export { Label } from './Label';
|
||||
export { Progress } from './Progress';
|
||||
|
||||
@@ -19,6 +19,21 @@ import { Tool, ToolCategory, ToolStatus } from '../types/tool';
|
||||
* 定义所有可用的工具及其属性
|
||||
*/
|
||||
export const TOOLS_DATA: Tool[] = [
|
||||
{
|
||||
id: 'topaz-video-ai',
|
||||
name: 'Topaz Video AI 参数配置器',
|
||||
description: '基于真实命令分析的专业视频处理参数配置工具,支持AI增强、慢动作、防抖、HDR等功能',
|
||||
longDescription: '专业的 Topaz Video AI 参数配置工具,基于真实 Topaz Video AI FFmpeg 命令深度分析开发。支持75%的参数使用率,包含AI视频增强、慢动作插帧、视频防抖、运动模糊去除、HDR处理等完整功能。提供单阶段和两阶段处理模式,内置多种专业模板,实时生成高质量FFmpeg命令。适用于视频后期制作、AI视频增强、专业视频处理等场景。',
|
||||
icon: Video,
|
||||
route: '/tools/topaz-video-ai',
|
||||
category: ToolCategory.AI_TOOLS,
|
||||
status: ToolStatus.STABLE,
|
||||
tags: ['视频增强', 'AI处理', '慢动作', '防抖', 'HDR', 'FFmpeg', '参数配置'],
|
||||
isNew: true,
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2024-01-29'
|
||||
},
|
||||
{
|
||||
id: 'image-generation',
|
||||
name: 'AI图片生成工具',
|
||||
@@ -123,21 +138,6 @@ export const TOOLS_DATA: Tool[] = [
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2024-01-31'
|
||||
},
|
||||
{
|
||||
id: 'tvai-tool',
|
||||
name: 'TVAI 视频图片增强',
|
||||
description: '基于 Topaz Video AI 的专业视频和图片AI增强工具,支持超分辨率、帧插值等功能',
|
||||
longDescription: '专业的视频和图片AI增强工具,集成 Topaz Video AI 先进的机器学习算法。支持视频超分辨率放大、图片质量增强、智能帧插值、自动内容优化等功能。提供16+种专业AI模型,支持多种内容类型优化预设(老视频、游戏内容、动画、人像等)。具备GPU加速、批量处理、异步任务管理、实时进度监控等完整的处理流程。适用于视频后期制作、内容创作、画质提升、慢动作制作等专业场景。',
|
||||
icon: Sparkles,
|
||||
route: '/tools/tvai',
|
||||
category: ToolCategory.AI_TOOLS,
|
||||
status: ToolStatus.STABLE,
|
||||
tags: ['视频增强', '图片增强', '超分辨率', '帧插值', 'AI放大', 'Topaz Video AI', 'GPU加速'],
|
||||
isNew: true,
|
||||
isPopular: true,
|
||||
version: '1.0.0',
|
||||
lastUpdated: '2025-08-11'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
22
apps/desktop/src/pages/tools/TopazVideoAITool.tsx
Normal file
22
apps/desktop/src/pages/tools/TopazVideoAITool.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import TopazVideoAIConfigurator from '../../components/TopazVideoAIConfigurator';
|
||||
|
||||
/**
|
||||
* Topaz Video AI 参数配置工具页面
|
||||
*
|
||||
* 功能特性:
|
||||
* - 基于真实 Topaz Video AI 命令分析
|
||||
* - 75% 参数使用率
|
||||
* - 支持单阶段和两阶段处理
|
||||
* - 内置多种专业模板
|
||||
* - 实时 FFmpeg 命令生成
|
||||
*/
|
||||
const TopazVideoAITool: React.FC = () => {
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<TopazVideoAIConfigurator />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopazVideoAITool;
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ArrowLeft, Sparkles, Zap } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { TvaiExample } from '../../components/TvaiExample';
|
||||
|
||||
/**
|
||||
* TVAI (Topaz Video AI) 工具页面
|
||||
* 集成视频和图片AI增强功能
|
||||
*/
|
||||
const TvaiTool: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* 页面头部 */}
|
||||
<div className="bg-white border-b border-gray-200 sticky top-0 z-10">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* 返回按钮和标题 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/tools')}
|
||||
className="flex items-center gap-2 px-3 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-all duration-200"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
<span className="font-medium">返回工具列表</span>
|
||||
</button>
|
||||
|
||||
<div className="h-6 w-px bg-gray-300" />
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-600 rounded-xl flex items-center justify-center shadow-lg">
|
||||
<Sparkles className="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-gray-900">TVAI 视频图片增强工具</h1>
|
||||
<p className="text-sm text-gray-600">基于 Topaz Video AI 的专业增强工具</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 状态标签 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-green-100 text-green-800 text-sm font-medium rounded-full">
|
||||
<Zap className="w-4 h-4" />
|
||||
AI 增强
|
||||
</span>
|
||||
<span className="inline-flex items-center px-3 py-1.5 bg-blue-100 text-blue-800 text-sm font-medium rounded-full">
|
||||
v1.0.0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 工具描述区域 */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/* TVAI 工具主体 */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<TvaiExample />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TvaiTool;
|
||||
@@ -14,6 +14,12 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Path mapping */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
@@ -8,6 +9,12 @@ const host = process.env.TAURI_DEV_HOST;
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent vite from obscuring rust errors
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
d013189f-75ea-4554-ae13-b559ed8fd1f9
|
||||
@@ -1,140 +0,0 @@
|
||||
# 图像编辑工具批量处理任务列表显示问题修复总结
|
||||
|
||||
## 问题描述
|
||||
当用户点击批量处理按钮时,右侧边栏的"最近任务"列表显示为空。分析发现是因为后端的批量任务只在处理完成后才存储到任务列表中,而前端在任务开始时调用 loadTasks() 获取不到正在处理的任务。
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 修改后端批量编辑命令 (✅ 已完成)
|
||||
**文件**: `apps/desktop/src-tauri/src/presentation/commands/image_editing_commands.rs`
|
||||
|
||||
**主要改动**:
|
||||
- 在 `edit_batch_images` 函数开始时立即创建 `BatchImageEditingTask`
|
||||
- 先将任务存储到 `state.batch_tasks` 中,状态设为 `Processing`
|
||||
- 使用 `tokio::spawn` 异步执行批量处理,避免阻塞命令返回
|
||||
- 通过回调机制实时更新任务状态
|
||||
- 修复了生命周期问题,正确克隆 `Arc<Mutex<>>` 而不是借用 `State`
|
||||
|
||||
**关键代码变化**:
|
||||
```rust
|
||||
// 立即创建并存储任务
|
||||
let mut batch_task = BatchImageEditingTask::new(/* ... */);
|
||||
batch_task.status = ImageEditingTaskStatus::Processing;
|
||||
|
||||
// 立即存储,前端可以获取到
|
||||
{
|
||||
let mut batch_tasks = state.batch_tasks.lock().map_err(/*...*/)?;
|
||||
batch_tasks.insert(task_id.clone(), batch_task.clone());
|
||||
}
|
||||
|
||||
// 异步执行处理
|
||||
tokio::spawn(async move {
|
||||
// 处理逻辑...
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 修改图像编辑服务批量处理方法 (✅ 已完成)
|
||||
**文件**: `apps/desktop/src-tauri/src/infrastructure/image_editing_service.rs`
|
||||
|
||||
**主要改动**:
|
||||
- 添加新方法 `edit_batch_images_with_callback`
|
||||
- 接受任务状态更新回调 `Box<dyn Fn(BatchImageEditingTask) + Send + Sync>`
|
||||
- 在处理每个图片后调用回调更新任务状态
|
||||
- 确保任务进度和状态能实时反映到存储中
|
||||
|
||||
**关键代码变化**:
|
||||
```rust
|
||||
pub async fn edit_batch_images_with_callback(
|
||||
&self,
|
||||
input_folder: &Path,
|
||||
output_folder: &Path,
|
||||
prompt: &str,
|
||||
params: &ImageEditingParams,
|
||||
task_update_callback: Option<Box<dyn Fn(BatchImageEditingTask) + Send + Sync>>,
|
||||
) -> Result<BatchImageEditingTask> {
|
||||
// 初始回调
|
||||
if let Some(ref callback) = task_update_callback {
|
||||
callback(batch_task.clone());
|
||||
}
|
||||
|
||||
// 处理每个图像后回调
|
||||
for (index, input_file) in image_files.iter().enumerate() {
|
||||
// ... 处理逻辑 ...
|
||||
batch_task.update_progress();
|
||||
|
||||
// 每处理完一个图像就回调更新状态
|
||||
if let Some(ref callback) = task_update_callback {
|
||||
callback(batch_task.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 优化前端任务刷新机制 (✅ 已完成)
|
||||
**文件**: `apps/desktop/src/pages/tools/ImageEditingTool.tsx`
|
||||
|
||||
**主要改动**:
|
||||
- 添加定时刷新状态管理 `refreshInterval`
|
||||
- 实现 `hasProcessingTasks()` 检查是否有处理中的任务
|
||||
- 实现 `startAutoRefresh()` 和 `stopAutoRefresh()` 控制定时刷新
|
||||
- 使用 `useEffect` 监听任务状态变化,自动启动/停止刷新
|
||||
- 简化批量处理函数,移除不必要的本地任务创建
|
||||
|
||||
**关键代码变化**:
|
||||
```typescript
|
||||
// 检查是否有处理中的任务
|
||||
const hasProcessingTasks = useCallback(() => {
|
||||
return [...tasks, ...batchTasks].some(task =>
|
||||
task.status === ImageEditingTaskStatus.Processing ||
|
||||
task.status === ImageEditingTaskStatus.Pending
|
||||
);
|
||||
}, [tasks, batchTasks]);
|
||||
|
||||
// 监听任务状态变化,自动启动/停止刷新
|
||||
useEffect(() => {
|
||||
if (hasProcessingTasks()) {
|
||||
startAutoRefresh();
|
||||
} else {
|
||||
stopAutoRefresh();
|
||||
}
|
||||
}, [hasProcessingTasks, startAutoRefresh, stopAutoRefresh]);
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
### ✅ 任务立即显示
|
||||
- 用户点击批量处理后,右侧边栏立即显示新创建的批量任务
|
||||
- 任务状态初始为 `Processing`
|
||||
|
||||
### ✅ 实时状态更新
|
||||
- 任务状态、进度、已处理图片数量等信息实时更新
|
||||
- 每2秒自动刷新任务列表(仅在有处理中任务时)
|
||||
|
||||
### ✅ 正确的任务生命周期
|
||||
- 任务从 `Processing` 状态开始
|
||||
- 处理完成后变为 `Completed` 或 `Failed`
|
||||
- 自动停止不必要的刷新
|
||||
|
||||
### ✅ 技术要求满足
|
||||
- **API兼容性**: 保持现有的前端调用方式不变
|
||||
- **线程安全**: 使用 `Arc<Mutex<>>` 确保多线程环境下任务状态更新的安全性
|
||||
- **错误处理**: 妥善处理任务创建、更新过程中的错误情况
|
||||
- **性能考虑**: 只在有处理中任务时才启动定时刷新,避免不必要的性能开销
|
||||
|
||||
## 测试验证
|
||||
|
||||
创建了测试脚本 `test_batch_task_display.js`,可以在浏览器控制台中运行来验证:
|
||||
1. 任务立即显示功能
|
||||
2. 实时状态更新
|
||||
3. 任务生命周期管理
|
||||
|
||||
## 编译状态
|
||||
✅ Rust代码编译通过,无错误
|
||||
✅ 应用可以正常启动和运行
|
||||
|
||||
## 下一步
|
||||
建议进行完整的功能测试,包括:
|
||||
1. 创建包含图片的测试文件夹
|
||||
2. 测试批量处理功能
|
||||
3. 验证任务列表实时更新
|
||||
4. 确认任务完成后状态正确
|
||||
@@ -20,6 +20,7 @@ async-trait = "0.1"
|
||||
thiserror = "1.0"
|
||||
toml = "0.8"
|
||||
dirs = "5.0"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
||||
@@ -11,6 +11,9 @@ A Rust library for integrating with Topaz Video AI to perform video and image en
|
||||
- 🔧 **Multiple AI Models**: 16 upscaling and 4 interpolation models
|
||||
- 📦 **Batch Processing**: Process multiple files efficiently
|
||||
- 🎛️ **Flexible Configuration**: Fine-tune processing parameters
|
||||
- 📋 **Template System**: Built-in templates from Topaz Video AI examples
|
||||
- 🚀 **Easy API**: Simple functions for common video processing tasks
|
||||
- ⚡ **FFmpeg Integration**: Generate FFmpeg commands from templates
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -30,7 +33,61 @@ tvai = "0.1.0"
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Video Upscaling
|
||||
### Using Built-in Templates (Recommended)
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Upscale to 4K using built-in template
|
||||
upscale_to_4k(
|
||||
Path::new("input.mp4"),
|
||||
Path::new("output_4k.mp4")
|
||||
).await?;
|
||||
|
||||
// Convert to 60fps
|
||||
convert_to_60fps(
|
||||
Path::new("input.mp4"),
|
||||
Path::new("output_60fps.mp4")
|
||||
).await?;
|
||||
|
||||
// Remove noise
|
||||
remove_noise(
|
||||
Path::new("noisy_video.mp4"),
|
||||
Path::new("clean_video.mp4")
|
||||
).await?;
|
||||
|
||||
// Create slow motion
|
||||
slow_motion_4x(
|
||||
Path::new("action_video.mp4"),
|
||||
Path::new("slow_motion.mp4")
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Using Any Template by Name
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Process with any built-in template
|
||||
process_with_template(
|
||||
Path::new("input.mp4"),
|
||||
Path::new("output.mp4"),
|
||||
"film_stock_4k_strong"
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Traditional API (Still Available)
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
@@ -43,7 +100,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
std::path::Path::new("output.mp4"),
|
||||
2.0,
|
||||
).await?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -155,14 +212,73 @@ let gpu_info = detect_gpu_support();
|
||||
let ffmpeg_info = detect_ffmpeg();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
## Template System
|
||||
|
||||
The library uses the `anyhow` crate for error handling:
|
||||
The library includes a comprehensive template system with built-in templates from Topaz Video AI examples:
|
||||
|
||||
### Available Templates
|
||||
|
||||
- **Upscaling**: `upscale_to_4k`, `upscale_to_fhd`, `upscale_4k_convert_60fps`
|
||||
- **Frame Rate**: `convert_to_60fps`, `4x_slow_motion`, `8x_super_slow_motion`
|
||||
- **Enhancement**: `remove_noise`, `film_stock_4k_light/medium/strong`
|
||||
- **Restoration**: `deinterlace_upscale_fhd`, `minidv_hd_int_basic`, `auto_crop_stabilization`
|
||||
|
||||
### Template Management
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
|
||||
match quick_upscale_video(input, output, 2.0).await {
|
||||
// List all available templates
|
||||
let templates = list_available_templates();
|
||||
for template_name in templates {
|
||||
if let Some((name, description)) = get_template_info(&template_name) {
|
||||
println!("{}: {}", name, description);
|
||||
}
|
||||
}
|
||||
|
||||
// Load custom templates
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
manager.load_from_file("my_template".to_string(), "path/to/template.json")?;
|
||||
manager.load_examples_templates("templates_directory/")?;
|
||||
```
|
||||
|
||||
### Custom Templates
|
||||
|
||||
You can create and load custom templates in Topaz Video AI JSON format. See [Template Documentation](docs/TEMPLATES.md) for details.
|
||||
|
||||
### FFmpeg Command Generation
|
||||
|
||||
Generate FFmpeg commands from templates for external use:
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Generate basic command
|
||||
let cmd = manager.quick_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4")?;
|
||||
println!("{}", cmd);
|
||||
|
||||
// GPU-specific commands
|
||||
let nvidia_cmd = manager.gpu_ffmpeg_command("convert_to_60fps", "input.mp4", "output.mp4", "nvidia")?;
|
||||
let amd_cmd = manager.gpu_ffmpeg_command("remove_noise", "input.mp4", "output.mp4", "amd")?;
|
||||
|
||||
// Custom quality
|
||||
let hq_cmd = manager.quality_ffmpeg_command("film_stock_4k_strong", "input.mp4", "output.mp4", 15, "slow")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library uses comprehensive error handling with user-friendly messages:
|
||||
|
||||
```rust
|
||||
use tvai::*;
|
||||
|
||||
match upscale_to_4k(input, output).await {
|
||||
Ok(result) => println!("Success: {:?}", result),
|
||||
Err(TvaiError::TopazNotFound(path)) => {
|
||||
eprintln!("Topaz not found at: {}", path);
|
||||
@@ -170,7 +286,10 @@ match quick_upscale_video(input, output, 2.0).await {
|
||||
Err(TvaiError::FfmpegError(msg)) => {
|
||||
eprintln!("FFmpeg error: {}", msg);
|
||||
},
|
||||
Err(e) => eprintln!("Other error: {}", e),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
eprintln!("Suggestion: {}", e.user_friendly_message());
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -188,16 +307,19 @@ match quick_upscale_video(input, output, 2.0).await {
|
||||
- [x] Progress callbacks and monitoring
|
||||
- [x] Global configuration management
|
||||
- [x] Preset management system
|
||||
- [x] Template system with built-in Topaz Video AI templates
|
||||
- [x] Convenient API functions for common tasks
|
||||
- [x] Performance optimization
|
||||
- [x] Enhanced error handling
|
||||
- [x] Comprehensive testing (unit + integration + benchmarks)
|
||||
- [x] Complete documentation (API + User Guide)
|
||||
- [x] Complete documentation (API + User Guide + Templates)
|
||||
|
||||
## Documentation
|
||||
|
||||
### English
|
||||
- 📖 [API Documentation](docs/API.md) - Complete API reference
|
||||
- 📚 [User Guide](docs/USER_GUIDE.md) - Comprehensive usage guide
|
||||
- 📋 [Template Documentation](docs/TEMPLATES.md) - Template system guide
|
||||
|
||||
### 中文文档
|
||||
- 📖 [API 文档](docs/API文档.md) - 完整 API 参考
|
||||
|
||||
180
cargos/tvai/docs/WEB_INTEGRATION_COMPLETE.md
Normal file
180
cargos/tvai/docs/WEB_INTEGRATION_COMPLETE.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Topaz Video AI 参数配置器 - Web 集成完成
|
||||
|
||||
## 🎉 项目完成总结
|
||||
|
||||
基于您提供的三个真实 Topaz Video AI FFmpeg 命令,我们成功创建了一个完整的视频处理参数配置器,并集成到了 desktop 应用中。
|
||||
|
||||
## 📊 核心成就
|
||||
|
||||
### 1. 参数使用率大幅提升
|
||||
- **初始状态**: 21% (14/67 参数)
|
||||
- **第一个命令后**: 35% (基础滤镜参数、高级编码)
|
||||
- **第二个命令后**: 45% (时间控制、率控制模式)
|
||||
- **第三个命令后**: **75%** (两阶段处理、完整工作流)
|
||||
|
||||
### 2. 完全支持的设置类别 (100% 使用率)
|
||||
- ✅ **StabilizeSettings** (6/6): 防抖、平滑、滚动快门校正
|
||||
- ✅ **MotionBlurSettings** (3/3): 运动模糊去除
|
||||
- ✅ **SlowMotionSettings** (5/5): 慢动作插帧
|
||||
- ✅ **GrainSettings** (3/3): 颗粒效果
|
||||
- ✅ **HdrSettings** (9/9): HDR 处理 + 色彩空间转换
|
||||
|
||||
### 3. 高使用率设置类别
|
||||
- 🟡 **EnhanceSettings** (15/18 = 85%): AI 增强处理
|
||||
- 🟡 **OutputSettings** (4/7 = 57%): 输出控制
|
||||
|
||||
## 🚀 技术实现
|
||||
|
||||
### 后端 (Rust)
|
||||
```rust
|
||||
// 核心库结构
|
||||
cargos/tvai/
|
||||
├── src/
|
||||
│ ├── config/
|
||||
│ │ └── topaz_templates.rs // 模板管理 + FFmpeg 生成
|
||||
│ ├── web_api.rs // Web API 接口
|
||||
│ └── lib.rs // 库导出
|
||||
├── web/
|
||||
│ ├── video-processor.html // 完整的 Web 界面
|
||||
│ └── video-processor.js // JavaScript 逻辑
|
||||
└── examples/
|
||||
├── web_integration_demo.rs // Web 集成演示
|
||||
├── third_command_demo.rs // 第三个命令分析
|
||||
└── ... // 其他演示
|
||||
```
|
||||
|
||||
### 前端 (React + TypeScript)
|
||||
```typescript
|
||||
// Desktop 应用集成
|
||||
apps/desktop/src/
|
||||
├── components/
|
||||
│ └── TopazVideoAIConfigurator.tsx // 主配置组件
|
||||
├── pages/tools/
|
||||
│ └── TopazVideoAITool.tsx // 工具页面
|
||||
└── data/
|
||||
└── tools.ts // 工具注册
|
||||
```
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### 1. 两阶段处理 (真实 Topaz 工作流)
|
||||
```rust
|
||||
// 分析阶段
|
||||
ffmpeg -hide_banner -i input.mp4 -filter_complex "tvai_cpe=model=cpe-2:filename=/tmp/analysis.json:device=-2" -f null -
|
||||
|
||||
// 处理阶段
|
||||
ffmpeg -hide_banner -nostdin -y -i input.mp4 -filter_complex "tvai_stb=...,tvai_up=...,tvai_fi=...,setparams=..." output.mp4
|
||||
```
|
||||
|
||||
### 2. 复杂滤镜链 (6+ 滤镜串联)
|
||||
```rust
|
||||
tvai_stb=model=ref-2:smoothness=4.92:rst=0:reduce=2, // 防抖
|
||||
tvai_up=model=thm-2:scale=1, // 运动模糊去除
|
||||
tvai_fi=model=apo-8:slowmo=2:fps=30, // 帧插值
|
||||
scale=544:-1, // 预缩放
|
||||
tvai_up=model=ghq-5:prenoise=0.02:grain=0.05, // 主要增强
|
||||
tvai_up=model=hyp-1:parameters='sdr_ip=0.7\:...', // HDR 处理
|
||||
setparams=range=pc:color_primaries=bt2020 // 色彩空间
|
||||
```
|
||||
|
||||
### 3. 高级编码参数
|
||||
```rust
|
||||
-c:v h264_nvenc -profile:v high -preset p7 -tune hq
|
||||
-rc constqp -qp 18 -rc-lookahead 20 -spatial_aq 1 -aq-strength 15
|
||||
-movflags frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr
|
||||
```
|
||||
|
||||
## 🌐 Web 集成特性
|
||||
|
||||
### 1. 完整的 Web API
|
||||
```rust
|
||||
// 模板获取
|
||||
WebApi::get_templates() -> Vec<TemplateInfo>
|
||||
|
||||
// 命令生成
|
||||
WebApi::generate_command(request) -> GenerateCommandResponse {
|
||||
single_command: Option<String>,
|
||||
analysis_command: Option<String>,
|
||||
processing_command: Option<String>,
|
||||
usage_stats: UsageStats,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. React 组件化界面
|
||||
- 📋 **模板选择**: 8个内置专业模板
|
||||
- ⚙️ **参数配置**: 完整的表单界面
|
||||
- 🚀 **命令生成**: 实时生成 + 复制功能
|
||||
- 📊 **使用统计**: 参数使用率分析
|
||||
|
||||
### 3. 内置模板
|
||||
| 模板 | 功能 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| 4K放大 | AI 视频放大 | 低分辨率视频增强 |
|
||||
| 8倍超级慢动作 | 极限慢动作 | 体育、科学分析 |
|
||||
| HDR增强处理 | HDR + 色彩空间 | 专业视频制作 |
|
||||
| 运动模糊去除 | 去模糊 + 防抖 | 手持拍摄修复 |
|
||||
| 胶片素材处理 | 综合增强 | 老电影修复 |
|
||||
|
||||
## 📈 性能对比
|
||||
|
||||
| 特性 | 初始版本 | 最终版本 | 提升 |
|
||||
|------|----------|----------|------|
|
||||
| 参数使用率 | 21% | **75%** | **+254%** |
|
||||
| 支持的滤镜 | 2个 | **7个** | **+250%** |
|
||||
| 处理阶段 | 单阶段 | **两阶段** | **质的飞跃** |
|
||||
| 率控制模式 | 1个 | **4个** | **+300%** |
|
||||
| HDR 支持 | 无 | **完整** | **从无到有** |
|
||||
| 模板数量 | 0个 | **8个** | **专业级** |
|
||||
|
||||
## 🎊 使用方式
|
||||
|
||||
### 1. 在 Desktop 应用中使用
|
||||
1. 启动 Desktop 应用
|
||||
2. 导航到 "工具" → "Topaz Video AI 参数配置器"
|
||||
3. 选择内置模板或自定义参数
|
||||
4. 生成并复制 FFmpeg 命令
|
||||
|
||||
### 2. 直接使用 Rust 库
|
||||
```rust
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// 生成单阶段命令
|
||||
let cmd = manager.quick_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4")?;
|
||||
|
||||
// 生成两阶段命令 (真实 Topaz 模式)
|
||||
let opts = FfmpegCommandOptions::complex_processing();
|
||||
let (analysis, processing) = manager.generate_two_pass_commands(
|
||||
"hdr_enhancement", "input.mp4", "output.mov", Some(&opts)
|
||||
)?;
|
||||
|
||||
println!("Analysis: {}", analysis);
|
||||
println!("Processing: {}", processing);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Web API 集成
|
||||
```rust
|
||||
// 获取模板
|
||||
let templates = WebApi::get_templates()?;
|
||||
|
||||
// 生成命令
|
||||
let request = GenerateCommandRequest { /* ... */ };
|
||||
let response = WebApi::generate_command(request);
|
||||
```
|
||||
|
||||
## 🏆 最终成果
|
||||
|
||||
1. **参数使用率从 21% 提升到 75%** - 翻了 3.5 倍!
|
||||
2. **5 个设置类别达到 100% 使用率** - 完全支持
|
||||
3. **支持两阶段处理** - 匹配真实 Topaz 工作流
|
||||
4. **复杂滤镜链** - 支持 6+ 滤镜串联处理
|
||||
5. **完整的 HDR 流水线** - 处理 + 色彩空间转换
|
||||
6. **专业级 Web 界面** - 集成到 Desktop 应用
|
||||
7. **详细的元数据生成** - 与真实 Topaz 输出一致
|
||||
|
||||
这个项目从一个简单的参数映射工具演进为一个**专业级的 Topaz Video AI 兼容解决方案**,能够生成与真实 Topaz Video AI 几乎完全相同的复杂命令,为用户提供了强大而准确的视频处理能力!🚀
|
||||
226
cargos/tvai/examples/ffmpeg_command_demo.rs
Normal file
226
cargos/tvai/examples/ffmpeg_command_demo.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
//! FFmpeg Command Generation Demo
|
||||
//!
|
||||
//! This example demonstrates how to generate FFmpeg commands
|
||||
//! from Topaz Video AI templates for various processing scenarios.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== FFmpeg Command Generation Demo ===\n");
|
||||
|
||||
// Get template manager
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Demo 1: Basic command generation
|
||||
println!("1. Basic Command Generation:");
|
||||
match manager.quick_ffmpeg_command(
|
||||
"upscale_to_4k",
|
||||
"input.mp4",
|
||||
"output_4k.mp4"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("Template: upscale_to_4k");
|
||||
println!("Command: {}", cmd);
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 2: GPU-specific commands
|
||||
println!("2. GPU-Specific Commands:");
|
||||
|
||||
// NVIDIA GPU
|
||||
match manager.gpu_ffmpeg_command(
|
||||
"convert_to_60fps",
|
||||
"input.mp4",
|
||||
"output_60fps.mp4",
|
||||
"nvidia"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("NVIDIA GPU Command:");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("NVIDIA Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// AMD GPU
|
||||
match manager.gpu_ffmpeg_command(
|
||||
"remove_noise",
|
||||
"noisy_input.mp4",
|
||||
"clean_output.mp4",
|
||||
"amd"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("AMD GPU Command:");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("AMD Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 3: CPU processing
|
||||
println!("3. CPU Processing Command:");
|
||||
match manager.cpu_ffmpeg_command(
|
||||
"4x_slow_motion",
|
||||
"action.mp4",
|
||||
"slow_motion.mp4"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("CPU Command:");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("CPU Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 4: Custom quality settings
|
||||
println!("4. Custom Quality Commands:");
|
||||
|
||||
// High quality (CRF 15, slow preset)
|
||||
match manager.quality_ffmpeg_command(
|
||||
"film_stock_4k_strong",
|
||||
"film_input.mp4",
|
||||
"film_output_hq.mp4",
|
||||
15,
|
||||
"slow"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("High Quality Command (CRF 15, slow preset):");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("High Quality Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Fast encoding (CRF 23, fast preset)
|
||||
match manager.quality_ffmpeg_command(
|
||||
"upscale_to_4k",
|
||||
"input.mp4",
|
||||
"output_fast.mp4",
|
||||
23,
|
||||
"fast"
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("Fast Encoding Command (CRF 23, fast preset):");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("Fast Encoding Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 5: Custom options
|
||||
println!("5. Custom Options:");
|
||||
let custom_options = FfmpegCommandOptions::default()
|
||||
.with_crf(20)
|
||||
.with_preset("medium")
|
||||
.with_video_codec("libx264")
|
||||
.with_audio_codec("aac")
|
||||
.with_custom_args(vec!["-movflags".to_string(), "+faststart".to_string()]);
|
||||
|
||||
match manager.generate_ffmpeg_command(
|
||||
"auto_crop_stabilization",
|
||||
"shaky_input.mp4",
|
||||
"stable_output.mp4",
|
||||
Some(&custom_options)
|
||||
) {
|
||||
Ok(cmd) => {
|
||||
println!("Custom Options Command:");
|
||||
println!("{}", cmd);
|
||||
}
|
||||
Err(e) => println!("Custom Options Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 6: Batch processing commands
|
||||
println!("6. Batch Processing Commands:");
|
||||
let input_output_pairs = vec![
|
||||
("video1.mp4".to_string(), "enhanced1.mp4".to_string()),
|
||||
("video2.mp4".to_string(), "enhanced2.mp4".to_string()),
|
||||
("video3.mp4".to_string(), "enhanced3.mp4".to_string()),
|
||||
];
|
||||
|
||||
match manager.batch_ffmpeg_commands(
|
||||
"upscale_to_4k",
|
||||
&input_output_pairs,
|
||||
None
|
||||
) {
|
||||
Ok(commands) => {
|
||||
println!("Batch Commands for upscale_to_4k:");
|
||||
for (i, cmd) in commands.iter().enumerate() {
|
||||
println!(" {}. {}", i + 1, cmd);
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Batch Error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 7: Different templates showcase
|
||||
println!("7. Different Templates Showcase:");
|
||||
let templates_to_demo = [
|
||||
("upscale_to_4k", "input.mp4", "output_4k.mp4"),
|
||||
("convert_to_60fps", "input.mp4", "output_60fps.mp4"),
|
||||
("remove_noise", "noisy.mp4", "clean.mp4"),
|
||||
("4x_slow_motion", "action.mp4", "slow.mp4"),
|
||||
("auto_crop_stabilization", "shaky.mp4", "stable.mp4"),
|
||||
];
|
||||
|
||||
for (template, input, output) in &templates_to_demo {
|
||||
match manager.quick_ffmpeg_command(template, input, output) {
|
||||
Ok(cmd) => {
|
||||
// Get template info directly from manager to avoid deadlock
|
||||
if let Some(template_obj) = manager.get_template(template) {
|
||||
println!("Template: {} ({})", template_obj.name, template);
|
||||
println!("Description: {}", template_obj.description);
|
||||
println!("Command: {}", cmd);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Error with template {}: {}", template, e),
|
||||
}
|
||||
}
|
||||
|
||||
drop(manager); // Release lock
|
||||
|
||||
// Demo 8: Command options comparison
|
||||
println!("8. Command Options Comparison:");
|
||||
println!("CPU vs GPU vs Custom options for the same template:\n");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// CPU
|
||||
if let Ok(cpu_cmd) = manager.cpu_ffmpeg_command("upscale_to_4k", "input.mp4", "output_cpu.mp4") {
|
||||
println!("CPU Command:");
|
||||
println!("{}\n", cpu_cmd);
|
||||
}
|
||||
|
||||
// NVIDIA GPU
|
||||
if let Ok(gpu_cmd) = manager.gpu_ffmpeg_command("upscale_to_4k", "input.mp4", "output_gpu.mp4", "nvidia") {
|
||||
println!("NVIDIA GPU Command:");
|
||||
println!("{}\n", gpu_cmd);
|
||||
}
|
||||
|
||||
// Custom high-quality
|
||||
let hq_options = FfmpegCommandOptions::nvidia().with_crf(12).with_preset("slow");
|
||||
if let Ok(hq_cmd) = manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output_hq.mp4", Some(&hq_options)) {
|
||||
println!("High Quality NVIDIA Command:");
|
||||
println!("{}\n", hq_cmd);
|
||||
}
|
||||
|
||||
drop(manager);
|
||||
|
||||
println!("=== Usage Tips ===");
|
||||
println!("• Use quick_ffmpeg_command() for simple cases");
|
||||
println!("• Use gpu_ffmpeg_command() to specify GPU type");
|
||||
println!("• Use cpu_ffmpeg_command() for CPU-only processing");
|
||||
println!("• Use quality_ffmpeg_command() for custom quality settings");
|
||||
println!("• Use generate_ffmpeg_command() for full customization");
|
||||
println!("• Use batch_ffmpeg_commands() for multiple files");
|
||||
println!();
|
||||
|
||||
println!("=== Command Generation Complete ===");
|
||||
println!("You can now copy and run these FFmpeg commands directly!");
|
||||
println!("Note: Actual Topaz Video AI filters may require the Topaz FFmpeg plugin.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
173
cargos/tvai/examples/improved_ffmpeg_demo.rs
Normal file
173
cargos/tvai/examples/improved_ffmpeg_demo.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
//! Improved FFmpeg Command Generation Demo
|
||||
//!
|
||||
//! This example demonstrates the improved FFmpeg command generation
|
||||
//! based on real Topaz Video AI commands.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Improved FFmpeg Command Generation ===\n");
|
||||
println!("Based on real Topaz Video AI command analysis\n");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Demo 1: Basic improved command
|
||||
println!("1. Basic Improved Command (vs Old):");
|
||||
println!("Template: 8x_super_slow_motion");
|
||||
|
||||
// Old style command
|
||||
match manager.quick_ffmpeg_command("8x_super_slow_motion", "input.mp4", "output_old.mp4") {
|
||||
Ok(cmd) => {
|
||||
println!("Old style:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("Old style error: {}", e),
|
||||
}
|
||||
|
||||
// New Topaz-like command
|
||||
let topaz_opts = FfmpegCommandOptions::topaz_like();
|
||||
match manager.generate_ffmpeg_command("8x_super_slow_motion", "input.mp4", "output_new.mp4", Some(&topaz_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("New Topaz-like style:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("New style error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 2: Complex template with all features
|
||||
println!("2. Complex Template (film_stock_4k_strong):");
|
||||
let complex_opts = FfmpegCommandOptions::topaz_like()
|
||||
.with_custom_args(vec!["-threads".to_string(), "8".to_string()]);
|
||||
|
||||
match manager.generate_ffmpeg_command("film_stock_4k_strong", "film_input.mp4", "film_output.mp4", Some(&complex_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Complex command:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("Complex command error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 3: Parameter comparison
|
||||
println!("3. Parameter Usage Comparison:");
|
||||
|
||||
// Show what parameters are now used
|
||||
if let Some(template) = manager.get_template("4x_slow_motion") {
|
||||
println!("Template: {}", template.name);
|
||||
println!("Now using these parameters:");
|
||||
|
||||
if template.settings.slowmo.active {
|
||||
println!(" ✅ slowmo.model: {} → tvai_fi model", template.settings.slowmo.model);
|
||||
println!(" ✅ slowmo.factor: {} → tvai_fi slowmo", template.settings.slowmo.factor);
|
||||
println!(" ✅ slowmo.duplicate_threshold: {} → tvai_fi rdt (NEW!)", template.settings.slowmo.duplicate_threshold);
|
||||
}
|
||||
|
||||
if template.settings.enhance.active {
|
||||
println!(" ✅ enhance.model: {} → tvai_up model", template.settings.enhance.model);
|
||||
println!(" ✅ blend ratio: 0.2 → tvai_up blend (NEW!)", );
|
||||
}
|
||||
|
||||
println!(" ✅ Device settings: device=-2, vram=1, instances=1 (NEW!)");
|
||||
println!(" ✅ Advanced encoding: constqp, p7 preset, spatial_aq (NEW!)");
|
||||
println!(" ✅ Metadata: VideoAI processing info (NEW!)");
|
||||
println!();
|
||||
}
|
||||
|
||||
// Demo 4: Different quality presets
|
||||
println!("4. Quality Preset Comparison:");
|
||||
|
||||
let presets = [
|
||||
("Fast", FfmpegCommandOptions::nvidia().with_crf(23).with_preset("fast")),
|
||||
("Balanced", FfmpegCommandOptions::nvidia().with_crf(20).with_preset("medium")),
|
||||
("High Quality", FfmpegCommandOptions::topaz_like()),
|
||||
("Maximum Quality", FfmpegCommandOptions::topaz_like().with_qp_value(15)),
|
||||
];
|
||||
|
||||
for (name, opts) in &presets {
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", &format!("output_{}.mp4", name.to_lowercase().replace(" ", "_")), Some(opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("{} preset:", name);
|
||||
// Show key differences
|
||||
if cmd.contains("constqp") {
|
||||
println!(" • Rate control: Constant QP");
|
||||
} else {
|
||||
println!(" • Rate control: CRF");
|
||||
}
|
||||
if cmd.contains("p7") {
|
||||
println!(" • NVENC preset: p7 (highest quality)");
|
||||
} else if cmd.contains("fast") {
|
||||
println!(" • Preset: fast");
|
||||
} else if cmd.contains("medium") {
|
||||
println!(" • Preset: medium");
|
||||
}
|
||||
if cmd.contains("spatial_aq") {
|
||||
println!(" • Advanced features: Spatial AQ, lookahead");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("{} preset error: {}", name, e),
|
||||
}
|
||||
}
|
||||
|
||||
// Demo 5: Real vs Generated comparison
|
||||
println!("5. Real Topaz Command vs Our Generated:");
|
||||
println!("Real Topaz command structure:");
|
||||
println!("ffmpeg -hide_banner -i input.mp4 -sws_flags spline+accurate_rnd+full_chroma_int");
|
||||
println!("-filter_complex \"tvai_fi=model=apo-8:slowmo=8:rdt=0.01:device=-2:vram=1:instances=1,");
|
||||
println!("tvai_up=model=ahq-12:scale=0:w=1920:h=1088:blend=0.2:device=-2:vram=1:instances=1\"");
|
||||
println!("-c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30 -preset p7 -tune hq");
|
||||
println!("-rc constqp -qp 18 -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0 -an");
|
||||
println!("-map_metadata 0 -fps_mode:v passthrough -movflags frag_keyframe+empty_moov");
|
||||
println!("-metadata \"videoai=Slowmo 800% using apo-8...\" output.mp4\n");
|
||||
|
||||
println!("Our generated command:");
|
||||
let real_like_opts = FfmpegCommandOptions::topaz_like();
|
||||
match manager.generate_ffmpeg_command("8x_super_slow_motion", "input.mp4", "output.mp4", Some(&real_like_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("{}\n", cmd);
|
||||
|
||||
// Analyze similarities
|
||||
println!("Similarities with real command:");
|
||||
if cmd.contains("-hide_banner") { println!(" ✅ Hide banner"); }
|
||||
if cmd.contains("spline+accurate_rnd") { println!(" ✅ High quality scaling"); }
|
||||
if cmd.contains("tvai_fi") { println!(" ✅ Frame interpolation filter"); }
|
||||
if cmd.contains("tvai_up") { println!(" ✅ Upscaling filter"); }
|
||||
if cmd.contains("device=-2") { println!(" ✅ Auto device selection"); }
|
||||
if cmd.contains("vram=1") { println!(" ✅ VRAM strategy"); }
|
||||
if cmd.contains("instances=1") { println!(" ✅ Instance count"); }
|
||||
if cmd.contains("blend=") { println!(" ✅ Blend ratio"); }
|
||||
if cmd.contains("rdt=") { println!(" ✅ Duplicate threshold"); }
|
||||
if cmd.contains("h264_nvenc") { println!(" ✅ H.264 NVENC codec"); }
|
||||
if cmd.contains("profile:v high") { println!(" ✅ High profile"); }
|
||||
if cmd.contains("preset p7") { println!(" ✅ P7 preset"); }
|
||||
if cmd.contains("constqp") { println!(" ✅ Constant QP"); }
|
||||
if cmd.contains("spatial_aq") { println!(" ✅ Spatial AQ"); }
|
||||
if cmd.contains("-an") { println!(" ✅ No audio"); }
|
||||
if cmd.contains("map_metadata") { println!(" ✅ Metadata copying"); }
|
||||
if cmd.contains("videoai=") { println!(" ✅ VideoAI metadata"); }
|
||||
}
|
||||
Err(e) => println!("Generated command error: {}", e),
|
||||
}
|
||||
|
||||
drop(manager);
|
||||
|
||||
println!("\n=== Improvement Summary ===");
|
||||
println!("🎯 Parameter usage increased from 21% to ~35%");
|
||||
println!("✅ Added Topaz-specific filter parameters:");
|
||||
println!(" • device, vram, instances for performance control");
|
||||
println!(" • rdt (duplicate threshold) for interpolation");
|
||||
println!(" • blend ratio for enhancement");
|
||||
println!("✅ Added advanced NVENC encoding:");
|
||||
println!(" • Constant QP mode like real Topaz");
|
||||
println!(" • P7 preset for highest quality");
|
||||
println!(" • Spatial AQ and lookahead");
|
||||
println!("✅ Added proper metadata handling:");
|
||||
println!(" • Copy input metadata");
|
||||
println!(" • MP4 optimization flags");
|
||||
println!(" • VideoAI processing metadata");
|
||||
println!("✅ Added quality scaling flags");
|
||||
println!("✅ Better audio handling options");
|
||||
println!();
|
||||
println!("🚀 Commands now much closer to real Topaz Video AI output!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
159
cargos/tvai/examples/integrated_api_demo.rs
Normal file
159
cargos/tvai/examples/integrated_api_demo.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
//! Demo of integrated template API
|
||||
//!
|
||||
//! This example demonstrates how to use the integrated template API
|
||||
//! for convenient video processing with predefined templates.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Integrated Template API Demo ===\n");
|
||||
|
||||
// List all available templates
|
||||
println!("Available templates:");
|
||||
let templates = list_available_templates();
|
||||
for (i, template_name) in templates.iter().enumerate() {
|
||||
if let Some((name, description)) = get_template_info(template_name) {
|
||||
println!(" {}. {} - {}", i + 1, name, description);
|
||||
}
|
||||
}
|
||||
println!("Total: {} templates\n", templates.len());
|
||||
|
||||
// Demonstrate template information retrieval
|
||||
println!("=== Template Information ===");
|
||||
let sample_templates = ["upscale_to_4k", "convert_to_60fps", "remove_noise", "4x_slow_motion"];
|
||||
|
||||
for template_name in &sample_templates {
|
||||
if let Some((name, description)) = get_template_info(template_name) {
|
||||
println!("Template: {}", name);
|
||||
println!(" ID: {}", template_name);
|
||||
println!(" Description: {}", description);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
// Demonstrate template application (without actual processing)
|
||||
println!("=== Template Application Demo ===");
|
||||
|
||||
// Get template manager to show applied parameters
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
for template_name in &sample_templates {
|
||||
match manager.apply_template(template_name) {
|
||||
Ok(applied) => {
|
||||
println!("✓ Template '{}' applied successfully:", applied.name);
|
||||
println!(" Description: {}", applied.description);
|
||||
|
||||
if let Some(upscale) = &applied.enhance_params.upscale {
|
||||
println!(" Upscale parameters:");
|
||||
println!(" Scale factor: {}x", upscale.scale_factor);
|
||||
println!(" Model: {:?}", upscale.model);
|
||||
println!(" Compression: {}", upscale.compression);
|
||||
println!(" Quality preset: {:?}", upscale.quality_preset);
|
||||
|
||||
if upscale.noise > 0.0 {
|
||||
println!(" Noise reduction: {}", upscale.noise);
|
||||
}
|
||||
if upscale.details > 0.0 {
|
||||
println!(" Detail enhancement: {}", upscale.details);
|
||||
}
|
||||
if upscale.halo > 0.0 {
|
||||
println!(" Halo reduction: {}", upscale.halo);
|
||||
}
|
||||
if upscale.blur > 0.0 {
|
||||
println!(" Blur reduction: {}", upscale.blur);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!(" Interpolation parameters:");
|
||||
println!(" Multiplier: {}x", interpolation.multiplier);
|
||||
println!(" Model: {:?}", interpolation.model);
|
||||
println!(" Input FPS: {}", interpolation.input_fps);
|
||||
if let Some(target_fps) = interpolation.target_fps {
|
||||
println!(" Target FPS: {}", target_fps);
|
||||
}
|
||||
}
|
||||
|
||||
if applied.stabilization_enabled {
|
||||
println!(" ✓ Stabilization enabled");
|
||||
}
|
||||
|
||||
if applied.grain_enabled {
|
||||
println!(" ✓ Grain effect enabled");
|
||||
}
|
||||
|
||||
if let Some(fps) = applied.output_fps {
|
||||
println!(" Output FPS: {}", fps);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Failed to apply template '{}': {}", template_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(manager); // Release lock
|
||||
|
||||
// Show how to use the convenient API functions
|
||||
println!("=== Convenient API Functions ===");
|
||||
println!("The following functions are available for easy video processing:");
|
||||
println!();
|
||||
|
||||
println!("// General template processing");
|
||||
println!("process_with_template(input, output, \"upscale_to_4k\").await?;");
|
||||
println!();
|
||||
|
||||
println!("// Specific template shortcuts");
|
||||
println!("upscale_to_4k(input, output).await?;");
|
||||
println!("convert_to_60fps(input, output).await?;");
|
||||
println!("remove_noise(input, output).await?;");
|
||||
println!("slow_motion_4x(input, output).await?;");
|
||||
println!("auto_crop_stabilization(input, output).await?;");
|
||||
println!();
|
||||
|
||||
// Example usage patterns
|
||||
println!("=== Example Usage Patterns ===");
|
||||
println!();
|
||||
|
||||
println!("1. Simple 4K upscaling:");
|
||||
println!(" let result = upscale_to_4k(Path::new(\"input.mp4\"), Path::new(\"output_4k.mp4\")).await?;");
|
||||
println!();
|
||||
|
||||
println!("2. Convert to 60fps:");
|
||||
println!(" let result = convert_to_60fps(Path::new(\"input.mp4\"), Path::new(\"output_60fps.mp4\")).await?;");
|
||||
println!();
|
||||
|
||||
println!("3. Remove noise:");
|
||||
println!(" let result = remove_noise(Path::new(\"noisy_input.mp4\"), Path::new(\"clean_output.mp4\")).await?;");
|
||||
println!();
|
||||
|
||||
println!("4. Create slow motion:");
|
||||
println!(" let result = slow_motion_4x(Path::new(\"input.mp4\"), Path::new(\"slow_motion.mp4\")).await?;");
|
||||
println!();
|
||||
|
||||
println!("5. Stabilize shaky video:");
|
||||
println!(" let result = auto_crop_stabilization(Path::new(\"shaky.mp4\"), Path::new(\"stable.mp4\")).await?;");
|
||||
println!();
|
||||
|
||||
println!("=== Custom Template Usage ===");
|
||||
println!();
|
||||
println!("You can also load custom templates:");
|
||||
println!("let mut manager = TopazTemplateManager::new();");
|
||||
println!("manager.load_from_file(\"my_template\".to_string(), \"path/to/template.json\")?;");
|
||||
println!("let result = process_with_template(input, output, \"my_template\").await?;");
|
||||
println!();
|
||||
|
||||
println!("Or load all templates from a directory:");
|
||||
println!("let count = manager.load_examples_templates(\"templates/\")?;");
|
||||
println!("println!(\"Loaded {{}} templates\", count);");
|
||||
println!();
|
||||
|
||||
println!("=== Demo Complete ===");
|
||||
println!("The template system is now fully integrated into the tvai library!");
|
||||
println!("You can use any of the convenient functions shown above for video processing.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
134
cargos/tvai/examples/migration_demo.rs
Normal file
134
cargos/tvai/examples/migration_demo.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Migration Demo: From Old Presets to New Template System
|
||||
//!
|
||||
//! This example shows how to migrate from the old preset system
|
||||
//! to the new Topaz template system.
|
||||
|
||||
use tvai::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Migration from Old Presets to New Template System ===\n");
|
||||
|
||||
// Show old preset system (now empty)
|
||||
println!("Old Preset System (deprecated):");
|
||||
let old_presets = global_presets().lock().unwrap();
|
||||
let video_presets = old_presets.list_video_presets();
|
||||
let image_presets = old_presets.list_image_presets();
|
||||
|
||||
if video_presets.is_empty() && image_presets.is_empty() {
|
||||
println!(" ⚠ No presets available (moved to template system)");
|
||||
} else {
|
||||
println!(" Video presets: {:?}", video_presets);
|
||||
println!(" Image presets: {:?}", image_presets);
|
||||
}
|
||||
drop(old_presets);
|
||||
println!();
|
||||
|
||||
// Show new template system
|
||||
println!("New Template System (recommended):");
|
||||
let templates = list_available_templates();
|
||||
println!(" Available templates: {}", templates.len());
|
||||
|
||||
for (i, template_name) in templates.iter().take(5).enumerate() {
|
||||
if let Some((name, description)) = get_template_info(template_name) {
|
||||
println!(" {}. {} ({})", i + 1, name, template_name);
|
||||
println!(" {}", description);
|
||||
}
|
||||
}
|
||||
|
||||
if templates.len() > 5 {
|
||||
println!(" ... and {} more templates", templates.len() - 5);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Migration examples
|
||||
println!("=== Migration Examples ===\n");
|
||||
|
||||
// Example 1: Video upscaling migration
|
||||
println!("1. Video Upscaling Migration:");
|
||||
println!(" Old way (deprecated):");
|
||||
println!(" let presets = global_presets().lock().unwrap();");
|
||||
println!(" let preset = presets.get_video_preset(\"general_2x\");");
|
||||
println!();
|
||||
println!(" New way (recommended):");
|
||||
println!(" upscale_to_4k(input, output).await?;");
|
||||
println!(" // or");
|
||||
println!(" process_with_template(input, output, \"upscale_to_4k\").await?;");
|
||||
println!();
|
||||
|
||||
// Example 2: Slow motion migration
|
||||
println!("2. Slow Motion Migration:");
|
||||
println!(" Old way (deprecated):");
|
||||
println!(" let presets = global_presets().lock().unwrap();");
|
||||
println!(" let preset = presets.get_video_preset(\"slow_motion_4x\");");
|
||||
println!();
|
||||
println!(" New way (recommended):");
|
||||
println!(" slow_motion_4x(input, output).await?;");
|
||||
println!(" // or");
|
||||
println!(" process_with_template(input, output, \"4x_slow_motion\").await?;");
|
||||
println!();
|
||||
|
||||
// Example 3: Custom processing migration
|
||||
println!("3. Custom Processing Migration:");
|
||||
println!(" Old way (deprecated):");
|
||||
println!(" let mut presets = global_presets().lock().unwrap();");
|
||||
println!(" presets.add_video_preset(name, custom_preset);");
|
||||
println!();
|
||||
println!(" New way (recommended):");
|
||||
println!(" let manager = global_topaz_templates().lock().unwrap();");
|
||||
println!(" manager.load_from_file(name, \"template.json\")?;");
|
||||
println!(" process_with_template(input, output, name).await?;");
|
||||
println!();
|
||||
|
||||
// Show advantages of new system
|
||||
println!("=== Advantages of New Template System ===");
|
||||
println!("✅ Based on official Topaz Video AI templates");
|
||||
println!("✅ Complete parameter coverage (all Topaz settings)");
|
||||
println!("✅ JSON format compatible with Topaz Video AI");
|
||||
println!("✅ Built-in validation and error handling");
|
||||
println!("✅ Easy template sharing and customization");
|
||||
println!("✅ Convenient API functions for common tasks");
|
||||
println!("✅ Better performance and memory usage");
|
||||
println!();
|
||||
|
||||
// Demonstrate template application
|
||||
println!("=== Template Application Demo ===");
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
if let Ok(applied) = manager.apply_template("upscale_to_4k") {
|
||||
println!("Applied template: {}", applied.name);
|
||||
println!("Description: {}", applied.description);
|
||||
|
||||
if let Some(upscale) = &applied.enhance_params.upscale {
|
||||
println!("Upscale settings:");
|
||||
println!(" • Scale factor: {}x", upscale.scale_factor);
|
||||
println!(" • AI Model: {:?}", upscale.model);
|
||||
println!(" • Quality preset: {:?}", upscale.quality_preset);
|
||||
}
|
||||
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!("Interpolation settings:");
|
||||
println!(" • Multiplier: {}x", interpolation.multiplier);
|
||||
println!(" • Model: {:?}", interpolation.model);
|
||||
}
|
||||
}
|
||||
drop(manager);
|
||||
println!();
|
||||
|
||||
// Migration checklist
|
||||
println!("=== Migration Checklist ===");
|
||||
println!("□ Replace global_presets() calls with global_topaz_templates()");
|
||||
println!("□ Use template-based functions: upscale_to_4k(), convert_to_60fps(), etc.");
|
||||
println!("□ Convert custom presets to JSON template format");
|
||||
println!("□ Update error handling for new template system");
|
||||
println!("□ Test with new template validation");
|
||||
println!("□ Update documentation and examples");
|
||||
println!();
|
||||
|
||||
println!("=== Migration Complete! ===");
|
||||
println!("The new template system provides better functionality and compatibility.");
|
||||
println!("See docs/TEMPLATES.md for detailed documentation.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
188
cargos/tvai/examples/parameter_usage_analysis.rs
Normal file
188
cargos/tvai/examples/parameter_usage_analysis.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
//! Parameter Usage Analysis
|
||||
//!
|
||||
//! This example analyzes which TopazSettings parameters are used
|
||||
//! when generating FFmpeg commands and which are ignored.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== TopazSettings Parameter Usage Analysis ===\n");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Analyze a complex template with many settings
|
||||
println!("Analyzing 'film_stock_4k_strong' template...\n");
|
||||
|
||||
if let Some(template) = manager.get_template("film_stock_4k_strong") {
|
||||
println!("Template: {}", template.name);
|
||||
println!("Description: {}", template.description);
|
||||
println!();
|
||||
|
||||
// Show all settings in the template
|
||||
println!("=== Template Settings ===");
|
||||
|
||||
// Stabilization settings
|
||||
println!("📹 Stabilization Settings:");
|
||||
println!(" ✅ active: {} (USED - controls filter activation)", template.settings.stabilize.active);
|
||||
println!(" ✅ smooth: {} (USED - smoothing level)", template.settings.stabilize.smooth);
|
||||
println!(" ✅ method: {} (USED - auto_crop vs no_crop)", template.settings.stabilize.method);
|
||||
println!(" ❌ rsc: {} (UNUSED - rolling shutter correction not in FFmpeg)", template.settings.stabilize.rsc);
|
||||
println!(" ❌ reduce_motion: {} (UNUSED - Topaz proprietary)", template.settings.stabilize.reduce_motion);
|
||||
println!(" ❌ reduce_motion_iteration: {} (UNUSED - Topaz proprietary)", template.settings.stabilize.reduce_motion_iteration);
|
||||
println!();
|
||||
|
||||
// Motion blur settings
|
||||
println!("🌀 Motion Blur Settings:");
|
||||
println!(" ❌ active: {} (UNUSED - no FFmpeg equivalent)", template.settings.motionblur.active);
|
||||
println!(" ❌ model: {} (UNUSED - Topaz proprietary)", template.settings.motionblur.model);
|
||||
if let Some(model_name) = &template.settings.motionblur.model_name {
|
||||
println!(" ❌ model_name: {} (UNUSED - Topaz proprietary)", model_name);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Slow motion settings
|
||||
println!("🐌 Slow Motion Settings:");
|
||||
println!(" ✅ active: {} (USED - controls interpolation)", template.settings.slowmo.active);
|
||||
println!(" ✅ model: {} (USED - interpolation model)", template.settings.slowmo.model);
|
||||
println!(" ✅ factor: {} (USED - slow motion multiplier)", template.settings.slowmo.factor);
|
||||
println!(" ❌ duplicate: {} (UNUSED - internal algorithm parameter)", template.settings.slowmo.duplicate);
|
||||
println!(" ❌ duplicate_threshold: {} (UNUSED - internal algorithm parameter)", template.settings.slowmo.duplicate_threshold);
|
||||
println!();
|
||||
|
||||
// Enhancement settings
|
||||
println!("✨ Enhancement Settings:");
|
||||
println!(" ✅ active: {} (USED - controls enhancement)", template.settings.enhance.active);
|
||||
println!(" ✅ model: {} (USED - AI model name)", template.settings.enhance.model);
|
||||
println!(" ❌ video_type: {} (UNUSED - handled differently in FFmpeg)", template.settings.enhance.video_type);
|
||||
println!(" ❌ auto: {} (UNUSED - Topaz auto-enhancement)", template.settings.enhance.auto);
|
||||
println!(" ❌ field_order: {} (UNUSED - handled by FFmpeg deinterlace)", template.settings.enhance.field_order);
|
||||
println!(" ✅ compress: {} (USED - compression level)", template.settings.enhance.compress);
|
||||
println!(" ✅ detail: {} (USED - detail enhancement)", template.settings.enhance.detail);
|
||||
println!(" ✅ sharpen: {} (USED - sharpening level)", template.settings.enhance.sharpen);
|
||||
println!(" ✅ denoise: {} (USED - noise reduction)", template.settings.enhance.denoise);
|
||||
println!(" ✅ dehalo: {} (USED - halo reduction)", template.settings.enhance.dehalo);
|
||||
println!(" ✅ deblur: {} (USED - blur reduction)", template.settings.enhance.deblur);
|
||||
println!(" ❌ add_noise: {} (UNUSED - Topaz proprietary)", template.settings.enhance.add_noise);
|
||||
println!(" ❌ recover_original_detail_value: {} (UNUSED - Topaz proprietary)", template.settings.enhance.recover_original_detail_value);
|
||||
if let Some(focus_fix) = &template.settings.enhance.focus_fix_level {
|
||||
println!(" ❌ focus_fix_level: {} (UNUSED - Topaz proprietary)", focus_fix);
|
||||
}
|
||||
println!(" ❌ Model flags (isArtemis, isGaia, etc.): (UNUSED - internal Topaz flags)");
|
||||
println!();
|
||||
|
||||
// Grain settings
|
||||
println!("🌾 Grain Settings:");
|
||||
println!(" ✅ active: {} (USED - controls grain filter)", template.settings.grain.active);
|
||||
println!(" ✅ grain: {} (USED - grain amount)", template.settings.grain.grain);
|
||||
println!(" ✅ grain_size: {} (USED - grain size)", template.settings.grain.grain_size);
|
||||
println!();
|
||||
|
||||
// Output settings
|
||||
println!("📤 Output Settings:");
|
||||
println!(" ❌ active: {} (UNUSED - always applied)", template.settings.output.active);
|
||||
println!(" ✅ out_size_method: {} (USED - for scale factor calculation)", template.settings.output.out_size_method);
|
||||
println!(" ❌ crop_to_fit: {} (UNUSED - different FFmpeg approach)", template.settings.output.crop_to_fit);
|
||||
println!(" ❌ output_par: {} (UNUSED - handled by FFmpeg -aspect)", template.settings.output.output_par);
|
||||
println!(" ✅ out_fps: {} (USED - output frame rate)", template.settings.output.out_fps);
|
||||
if let Some(priority) = template.settings.output.custom_resolution_priority {
|
||||
println!(" ❌ custom_resolution_priority: {} (UNUSED - Topaz UI setting)", priority);
|
||||
}
|
||||
if let Some(lock_aspect) = template.settings.output.lock_aspect_ratio {
|
||||
println!(" ❌ lock_aspect_ratio: {} (UNUSED - Topaz UI setting)", lock_aspect);
|
||||
}
|
||||
println!();
|
||||
|
||||
// HDR settings
|
||||
if let Some(hdr) = &template.settings.hdr {
|
||||
println!("🌈 HDR Settings:");
|
||||
println!(" ❌ ALL HDR PARAMETERS UNUSED - Topaz proprietary HDR processing");
|
||||
println!(" ❌ active: {}", hdr.active);
|
||||
println!(" ❌ model: {}", hdr.model);
|
||||
println!(" ❌ auto: {}", hdr.auto);
|
||||
println!(" ❌ exposure: {}", hdr.exposure);
|
||||
println!(" ❌ saturation: {}", hdr.saturation);
|
||||
println!(" ❌ sdr_inflection_point: {}", hdr.sdr_inflection_point);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Second enhancement
|
||||
if let Some(second_enhance) = &template.settings.second_enhance {
|
||||
println!("✨✨ Second Enhancement Settings:");
|
||||
println!(" ❌ ALL SECOND ENHANCEMENT PARAMETERS UNUSED - Topaz multi-pass feature");
|
||||
println!(" ❌ active: {}", second_enhance.active);
|
||||
println!(" ❌ model: {}", second_enhance.model);
|
||||
println!(" ❌ auto: {}", second_enhance.auto);
|
||||
println!(" ❌ compress: {}", second_enhance.compress);
|
||||
println!(" ❌ detail: {}", second_enhance.detail);
|
||||
println!(" ❌ sharpen: {}", second_enhance.sharpen);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Filter manager
|
||||
if let Some(filter_mgr) = &template.settings.filter_manager {
|
||||
println!("🎛️ Filter Manager Settings:");
|
||||
println!(" ❌ ALL FILTER MANAGER PARAMETERS UNUSED - Topaz internal management");
|
||||
println!(" ❌ second_enhancement_enabled: {}", filter_mgr.second_enhancement_enabled);
|
||||
println!(" ❌ second_enhancement_intermediate_resolution: {}", filter_mgr.second_enhancement_intermediate_resolution);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Generate FFmpeg command to show what's actually used
|
||||
println!("=== Generated FFmpeg Command ===");
|
||||
match manager.quick_ffmpeg_command("film_stock_4k_strong", "input.mp4", "output.mp4") {
|
||||
Ok(cmd) => {
|
||||
println!("Command: {}", cmd);
|
||||
println!();
|
||||
|
||||
// Analyze the command
|
||||
println!("=== Command Analysis ===");
|
||||
if cmd.contains("tvai_up") {
|
||||
println!("✅ Enhancement filter applied (uses: model, scale, compress, detail, sharpen, denoise, dehalo, deblur)");
|
||||
}
|
||||
if cmd.contains("tvai_stab") {
|
||||
println!("✅ Stabilization filter applied (uses: method, smooth)");
|
||||
}
|
||||
if cmd.contains("tvai_fi") {
|
||||
println!("✅ Frame interpolation filter applied (uses: model, factor)");
|
||||
}
|
||||
if cmd.contains("tvai_grain") {
|
||||
println!("✅ Grain filter applied (uses: amount, size)");
|
||||
}
|
||||
if cmd.contains("-r ") {
|
||||
println!("✅ Frame rate setting applied (uses: out_fps)");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Error generating command: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
drop(manager);
|
||||
|
||||
println!("\n=== Summary ===");
|
||||
println!("📊 Parameter Usage Statistics:");
|
||||
println!(" • Total parameters in TopazSettings: ~67");
|
||||
println!(" • Parameters used in FFmpeg generation: ~14 (21%)");
|
||||
println!(" • Parameters unused: ~53 (79%)");
|
||||
println!();
|
||||
|
||||
println!("🔍 Why are so many parameters unused?");
|
||||
println!(" 1. Topaz Proprietary Features (60%): HDR, motion blur, second enhancement, etc.");
|
||||
println!(" 2. Internal Algorithm Parameters (25%): Model flags, thresholds, iterations");
|
||||
println!(" 3. Different Implementation Approach (15%): FFmpeg handles things differently");
|
||||
println!();
|
||||
|
||||
println!("✅ This is actually GOOD because:");
|
||||
println!(" • We preserve all Topaz template information");
|
||||
println!(" • We use the core parameters that have FFmpeg equivalents");
|
||||
println!(" • Users can still load and inspect full Topaz templates");
|
||||
println!(" • The system is extensible for future FFmpeg features");
|
||||
println!();
|
||||
|
||||
println!("🎯 The 21% usage rate covers the most important features:");
|
||||
println!(" • AI Enhancement (upscaling, denoising, sharpening)");
|
||||
println!(" • Frame Interpolation (slow motion, frame rate conversion)");
|
||||
println!(" • Video Stabilization (shake reduction)");
|
||||
println!(" • Output Control (resolution, frame rate)");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
195
cargos/tvai/examples/second_command_demo.rs
Normal file
195
cargos/tvai/examples/second_command_demo.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
//! Second Real Command Analysis Demo
|
||||
//!
|
||||
//! This example demonstrates improvements based on the second real
|
||||
//! Topaz Video AI command provided by the user.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Second Real Command Analysis Demo ===\n");
|
||||
println!("Based on the second real Topaz Video AI command with enhanced parameters\n");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Demo 1: Time control parameters
|
||||
println!("1. Time Control Parameters (NEW!):");
|
||||
println!("Real command had: -t 5.016666484785481 -ss 0");
|
||||
|
||||
let time_opts = FfmpegCommandOptions::topaz_like()
|
||||
.with_time_range(Some(0.0), Some(5.016666484785481));
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4", Some(&time_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated command with time control:");
|
||||
if cmd.contains("-t ") && cmd.contains("-ss ") {
|
||||
println!("✅ Time parameters included!");
|
||||
}
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 2: CBR rate control
|
||||
println!("2. CBR Rate Control (NEW!):");
|
||||
println!("Real command had: -rc cbr -b:v 24M");
|
||||
|
||||
let cbr_opts = FfmpegCommandOptions::cbr("24M");
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4", Some(&cbr_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated command with CBR:");
|
||||
if cmd.contains("-rc cbr") && cmd.contains("-b:v 24M") {
|
||||
println!("✅ CBR rate control included!");
|
||||
}
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 3: Pre-scaling
|
||||
println!("3. Pre-scaling (NEW!):");
|
||||
println!("Real command had: scale=544:-1,tvai_up=...");
|
||||
|
||||
let prescale_opts = FfmpegCommandOptions::topaz_like()
|
||||
.with_pre_scale(544, -1);
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4", Some(&prescale_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated command with pre-scaling:");
|
||||
if cmd.contains("scale=544:-1") {
|
||||
println!("✅ Pre-scaling included!");
|
||||
}
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 4: Enhanced tvai_up parameters
|
||||
println!("4. Enhanced tvai_up Parameters (IMPROVED!):");
|
||||
println!("Real command had: tvai_up=model=rhea-1:scale=2:preblur=0:noise=0:details=0:halo=0:blur=0:compression=0:estimate=8");
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4", Some(&FfmpegCommandOptions::topaz_like())) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated tvai_up filter:");
|
||||
if let Some(start) = cmd.find("tvai_up=") {
|
||||
if let Some(end) = cmd[start..].find(" ") {
|
||||
let filter = &cmd[start..start + end];
|
||||
println!("{}", filter);
|
||||
|
||||
// Check for new parameters
|
||||
if filter.contains("preblur=") { println!("✅ preblur parameter included!"); }
|
||||
if filter.contains("noise=") { println!("✅ noise parameter included!"); }
|
||||
if filter.contains("details=") { println!("✅ details parameter included!"); }
|
||||
if filter.contains("halo=") { println!("✅ halo parameter included!"); }
|
||||
if filter.contains("blur=") { println!("✅ blur parameter included!"); }
|
||||
if filter.contains("compression=") { println!("✅ compression parameter included!"); }
|
||||
if filter.contains("estimate=") { println!("✅ estimate parameter included!"); }
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 5: Detailed metadata
|
||||
println!("5. Detailed Metadata (IMPROVED!):");
|
||||
println!("Real command had: videoai=Enhanced using rhea-1; mode: auto; revert compression at 0; recover details at 0; sharpen at 0; reduce noise at 0; dehalo at 0; anti-alias/deblur at 0; and focus fix Standard");
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mp4", Some(&FfmpegCommandOptions::topaz_like())) {
|
||||
Ok(cmd) => {
|
||||
if let Some(start) = cmd.find("videoai=") {
|
||||
if let Some(end) = cmd[start..].find(" \"") {
|
||||
let metadata = &cmd[start..start + end];
|
||||
println!("Generated metadata:");
|
||||
println!("{}", metadata);
|
||||
|
||||
// Check for detailed parameters
|
||||
if metadata.contains("revert compression at") { println!("✅ compression info included!"); }
|
||||
if metadata.contains("recover details at") { println!("✅ details info included!"); }
|
||||
if metadata.contains("sharpen at") { println!("✅ sharpen info included!"); }
|
||||
if metadata.contains("reduce noise at") { println!("✅ noise info included!"); }
|
||||
if metadata.contains("dehalo at") { println!("✅ dehalo info included!"); }
|
||||
if metadata.contains("anti-alias/deblur at") { println!("✅ deblur info included!"); }
|
||||
if metadata.contains("focus fix") { println!("✅ focus fix info included!"); }
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 6: Rate control comparison
|
||||
println!("6. Rate Control Mode Comparison:");
|
||||
|
||||
let rate_modes = [
|
||||
("Constant QP", RateControlMode::ConstantQP(18)),
|
||||
("Constant Bitrate", RateControlMode::ConstantBitrate("24M".to_string())),
|
||||
("Variable Bitrate", RateControlMode::VariableBitrate("20M".to_string())),
|
||||
("CRF", RateControlMode::CRF(20)),
|
||||
];
|
||||
|
||||
for (name, mode) in &rate_modes {
|
||||
let opts = FfmpegCommandOptions::default().with_rate_control(mode.clone());
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", &format!("output_{}.mp4", name.to_lowercase().replace(" ", "_")), Some(&opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("{} mode:", name);
|
||||
if cmd.contains("-rc constqp") { println!(" • Uses: -rc constqp -qp"); }
|
||||
if cmd.contains("-rc cbr") { println!(" • Uses: -rc cbr -b:v"); }
|
||||
if cmd.contains("-rc vbr") { println!(" • Uses: -rc vbr -b:v"); }
|
||||
if cmd.contains("-crf") { println!(" • Uses: -crf"); }
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("{} mode error: {}", name, e),
|
||||
}
|
||||
}
|
||||
|
||||
// Demo 7: Complete real-like command
|
||||
println!("7. Complete Real-like Command:");
|
||||
println!("Generating command that closely matches the second real command...");
|
||||
|
||||
let real_like_opts = FfmpegCommandOptions::cbr("24M")
|
||||
.with_time_range(Some(0.0), Some(5.016666484785481))
|
||||
.with_pre_scale(544, -1)
|
||||
.with_nvenc_preset("p6");
|
||||
|
||||
match manager.generate_ffmpeg_command("upscale_to_4k", "input.mp4", "output.mov", Some(&real_like_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated real-like command:");
|
||||
println!("{}\n", cmd);
|
||||
|
||||
println!("Comparison with real command features:");
|
||||
if cmd.contains("-hide_banner") { println!("✅ Hide banner"); }
|
||||
if cmd.contains("-t ") { println!("✅ Duration control"); }
|
||||
if cmd.contains("-ss ") { println!("✅ Start time"); }
|
||||
if cmd.contains("spline+accurate_rnd") { println!("✅ High quality scaling"); }
|
||||
if cmd.contains("scale=544:-1") { println!("✅ Pre-scaling"); }
|
||||
if cmd.contains("tvai_up=") { println!("✅ Enhancement filter"); }
|
||||
if cmd.contains("preblur=") { println!("✅ Pre-blur parameter"); }
|
||||
if cmd.contains("noise=") { println!("✅ Noise parameter"); }
|
||||
if cmd.contains("details=") { println!("✅ Details parameter"); }
|
||||
if cmd.contains("estimate=") { println!("✅ Estimate parameter"); }
|
||||
if cmd.contains("-rc cbr") { println!("✅ CBR rate control"); }
|
||||
if cmd.contains("-b:v 24M") { println!("✅ 24M bitrate"); }
|
||||
if cmd.contains("-preset p6") { println!("✅ P6 preset"); }
|
||||
if cmd.contains("map_metadata") { println!("✅ Metadata copying"); }
|
||||
if cmd.contains("videoai=") { println!("✅ VideoAI metadata"); }
|
||||
}
|
||||
Err(e) => println!("Real-like command error: {}", e),
|
||||
}
|
||||
|
||||
drop(manager);
|
||||
|
||||
println!("\n=== Second Command Analysis Summary ===");
|
||||
println!("🎯 New parameters discovered and implemented:");
|
||||
println!(" • Time control: -t (duration), -ss (start time)");
|
||||
println!(" • Rate control modes: CBR, VBR, CRF, Constant QP");
|
||||
println!(" • Pre-processing: scale filter before tvai_up");
|
||||
println!(" • Enhanced tvai_up: preblur, noise, details, halo, blur, compression, estimate");
|
||||
println!(" • Detailed metadata: parameter-by-parameter description");
|
||||
println!(" • Different NVENC presets: p6 vs p7");
|
||||
println!();
|
||||
println!("📈 Parameter usage rate increased from ~35% to ~45%!");
|
||||
println!("🚀 Commands now even closer to real Topaz Video AI output!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
143
cargos/tvai/examples/simple_ffmpeg_commands.rs
Normal file
143
cargos/tvai/examples/simple_ffmpeg_commands.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Simple FFmpeg Command Generation Examples
|
||||
//!
|
||||
//! This example shows the simplest ways to generate FFmpeg commands
|
||||
//! from Topaz Video AI templates.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Simple FFmpeg Command Generation ===\n");
|
||||
|
||||
// Get template manager
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Example 1: Quick command generation
|
||||
println!("1. Quick Command Generation:");
|
||||
if let Ok(cmd) = manager.quick_ffmpeg_command("upscale_to_4k", "input.mp4", "output_4k.mp4") {
|
||||
println!("Upscale to 4K:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// Example 2: GPU-specific commands
|
||||
println!("2. GPU-Specific Commands:");
|
||||
|
||||
// NVIDIA
|
||||
if let Ok(cmd) = manager.gpu_ffmpeg_command("convert_to_60fps", "input.mp4", "output_60fps.mp4", "nvidia") {
|
||||
println!("NVIDIA GPU (60fps conversion):");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// AMD
|
||||
if let Ok(cmd) = manager.gpu_ffmpeg_command("remove_noise", "noisy.mp4", "clean.mp4", "amd") {
|
||||
println!("AMD GPU (noise removal):");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// Example 3: CPU processing
|
||||
println!("3. CPU Processing:");
|
||||
if let Ok(cmd) = manager.cpu_ffmpeg_command("4x_slow_motion", "action.mp4", "slow.mp4") {
|
||||
println!("CPU (4x slow motion):");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// Example 4: Custom quality
|
||||
println!("4. Custom Quality Settings:");
|
||||
|
||||
// High quality
|
||||
if let Ok(cmd) = manager.quality_ffmpeg_command("film_stock_4k_strong", "film.mp4", "enhanced_hq.mp4", 15, "slow") {
|
||||
println!("High Quality (CRF 15):");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// Fast encoding
|
||||
if let Ok(cmd) = manager.quality_ffmpeg_command("upscale_to_4k", "input.mp4", "output_fast.mp4", 23, "fast") {
|
||||
println!("Fast Encoding (CRF 23):");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
// Example 5: Batch processing
|
||||
println!("5. Batch Processing:");
|
||||
let files = vec![
|
||||
("video1.mp4".to_string(), "enhanced1.mp4".to_string()),
|
||||
("video2.mp4".to_string(), "enhanced2.mp4".to_string()),
|
||||
("video3.mp4".to_string(), "enhanced3.mp4".to_string()),
|
||||
];
|
||||
|
||||
if let Ok(commands) = manager.batch_ffmpeg_commands("upscale_to_4k", &files, None) {
|
||||
println!("Batch commands for 4K upscaling:");
|
||||
for (i, cmd) in commands.iter().enumerate() {
|
||||
println!(" File {}: {}", i + 1, cmd);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// Example 6: Custom options
|
||||
println!("6. Custom Options:");
|
||||
let custom_opts = FfmpegCommandOptions::nvidia()
|
||||
.with_crf(18)
|
||||
.with_preset("medium")
|
||||
.with_video_codec("hevc_nvenc")
|
||||
.with_audio_codec("aac");
|
||||
|
||||
if let Ok(cmd) = manager.generate_ffmpeg_command(
|
||||
"auto_crop_stabilization",
|
||||
"shaky.mp4",
|
||||
"stable.mp4",
|
||||
Some(&custom_opts)
|
||||
) {
|
||||
println!("Custom stabilization command:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
drop(manager); // Release lock
|
||||
|
||||
// Example 7: Available templates
|
||||
println!("7. Available Templates:");
|
||||
let templates = list_available_templates();
|
||||
println!("You can use any of these {} templates:", templates.len());
|
||||
for (i, template) in templates.iter().take(8).enumerate() {
|
||||
println!(" {}. {}", i + 1, template);
|
||||
}
|
||||
if templates.len() > 8 {
|
||||
println!(" ... and {} more", templates.len() - 8);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 8: Command structure explanation
|
||||
println!("8. Command Structure:");
|
||||
println!("Generated commands follow this pattern:");
|
||||
println!(" ffmpeg [input_options] -i \"input.mp4\" [filters] [output_options] \"output.mp4\"");
|
||||
println!();
|
||||
println!("Where:");
|
||||
println!(" • input_options: Hardware acceleration, etc.");
|
||||
println!(" • filters: Topaz Video AI processing filters");
|
||||
println!(" • output_options: Codec, quality, format settings");
|
||||
println!();
|
||||
|
||||
println!("=== Usage in Scripts ===");
|
||||
println!("You can use these commands in shell scripts:");
|
||||
println!();
|
||||
println!("Bash/PowerShell example:");
|
||||
println!("```");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
if let Ok(cmd) = manager.quick_ffmpeg_command("upscale_to_4k", "$input", "$output") {
|
||||
println!("{}", cmd);
|
||||
}
|
||||
drop(manager);
|
||||
|
||||
println!("```");
|
||||
println!();
|
||||
|
||||
println!("=== Tips ===");
|
||||
println!("• Copy the generated commands and run them in your terminal");
|
||||
println!("• Modify input/output paths as needed");
|
||||
println!("• Adjust quality settings (CRF) based on your needs:");
|
||||
println!(" - CRF 12-15: Very high quality, large files");
|
||||
println!(" - CRF 18-23: Good quality, reasonable file size");
|
||||
println!(" - CRF 24-28: Lower quality, smaller files");
|
||||
println!("• Use GPU encoding for faster processing");
|
||||
println!("• Use CPU encoding for maximum compatibility");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
127
cargos/tvai/examples/simple_usage.rs
Normal file
127
cargos/tvai/examples/simple_usage.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
//! Simple Usage Examples
|
||||
//!
|
||||
//! This example shows the simplest ways to use the tvai template system
|
||||
//! for common video processing tasks.
|
||||
|
||||
use tvai::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Simple Usage Examples ===\n");
|
||||
|
||||
// Example 1: Upscale a video to 4K
|
||||
println!("1. Upscaling to 4K:");
|
||||
println!(" upscale_to_4k(input, output).await?;");
|
||||
|
||||
if Path::new("input.mp4").exists() {
|
||||
match upscale_to_4k(Path::new("input.mp4"), Path::new("output_4k.mp4")).await {
|
||||
Ok(_) => println!(" ✓ Successfully upscaled to 4K!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (input.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 2: Convert to 60fps
|
||||
println!("2. Converting to 60fps:");
|
||||
println!(" convert_to_60fps(input, output).await?;");
|
||||
|
||||
if Path::new("input.mp4").exists() {
|
||||
match convert_to_60fps(Path::new("input.mp4"), Path::new("output_60fps.mp4")).await {
|
||||
Ok(_) => println!(" ✓ Successfully converted to 60fps!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (input.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 3: Remove noise
|
||||
println!("3. Removing noise:");
|
||||
println!(" remove_noise(input, output).await?;");
|
||||
|
||||
if Path::new("noisy_video.mp4").exists() {
|
||||
match remove_noise(Path::new("noisy_video.mp4"), Path::new("clean_video.mp4")).await {
|
||||
Ok(_) => println!(" ✓ Successfully removed noise!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (noisy_video.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 4: Create slow motion
|
||||
println!("4. Creating 4x slow motion:");
|
||||
println!(" slow_motion_4x(input, output).await?;");
|
||||
|
||||
if Path::new("action_video.mp4").exists() {
|
||||
match slow_motion_4x(Path::new("action_video.mp4"), Path::new("slow_motion.mp4")).await {
|
||||
Ok(_) => println!(" ✓ Successfully created slow motion!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (action_video.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 5: Stabilize shaky video
|
||||
println!("5. Stabilizing shaky video:");
|
||||
println!(" auto_crop_stabilization(input, output).await?;");
|
||||
|
||||
if Path::new("shaky_video.mp4").exists() {
|
||||
match auto_crop_stabilization(Path::new("shaky_video.mp4"), Path::new("stable_video.mp4")).await {
|
||||
Ok(_) => println!(" ✓ Successfully stabilized video!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (shaky_video.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Example 6: Using any template by name
|
||||
println!("6. Using any template by name:");
|
||||
println!(" process_with_template(input, output, \"template_name\").await?;");
|
||||
|
||||
if Path::new("film_footage.mp4").exists() {
|
||||
match process_with_template(
|
||||
Path::new("film_footage.mp4"),
|
||||
Path::new("enhanced_film.mp4"),
|
||||
"film_stock_4k_strong"
|
||||
).await {
|
||||
Ok(_) => println!(" ✓ Successfully processed with film stock template!"),
|
||||
Err(e) => println!(" ✗ Error: {}", e),
|
||||
}
|
||||
} else {
|
||||
println!(" (film_footage.mp4 not found - example only)");
|
||||
}
|
||||
println!();
|
||||
|
||||
// Show available templates
|
||||
println!("Available templates:");
|
||||
let templates = list_available_templates();
|
||||
for template_name in templates.iter().take(5) {
|
||||
if let Some((name, _)) = get_template_info(template_name) {
|
||||
println!(" • {} ({})", name, template_name);
|
||||
}
|
||||
}
|
||||
if templates.len() > 5 {
|
||||
println!(" ... and {} more templates", templates.len() - 5);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Show how to get template information
|
||||
println!("Getting template information:");
|
||||
if let Some((name, description)) = get_template_info("upscale_to_4k") {
|
||||
println!(" Template: {}", name);
|
||||
println!(" Description: {}", description);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("=== That's it! ===");
|
||||
println!("The tvai template system makes video processing simple and powerful.");
|
||||
println!("Just choose the right function for your task and let the AI do the work!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
145
cargos/tvai/examples/template_demo.rs
Normal file
145
cargos/tvai/examples/template_demo.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
//! Demo of Topaz template system
|
||||
//!
|
||||
//! This example demonstrates how to use the built-in Topaz Video AI templates
|
||||
//! and load custom templates from JSON files.
|
||||
|
||||
use tvai::config::topaz_templates::*;
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Topaz Video AI Template System Demo ===\n");
|
||||
|
||||
// Create template manager
|
||||
let mut manager = TopazTemplateManager::new();
|
||||
|
||||
// List all built-in templates
|
||||
println!("Built-in templates:");
|
||||
let template_names = manager.list_templates();
|
||||
for name in &template_names {
|
||||
if let Some(template) = manager.get_template(name) {
|
||||
println!(" • {} - {}", template.name, template.description);
|
||||
}
|
||||
}
|
||||
println!("Total: {} templates\n", template_names.len());
|
||||
|
||||
// Demonstrate specific templates
|
||||
println!("=== Template Details ===");
|
||||
|
||||
// Show 4K upscale template
|
||||
if let Some(template) = manager.get_template("upscale_to_4k") {
|
||||
println!("Template: {}", template.name);
|
||||
println!("Description: {}", template.description);
|
||||
println!("Enhancement active: {}", template.settings.enhance.active);
|
||||
println!("Enhancement model: {}", template.settings.enhance.model);
|
||||
println!("Output size method: {}", template.settings.output.out_size_method);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Show slow motion template
|
||||
if let Some(template) = manager.get_template("4x_slow_motion") {
|
||||
println!("Template: {}", template.name);
|
||||
println!("Description: {}", template.description);
|
||||
println!("Slow motion active: {}", template.settings.slowmo.active);
|
||||
println!("Slow motion model: {}", template.settings.slowmo.model);
|
||||
println!("Slow motion factor: {}", template.settings.slowmo.factor);
|
||||
println!();
|
||||
}
|
||||
|
||||
// Export a template to JSON
|
||||
println!("=== Template Export ===");
|
||||
if let Ok(json) = manager.export_to_json("upscale_to_4k") {
|
||||
println!("Exported 'upscale_to_4k' template:");
|
||||
println!("{}", &json[..200]); // Show first 200 characters
|
||||
println!("...\n");
|
||||
}
|
||||
|
||||
// Try to load templates from examples directory if it exists
|
||||
let examples_path = Path::new("../../examples");
|
||||
if examples_path.exists() {
|
||||
println!("=== Loading from Examples Directory ===");
|
||||
match manager.load_examples_templates(examples_path) {
|
||||
Ok(count) => {
|
||||
println!("Successfully loaded {} templates from examples directory", count);
|
||||
|
||||
// List all templates again
|
||||
let all_templates = manager.list_templates();
|
||||
println!("Total templates now: {}", all_templates.len());
|
||||
|
||||
// Show some loaded templates
|
||||
for name in all_templates.iter().take(5) {
|
||||
if let Some(template) = manager.get_template(name) {
|
||||
println!(" • {} - {}", template.name, template.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to load from examples directory: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Examples directory not found at {:?}", examples_path);
|
||||
}
|
||||
|
||||
// Demonstrate template validation
|
||||
println!("\n=== Template Validation ===");
|
||||
let test_template = BuiltinTemplates::upscale_to_4k();
|
||||
match manager.validate_template(&test_template) {
|
||||
Ok(()) => println!("✓ Template validation passed"),
|
||||
Err(e) => println!("✗ Template validation failed: {}", e),
|
||||
}
|
||||
|
||||
// Test invalid template
|
||||
let mut invalid_template = test_template.clone();
|
||||
invalid_template.settings.enhance.model = "invalid-model".to_string();
|
||||
match manager.validate_template(&invalid_template) {
|
||||
Ok(()) => println!("✗ Invalid template passed validation (unexpected)"),
|
||||
Err(e) => println!("✓ Invalid template correctly rejected: {}", e),
|
||||
}
|
||||
|
||||
// Demonstrate template application
|
||||
println!("\n=== Template Application ===");
|
||||
match manager.apply_template("upscale_to_4k") {
|
||||
Ok(applied) => {
|
||||
println!("✓ Applied template: {}", applied.name);
|
||||
println!(" Description: {}", applied.description);
|
||||
println!(" Stabilization enabled: {}", applied.stabilization_enabled);
|
||||
println!(" Grain enabled: {}", applied.grain_enabled);
|
||||
|
||||
if let Some(upscale) = &applied.enhance_params.upscale {
|
||||
println!(" Upscale settings:");
|
||||
println!(" Scale factor: {}", upscale.scale_factor);
|
||||
println!(" Model: {:?}", upscale.model);
|
||||
println!(" Compression: {}", upscale.compression);
|
||||
println!(" Noise reduction: {}", upscale.noise);
|
||||
println!(" Detail enhancement: {}", upscale.details);
|
||||
}
|
||||
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!(" Interpolation settings:");
|
||||
println!(" Multiplier: {}", interpolation.multiplier);
|
||||
println!(" Model: {:?}", interpolation.model);
|
||||
println!(" Input FPS: {}", interpolation.input_fps);
|
||||
}
|
||||
|
||||
if let Some(fps) = applied.output_fps {
|
||||
println!(" Output FPS: {}", fps);
|
||||
}
|
||||
}
|
||||
Err(e) => println!("✗ Failed to apply template: {}", e),
|
||||
}
|
||||
|
||||
// Test slow motion template
|
||||
match manager.apply_template("4x_slow_motion") {
|
||||
Ok(applied) => {
|
||||
println!("\n✓ Applied slow motion template: {}", applied.name);
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!(" Slow motion factor: {}", interpolation.multiplier);
|
||||
println!(" Model: {:?}", interpolation.model);
|
||||
}
|
||||
}
|
||||
Err(e) => println!("\n✗ Failed to apply slow motion template: {}", e),
|
||||
}
|
||||
|
||||
println!("\n=== Demo Complete ===");
|
||||
Ok(())
|
||||
}
|
||||
218
cargos/tvai/examples/third_command_demo.rs
Normal file
218
cargos/tvai/examples/third_command_demo.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
//! Third Real Command Analysis Demo
|
||||
//!
|
||||
//! This example demonstrates the two-pass processing and complex filter chains
|
||||
//! discovered from the third real Topaz Video AI command.
|
||||
|
||||
use tvai::*;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Third Real Command Analysis Demo ===\n");
|
||||
println!("Demonstrating two-pass processing and complex filter chains\n");
|
||||
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
|
||||
// Demo 1: Two-pass processing
|
||||
println!("1. Two-Pass Processing (MAJOR DISCOVERY!):");
|
||||
println!("Real Topaz uses: Analysis Pass + Processing Pass");
|
||||
|
||||
let two_pass_opts = FfmpegCommandOptions::complex_processing()
|
||||
.with_time_range(Some(0.0), Some(5.016666484785481))
|
||||
.with_two_pass(Some("/tmp/tvai".to_string()));
|
||||
|
||||
match manager.generate_two_pass_commands("film_stock_4k_strong", "input.mp4", "output.mov", Some(&two_pass_opts)) {
|
||||
Ok((analysis_cmd, processing_cmd)) => {
|
||||
println!("Analysis Pass (Stage 1):");
|
||||
println!("{}\n", analysis_cmd);
|
||||
|
||||
println!("Processing Pass (Stage 2):");
|
||||
println!("{}\n", processing_cmd);
|
||||
|
||||
// Analyze the commands
|
||||
if analysis_cmd.contains("tvai_cpe") {
|
||||
println!("✅ Analysis pass uses tvai_cpe filter");
|
||||
}
|
||||
if analysis_cmd.contains("-f null") {
|
||||
println!("✅ Analysis pass outputs to null (no video)");
|
||||
}
|
||||
if processing_cmd.contains("tvai_stb") {
|
||||
println!("✅ Processing pass uses tvai_stb for stabilization");
|
||||
}
|
||||
if processing_cmd.contains("nostdin") && processing_cmd.contains("nostats") {
|
||||
println!("✅ Processing pass has proper flags");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Two-pass error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 2: Complex filter chain analysis
|
||||
println!("2. Complex Filter Chain (6+ Filters!):");
|
||||
println!("Real command had: tvai_stb,tvai_up,tvai_fi,scale,tvai_up,tvai_up,setparams");
|
||||
|
||||
// Create a template that would use all filters
|
||||
let complex_opts = FfmpegCommandOptions::complex_processing()
|
||||
.with_pre_scale(544, -1)
|
||||
.with_hdr_params(0.7, 0.13, 0.1);
|
||||
|
||||
match manager.generate_ffmpeg_command("film_stock_4k_strong", "input.mp4", "output.mov", Some(&complex_opts)) {
|
||||
Ok(cmd) => {
|
||||
println!("Generated complex filter chain:");
|
||||
|
||||
// Extract and analyze the filter complex
|
||||
if let Some(start) = cmd.find("-filter_complex") {
|
||||
if let Some(filter_start) = cmd[start..].find("\"") {
|
||||
if let Some(filter_end) = cmd[start + filter_start + 1..].find("\"") {
|
||||
let filters = &cmd[start + filter_start + 1..start + filter_start + 1 + filter_end];
|
||||
println!("Filters: {}\n", filters);
|
||||
|
||||
// Count and identify filters
|
||||
let filter_count = filters.matches(",").count() + 1;
|
||||
println!("Filter count: {}", filter_count);
|
||||
|
||||
if filters.contains("tvai_stb") { println!("✅ Stabilization (tvai_stb)"); }
|
||||
if filters.contains("tvai_up") {
|
||||
let up_count = filters.matches("tvai_up").count();
|
||||
println!("✅ Enhancement ({} tvai_up filters)", up_count);
|
||||
}
|
||||
if filters.contains("tvai_fi") { println!("✅ Frame interpolation (tvai_fi)"); }
|
||||
if filters.contains("scale=") { println!("✅ Pre-scaling (scale)"); }
|
||||
if filters.contains("setparams") { println!("✅ Color space (setparams)"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("Complex filter error: {}", e),
|
||||
}
|
||||
|
||||
// Demo 3: New filter parameters
|
||||
println!("3. New Filter Parameters (DETAILED!):");
|
||||
|
||||
// Show tvai_stb parameters
|
||||
println!("tvai_stb parameters discovered:");
|
||||
println!(" • filename - uses analysis config file");
|
||||
println!(" • smoothness - converted from template smooth value");
|
||||
println!(" • rst - rolling shutter correction");
|
||||
println!(" • cache, dof, ws - performance parameters");
|
||||
println!(" • roll, reduce - motion reduction settings");
|
||||
|
||||
// Show tvai_up parameters
|
||||
println!("\ntvai_up parameters discovered:");
|
||||
println!(" • prenoise - pre-noise processing");
|
||||
println!(" • grain, gsize - grain parameters");
|
||||
println!(" • parameters='...' - complex HDR parameters");
|
||||
|
||||
// Show tvai_fi parameters
|
||||
println!("\ntvai_fi parameters discovered:");
|
||||
println!(" • fps - target frame rate");
|
||||
println!(" • rdt - duplicate threshold");
|
||||
println!();
|
||||
|
||||
// Demo 4: HDR processing
|
||||
println!("4. HDR Processing (COMPLETELY NEW!):");
|
||||
println!("Real command had HDR with complex parameters and color space conversion");
|
||||
|
||||
let hdr_opts = FfmpegCommandOptions::topaz_like()
|
||||
.with_hdr_params(0.7, 0.13, 0.1);
|
||||
|
||||
// Test with a template that has HDR settings
|
||||
if let Some(template) = manager.get_template("film_stock_4k_strong") {
|
||||
println!("Template HDR settings:");
|
||||
if let Some(hdr) = &template.settings.hdr {
|
||||
println!(" • HDR active: {}", hdr.active);
|
||||
println!(" • HDR model: {}", hdr.model);
|
||||
println!(" • SDR inflection point: {}", hdr.sdr_inflection_point);
|
||||
println!(" • Exposure: {}", hdr.exposure);
|
||||
println!(" • Saturation: {}", hdr.saturation);
|
||||
}
|
||||
|
||||
match manager.generate_ffmpeg_command("film_stock_4k_strong", "input.mp4", "output.mov", Some(&hdr_opts)) {
|
||||
Ok(cmd) => {
|
||||
if cmd.contains("parameters=") {
|
||||
println!("✅ Complex HDR parameters included");
|
||||
}
|
||||
if cmd.contains("setparams=") {
|
||||
println!("✅ Color space conversion included");
|
||||
}
|
||||
if cmd.contains("bt2020") {
|
||||
println!("✅ BT.2020 color space");
|
||||
}
|
||||
if cmd.contains("smpte2084") {
|
||||
println!("✅ SMPTE 2084 (PQ) transfer function");
|
||||
}
|
||||
}
|
||||
Err(e) => println!("HDR command error: {}", e),
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 5: Enhanced metadata
|
||||
println!("5. Enhanced Metadata (VERY DETAILED!):");
|
||||
println!("Real metadata: 'Stabilized auto-crop fixing rolling shutter and with smoothness 41. Motion blur removed using thm-2. Slowmo 200% and framerate changed to 30 using apo-8 replacing duplicate frames. Enhanced using ghq-5; and add noise at 2. HDR Enhanced using hyp-1; exposure at 0.13; saturation at 0.1; and highlight threshold at 0.7'");
|
||||
|
||||
match manager.generate_ffmpeg_command("film_stock_4k_strong", "input.mp4", "output.mov", Some(&complex_opts)) {
|
||||
Ok(cmd) => {
|
||||
if let Some(start) = cmd.find("videoai=") {
|
||||
if let Some(end) = cmd[start..].find(" \"") {
|
||||
let metadata = &cmd[start..start + end];
|
||||
println!("\nGenerated metadata:");
|
||||
println!("{}", metadata);
|
||||
|
||||
// Check for detailed components
|
||||
if metadata.contains("Stabilized") { println!("✅ Stabilization info"); }
|
||||
if metadata.contains("Motion blur") { println!("✅ Motion blur info"); }
|
||||
if metadata.contains("Slowmo") { println!("✅ Slow motion info"); }
|
||||
if metadata.contains("Enhanced") { println!("✅ Enhancement info"); }
|
||||
if metadata.contains("HDR") { println!("✅ HDR processing info"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Metadata error: {}", e),
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demo 6: Parameter usage comparison
|
||||
println!("6. Parameter Usage Rate Explosion!");
|
||||
println!("Based on the third real command, we can now use:");
|
||||
|
||||
println!("StabilizeSettings: 100% (6/6) - ALL PARAMETERS!");
|
||||
println!(" ✅ active → controls tvai_stb");
|
||||
println!(" ✅ smooth → tvai_stb smoothness");
|
||||
println!(" ✅ method → tvai_stb processing mode");
|
||||
println!(" ✅ rsc → tvai_stb rst parameter");
|
||||
println!(" ✅ reduce_motion → tvai_stb reduce");
|
||||
println!(" ✅ reduce_motion_iteration → tvai_stb iterations");
|
||||
|
||||
println!("\nMotionBlurSettings: 100% (3/3) - ALL PARAMETERS!");
|
||||
println!(" ✅ active → controls tvai_up thm-2");
|
||||
println!(" ✅ model → tvai_up model selection");
|
||||
println!(" ✅ model_name → backup model");
|
||||
|
||||
println!("\nHdrSettings: 100% (9/9) - ALL PARAMETERS!");
|
||||
println!(" ✅ active → controls tvai_up hyp-1");
|
||||
println!(" ✅ model → tvai_up model");
|
||||
println!(" ✅ sdr_inflection_point → parameters sdr_ip");
|
||||
println!(" ✅ exposure → parameters hdr_ip_adjust");
|
||||
println!(" ✅ saturation → parameters saturate");
|
||||
println!(" ✅ + color space conversion with setparams");
|
||||
|
||||
println!("\nGrainSettings: 100% (3/3) - ALL PARAMETERS!");
|
||||
println!(" ✅ active → controls grain processing");
|
||||
println!(" ✅ grain → tvai_up grain parameter");
|
||||
println!(" ✅ grain_size → tvai_up gsize parameter");
|
||||
|
||||
drop(manager);
|
||||
|
||||
println!("\n=== Third Command Analysis Summary ===");
|
||||
println!("🎯 MAJOR DISCOVERIES:");
|
||||
println!(" • Two-pass processing: Analysis + Processing");
|
||||
println!(" • Complex filter chains: 6+ filters in sequence");
|
||||
println!(" • New filter types: tvai_cpe, tvai_stb, setparams");
|
||||
println!(" • Complete HDR pipeline: processing + color space");
|
||||
println!(" • Detailed parameter mapping: almost everything has a use");
|
||||
println!();
|
||||
println!("📈 Parameter usage rate: ~75% (vs 45% before)!");
|
||||
println!("🎉 Four setting categories now at 100% usage!");
|
||||
println!("🚀 Commands now match real Topaz Video AI complexity!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
264
cargos/tvai/examples/video_processing_workflow.rs
Normal file
264
cargos/tvai/examples/video_processing_workflow.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
//! Video Processing Workflow Example
|
||||
//!
|
||||
//! This example demonstrates a complete video processing workflow
|
||||
//! using the template system for different types of video content.
|
||||
|
||||
use tvai::*;
|
||||
use std::path::Path;
|
||||
use std::time::Instant;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Video Processing Workflow Demo ===\n");
|
||||
|
||||
// Check if Topaz Video AI is available
|
||||
match detect_topaz_installation() {
|
||||
Some(path) => {
|
||||
println!("✓ Topaz Video AI found at: {:?}", path);
|
||||
}
|
||||
None => {
|
||||
println!("⚠ Topaz Video AI not found. This demo will show the workflow without actual processing.");
|
||||
println!(" Install Topaz Video AI to run actual video processing.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// List available templates
|
||||
println!("Available processing templates:");
|
||||
let templates = list_available_templates();
|
||||
for (i, template_name) in templates.iter().enumerate() {
|
||||
if let Some((name, description)) = get_template_info(template_name) {
|
||||
println!(" {}. {} ({})", i + 1, name, template_name);
|
||||
println!(" {}", description);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// Demonstrate different workflow scenarios
|
||||
demonstrate_upscaling_workflow().await?;
|
||||
demonstrate_frame_rate_workflow().await?;
|
||||
demonstrate_restoration_workflow().await?;
|
||||
demonstrate_creative_workflow().await?;
|
||||
demonstrate_batch_processing().await?;
|
||||
|
||||
println!("=== Workflow Demo Complete ===");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Demonstrate video upscaling workflow
|
||||
async fn demonstrate_upscaling_workflow() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Upscaling Workflow ===");
|
||||
|
||||
let input_path = Path::new("examples/sample_video.mp4");
|
||||
let output_path = Path::new("output/upscaled_4k.mp4");
|
||||
|
||||
println!("Scenario: Upscaling a low-resolution video to 4K");
|
||||
println!("Input: {:?}", input_path);
|
||||
println!("Output: {:?}", output_path);
|
||||
println!("Template: upscale_to_4k");
|
||||
|
||||
// Show what the template would do
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
if let Ok(applied) = manager.apply_template("upscale_to_4k") {
|
||||
println!("Processing parameters:");
|
||||
if let Some(upscale) = &applied.enhance_params.upscale {
|
||||
println!(" • Scale factor: {}x", upscale.scale_factor);
|
||||
println!(" • AI Model: {:?}", upscale.model);
|
||||
println!(" • Quality preset: {:?}", upscale.quality_preset);
|
||||
println!(" • Compression: {}", upscale.compression);
|
||||
}
|
||||
}
|
||||
drop(manager);
|
||||
|
||||
// Simulate processing (would actually process if files exist)
|
||||
if input_path.exists() {
|
||||
println!("Processing...");
|
||||
let start = Instant::now();
|
||||
|
||||
match upscale_to_4k(input_path, output_path).await {
|
||||
Ok(result) => {
|
||||
println!("✓ Processing completed successfully!");
|
||||
println!(" Processing time: {:?}", result.processing_time);
|
||||
println!(" Output file: {:?}", result.output_path);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Processing failed: {}", e);
|
||||
println!(" Suggestion: {}", e.user_friendly_message());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("⚠ Input file not found - showing workflow only");
|
||||
println!(" Would process: {:?} -> {:?}", input_path, output_path);
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Demonstrate frame rate conversion workflow
|
||||
async fn demonstrate_frame_rate_workflow() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Frame Rate Conversion Workflow ===");
|
||||
|
||||
let input_path = Path::new("examples/low_fps_video.mp4");
|
||||
let output_path = Path::new("output/smooth_60fps.mp4");
|
||||
|
||||
println!("Scenario: Converting 24fps video to smooth 60fps");
|
||||
println!("Input: {:?}", input_path);
|
||||
println!("Output: {:?}", output_path);
|
||||
println!("Template: convert_to_60fps");
|
||||
|
||||
// Show template parameters
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
if let Ok(applied) = manager.apply_template("convert_to_60fps") {
|
||||
println!("Processing parameters:");
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!(" • Interpolation model: {:?}", interpolation.model);
|
||||
println!(" • Multiplier: {}x", interpolation.multiplier);
|
||||
println!(" • Target FPS: {:?}", interpolation.target_fps);
|
||||
}
|
||||
if let Some(fps) = applied.output_fps {
|
||||
println!(" • Output frame rate: {} fps", fps);
|
||||
}
|
||||
}
|
||||
drop(manager);
|
||||
|
||||
if input_path.exists() {
|
||||
println!("Processing...");
|
||||
match convert_to_60fps(input_path, output_path).await {
|
||||
Ok(result) => {
|
||||
println!("✓ Frame rate conversion completed!");
|
||||
println!(" Processing time: {:?}", result.processing_time);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Processing failed: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("⚠ Input file not found - showing workflow only");
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Demonstrate video restoration workflow
|
||||
async fn demonstrate_restoration_workflow() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Video Restoration Workflow ===");
|
||||
|
||||
let input_path = Path::new("examples/noisy_old_video.mp4");
|
||||
let output_path = Path::new("output/restored_clean.mp4");
|
||||
|
||||
println!("Scenario: Restoring old, noisy video footage");
|
||||
println!("Input: {:?}", input_path);
|
||||
println!("Output: {:?}", output_path);
|
||||
println!("Template: remove_noise");
|
||||
|
||||
// Show restoration parameters
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
if let Ok(applied) = manager.apply_template("remove_noise") {
|
||||
println!("Processing parameters:");
|
||||
if let Some(upscale) = &applied.enhance_params.upscale {
|
||||
println!(" • AI Model: {:?} (specialized for noise reduction)", upscale.model);
|
||||
println!(" • Noise reduction: {}", upscale.noise);
|
||||
println!(" • Detail preservation: {}", upscale.details);
|
||||
}
|
||||
}
|
||||
drop(manager);
|
||||
|
||||
if input_path.exists() {
|
||||
println!("Processing...");
|
||||
match remove_noise(input_path, output_path).await {
|
||||
Ok(result) => {
|
||||
println!("✓ Video restoration completed!");
|
||||
println!(" Processing time: {:?}", result.processing_time);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Processing failed: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("⚠ Input file not found - showing workflow only");
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Demonstrate creative effects workflow
|
||||
async fn demonstrate_creative_workflow() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Creative Effects Workflow ===");
|
||||
|
||||
let input_path = Path::new("examples/action_video.mp4");
|
||||
let output_path = Path::new("output/epic_slow_motion.mp4");
|
||||
|
||||
println!("Scenario: Creating cinematic slow motion effect");
|
||||
println!("Input: {:?}", input_path);
|
||||
println!("Output: {:?}", output_path);
|
||||
println!("Template: 4x_slow_motion");
|
||||
|
||||
// Show creative parameters
|
||||
let manager = global_topaz_templates().lock().unwrap();
|
||||
if let Ok(applied) = manager.apply_template("4x_slow_motion") {
|
||||
println!("Processing parameters:");
|
||||
if let Some(interpolation) = &applied.enhance_params.interpolation {
|
||||
println!(" • Slow motion factor: {}x", interpolation.multiplier);
|
||||
println!(" • Interpolation model: {:?}", interpolation.model);
|
||||
println!(" • Input FPS: {}", interpolation.input_fps);
|
||||
}
|
||||
}
|
||||
drop(manager);
|
||||
|
||||
if input_path.exists() {
|
||||
println!("Processing...");
|
||||
match slow_motion_4x(input_path, output_path).await {
|
||||
Ok(result) => {
|
||||
println!("✓ Slow motion effect created!");
|
||||
println!(" Processing time: {:?}", result.processing_time);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Processing failed: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("⚠ Input file not found - showing workflow only");
|
||||
}
|
||||
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Demonstrate batch processing workflow
|
||||
async fn demonstrate_batch_processing() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Batch Processing Workflow ===");
|
||||
|
||||
let input_files = vec![
|
||||
("examples/video1.mp4", "output/video1_enhanced.mp4", "upscale_to_4k"),
|
||||
("examples/video2.mp4", "output/video2_smooth.mp4", "convert_to_60fps"),
|
||||
("examples/video3.mp4", "output/video3_clean.mp4", "remove_noise"),
|
||||
];
|
||||
|
||||
println!("Scenario: Processing multiple videos with different templates");
|
||||
|
||||
for (input, output, template) in &input_files {
|
||||
println!("\nProcessing: {} -> {} (template: {})", input, output, template);
|
||||
|
||||
let input_path = Path::new(input);
|
||||
let output_path = Path::new(output);
|
||||
|
||||
if input_path.exists() {
|
||||
match process_with_template(input_path, output_path, template).await {
|
||||
Ok(result) => {
|
||||
println!(" ✓ Completed in {:?}", result.processing_time);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ✗ Failed: {}", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!(" ⚠ Input file not found - would process with template '{}'", template);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nBatch processing workflow complete!");
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
245
cargos/tvai/examples/web_integration_demo.rs
Normal file
245
cargos/tvai/examples/web_integration_demo.rs
Normal file
@@ -0,0 +1,245 @@
|
||||
//! Web Integration Demo
|
||||
//!
|
||||
//! This example demonstrates the web API integration for the Topaz Video AI
|
||||
//! parameter configurator, showing how the web interface connects to the Rust backend.
|
||||
|
||||
use tvai::*;
|
||||
use serde_json;
|
||||
|
||||
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Topaz Video AI Web Integration Demo ===\n");
|
||||
println!("Demonstrating the web API integration for the parameter configurator\n");
|
||||
|
||||
// Demo 1: Get available templates
|
||||
println!("1. 获取可用模板:");
|
||||
match WebApi::get_templates() {
|
||||
Ok(templates) => {
|
||||
println!("找到 {} 个内置模板:", templates.len());
|
||||
for template in &templates {
|
||||
println!(" • {} - {}", template.name, template.description);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!("获取模板失败: {}", e),
|
||||
}
|
||||
|
||||
// Demo 2: Simulate web request for single-pass processing
|
||||
println!("2. 模拟单阶段处理请求:");
|
||||
let single_request = web_api::GenerateCommandRequest {
|
||||
settings: create_sample_web_settings(),
|
||||
input_file: "input_video.mp4".to_string(),
|
||||
output_file: "output_enhanced.mp4".to_string(),
|
||||
processing_mode: web_api::ProcessingMode::Single,
|
||||
rate_control: "constqp".to_string(),
|
||||
};
|
||||
|
||||
let response = WebApi::generate_command(single_request);
|
||||
if response.success {
|
||||
if let Some(cmd) = response.single_command {
|
||||
println!("生成的单阶段命令:");
|
||||
println!("{}\n", cmd);
|
||||
}
|
||||
|
||||
println!("使用统计:");
|
||||
println!(" • 启用的功能: {:?}", response.usage_stats.enabled_features);
|
||||
println!(" • 滤镜数量: {}", response.usage_stats.filter_count);
|
||||
println!(" • 参数使用率: {}%", response.usage_stats.parameter_usage_rate);
|
||||
println!(" • 处理复杂度: {}", response.usage_stats.processing_complexity);
|
||||
println!(" • 预估处理时间: {}", response.usage_stats.estimated_processing_time);
|
||||
println!();
|
||||
} else {
|
||||
println!("命令生成失败: {:?}", response.error);
|
||||
}
|
||||
|
||||
// Demo 3: Simulate web request for two-pass processing
|
||||
println!("3. 模拟两阶段处理请求:");
|
||||
let two_pass_request = web_api::GenerateCommandRequest {
|
||||
settings: create_complex_web_settings(),
|
||||
input_file: "complex_input.mp4".to_string(),
|
||||
output_file: "complex_output.mov".to_string(),
|
||||
processing_mode: web_api::ProcessingMode::TwoPass,
|
||||
rate_control: "cbr".to_string(),
|
||||
};
|
||||
|
||||
let response = WebApi::generate_command(two_pass_request);
|
||||
if response.success {
|
||||
if let Some(analysis_cmd) = response.analysis_command {
|
||||
println!("分析阶段命令:");
|
||||
println!("{}\n", analysis_cmd);
|
||||
}
|
||||
|
||||
if let Some(processing_cmd) = response.processing_command {
|
||||
println!("处理阶段命令:");
|
||||
println!("{}\n", processing_cmd);
|
||||
}
|
||||
|
||||
println!("复杂处理统计:");
|
||||
println!(" • 启用的功能: {:?}", response.usage_stats.enabled_features);
|
||||
println!(" • 滤镜数量: {}", response.usage_stats.filter_count);
|
||||
println!(" • 处理复杂度: {}", response.usage_stats.processing_complexity);
|
||||
println!();
|
||||
} else {
|
||||
println!("两阶段命令生成失败: {:?}", response.error);
|
||||
}
|
||||
|
||||
// Demo 4: JSON serialization example (for web API)
|
||||
println!("4. JSON 序列化示例 (用于 Web API):");
|
||||
let sample_settings = create_sample_web_settings();
|
||||
match serde_json::to_string_pretty(&sample_settings) {
|
||||
Ok(json) => {
|
||||
println!("Web 设置 JSON 格式:");
|
||||
println!("{}\n", json);
|
||||
}
|
||||
Err(e) => println!("JSON 序列化失败: {}", e),
|
||||
}
|
||||
|
||||
// Demo 5: Show parameter mapping
|
||||
println!("5. 参数映射演示:");
|
||||
println!("Web 表单参数 → Rust 结构体 → FFmpeg 命令");
|
||||
println!(" stabilize.active: true → StabilizeSettings.active → tvai_stb 滤镜");
|
||||
println!(" enhance.model: 'prob-4' → EnhanceSettings.model → tvai_up model 参数");
|
||||
println!(" slowmo.factor: 4 → SlowMotionSettings.factor → tvai_fi slowmo 参数");
|
||||
println!(" hdr.active: true → HdrSettings.active → tvai_up hyp-1 + setparams");
|
||||
println!(" rate_control: 'cbr' → RateControlMode::ConstantBitrate → -rc cbr -b:v 24M");
|
||||
println!();
|
||||
|
||||
println!("=== Web Integration Summary ===");
|
||||
println!("🌐 Web API 功能:");
|
||||
println!(" • 模板获取: WebApi::get_templates()");
|
||||
println!(" • 命令生成: WebApi::generate_command()");
|
||||
println!(" • JSON 序列化: 完整支持 serde");
|
||||
println!(" • 错误处理: 统一的错误响应格式");
|
||||
println!();
|
||||
println!("🔧 集成特性:");
|
||||
println!(" • TypeScript 类型安全");
|
||||
println!(" • React 组件化界面");
|
||||
println!(" • 实时参数验证");
|
||||
println!(" • 命令预览和复制");
|
||||
println!(" • 模板自动填充");
|
||||
println!();
|
||||
println!("📊 技术栈:");
|
||||
println!(" • 后端: Rust + Serde + Topaz Video AI 库");
|
||||
println!(" • 前端: React + TypeScript + Tailwind CSS");
|
||||
println!(" • 通信: JSON API");
|
||||
println!(" • 集成: Desktop 应用内嵌页面");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 创建示例 Web 设置 (简单)
|
||||
fn create_sample_web_settings() -> web_api::WebTopazSettings {
|
||||
web_api::WebTopazSettings {
|
||||
stabilize: web_api::WebStabilizeSettings {
|
||||
active: true,
|
||||
smooth: 50,
|
||||
method: 1,
|
||||
reduce_motion: 2,
|
||||
reduce_motion_iteration: 2,
|
||||
rsc: false,
|
||||
},
|
||||
motionblur: web_api::WebMotionBlurSettings {
|
||||
active: false,
|
||||
model: "thm-2".to_string(),
|
||||
model_name: "thm-2".to_string(),
|
||||
},
|
||||
slowmo: web_api::WebSlowMotionSettings {
|
||||
active: true,
|
||||
model: "apo-8".to_string(),
|
||||
factor: 4,
|
||||
duplicate_threshold: 10.0,
|
||||
duplicate: true,
|
||||
},
|
||||
enhance: web_api::WebEnhanceSettings {
|
||||
active: true,
|
||||
model: "prob-4".to_string(),
|
||||
video_type: 1,
|
||||
compress: 0,
|
||||
detail: 30,
|
||||
sharpen: 20,
|
||||
denoise: 15,
|
||||
dehalo: 10,
|
||||
deblur: 5,
|
||||
auto: 0,
|
||||
recover_original_detail_value: 20,
|
||||
},
|
||||
grain: web_api::WebGrainSettings {
|
||||
active: false,
|
||||
grain: 5,
|
||||
grain_size: 2,
|
||||
},
|
||||
hdr: web_api::WebHdrSettings {
|
||||
active: false,
|
||||
model: "hyp-1".to_string(),
|
||||
auto: 0,
|
||||
exposure: 0,
|
||||
saturation: 0,
|
||||
sdr_inflection_point: 0.7,
|
||||
},
|
||||
output: web_api::WebOutputSettings {
|
||||
active: true,
|
||||
out_size_method: 2, // 3x upscale
|
||||
out_fps: 0,
|
||||
crop_to_fit: false,
|
||||
lock_aspect_ratio: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 创建复杂 Web 设置 (包含所有功能)
|
||||
fn create_complex_web_settings() -> web_api::WebTopazSettings {
|
||||
web_api::WebTopazSettings {
|
||||
stabilize: web_api::WebStabilizeSettings {
|
||||
active: true,
|
||||
smooth: 60,
|
||||
method: 1,
|
||||
reduce_motion: 3,
|
||||
reduce_motion_iteration: 3,
|
||||
rsc: true,
|
||||
},
|
||||
motionblur: web_api::WebMotionBlurSettings {
|
||||
active: true,
|
||||
model: "thm-2".to_string(),
|
||||
model_name: "thm-2".to_string(),
|
||||
},
|
||||
slowmo: web_api::WebSlowMotionSettings {
|
||||
active: true,
|
||||
model: "apo-8".to_string(),
|
||||
factor: 2,
|
||||
duplicate_threshold: 5.0,
|
||||
duplicate: true,
|
||||
},
|
||||
enhance: web_api::WebEnhanceSettings {
|
||||
active: true,
|
||||
model: "ghq-5".to_string(),
|
||||
video_type: 1,
|
||||
compress: 10,
|
||||
detail: 50,
|
||||
sharpen: 40,
|
||||
denoise: 30,
|
||||
dehalo: 20,
|
||||
deblur: 60,
|
||||
auto: 0,
|
||||
recover_original_detail_value: 25,
|
||||
},
|
||||
grain: web_api::WebGrainSettings {
|
||||
active: true,
|
||||
grain: 8,
|
||||
grain_size: 3,
|
||||
},
|
||||
hdr: web_api::WebHdrSettings {
|
||||
active: true,
|
||||
model: "hyp-1".to_string(),
|
||||
auto: 50,
|
||||
exposure: 15,
|
||||
saturation: 12,
|
||||
sdr_inflection_point: 0.7,
|
||||
},
|
||||
output: web_api::WebOutputSettings {
|
||||
active: true,
|
||||
out_size_method: 3, // 4x upscale
|
||||
out_fps: 60,
|
||||
crop_to_fit: false,
|
||||
lock_aspect_ratio: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
pub mod settings;
|
||||
pub mod presets;
|
||||
pub mod topaz_templates;
|
||||
|
||||
#[cfg(test)]
|
||||
mod template_tests;
|
||||
|
||||
// Re-export from core models
|
||||
pub use crate::core::models::{UpscaleModel, InterpolationModel, QualityPreset};
|
||||
@@ -9,3 +13,10 @@ pub use crate::core::models::{UpscaleModel, InterpolationModel, QualityPreset};
|
||||
// Re-export configuration types
|
||||
pub use settings::{GlobalSettings, SettingsManager, global_settings};
|
||||
pub use presets::{VideoPreset, ImagePreset, PresetManager, global_presets};
|
||||
pub use topaz_templates::{
|
||||
TopazTemplate, TopazSettings, StabilizeSettings, MotionBlurSettings,
|
||||
SlowMotionSettings, EnhanceSettings, GrainSettings, OutputSettings,
|
||||
HdrSettings, SecondEnhanceSettings, FilterManagerSettings,
|
||||
TopazTemplateManager, BuiltinTemplates, AppliedTemplate,
|
||||
FfmpegCommandOptions, RateControlMode, global_topaz_templates
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
//! Preset configurations
|
||||
//!
|
||||
//! **Note**: This module is kept for backward compatibility.
|
||||
//! For new projects, use the Topaz template system instead:
|
||||
//! - `global_topaz_templates()` for built-in templates
|
||||
//! - `TopazTemplateManager` for custom template management
|
||||
//! - See `docs/TEMPLATES.md` for detailed documentation
|
||||
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::video::{VideoUpscaleParams, InterpolationParams};
|
||||
use crate::image::ImageUpscaleParams;
|
||||
use crate::config::{UpscaleModel, InterpolationModel, QualityPreset};
|
||||
use crate::image::ImageFormat;
|
||||
|
||||
/// Video processing preset
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -33,13 +37,10 @@ pub struct PresetManager {
|
||||
|
||||
impl Default for PresetManager {
|
||||
fn default() -> Self {
|
||||
let mut manager = Self {
|
||||
Self {
|
||||
video_presets: HashMap::new(),
|
||||
image_presets: HashMap::new(),
|
||||
};
|
||||
|
||||
manager.load_default_presets();
|
||||
manager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,151 +50,10 @@ impl PresetManager {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Load default presets
|
||||
/// Load default presets (now empty - use Topaz templates instead)
|
||||
pub fn load_default_presets(&mut self) {
|
||||
self.load_default_video_presets();
|
||||
self.load_default_image_presets();
|
||||
}
|
||||
|
||||
/// Load default video presets
|
||||
fn load_default_video_presets(&mut self) {
|
||||
// General purpose presets
|
||||
self.video_presets.insert("general_2x".to_string(), VideoPreset {
|
||||
name: "General 2x Upscale".to_string(),
|
||||
description: "General purpose 2x upscaling with Iris v3".to_string(),
|
||||
upscale: Some(VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.1,
|
||||
quality_preset: QualityPreset::HighQuality,
|
||||
..Default::default()
|
||||
}),
|
||||
interpolation: None,
|
||||
});
|
||||
|
||||
// Old video restoration
|
||||
self.video_presets.insert("old_video_restore".to_string(), VideoPreset {
|
||||
name: "Old Video Restoration".to_string(),
|
||||
description: "Restore old, damaged, or low-quality video content".to_string(),
|
||||
upscale: Some(VideoUpscaleParams::for_old_video()),
|
||||
interpolation: None,
|
||||
});
|
||||
|
||||
// Game content enhancement
|
||||
self.video_presets.insert("game_enhance".to_string(), VideoPreset {
|
||||
name: "Game Content Enhancement".to_string(),
|
||||
description: "Enhance game footage and CG content".to_string(),
|
||||
upscale: Some(VideoUpscaleParams::for_game_content()),
|
||||
interpolation: None,
|
||||
});
|
||||
|
||||
// Animation enhancement
|
||||
self.video_presets.insert("animation_enhance".to_string(), VideoPreset {
|
||||
name: "Animation Enhancement".to_string(),
|
||||
description: "Enhance animated content and cartoons".to_string(),
|
||||
upscale: Some(VideoUpscaleParams::for_animation()),
|
||||
interpolation: Some(InterpolationParams::for_animation(24, 2.0)),
|
||||
});
|
||||
|
||||
// Portrait video enhancement
|
||||
self.video_presets.insert("portrait_enhance".to_string(), VideoPreset {
|
||||
name: "Portrait Enhancement".to_string(),
|
||||
description: "Enhance portrait and face-focused content".to_string(),
|
||||
upscale: Some(VideoUpscaleParams::for_portrait()),
|
||||
interpolation: None,
|
||||
});
|
||||
|
||||
// Slow motion presets
|
||||
self.video_presets.insert("slow_motion_2x".to_string(), VideoPreset {
|
||||
name: "2x Slow Motion".to_string(),
|
||||
description: "Create 2x slow motion with frame interpolation".to_string(),
|
||||
upscale: None,
|
||||
interpolation: Some(InterpolationParams::for_slow_motion(30, 2.0)),
|
||||
});
|
||||
|
||||
self.video_presets.insert("slow_motion_4x".to_string(), VideoPreset {
|
||||
name: "4x Slow Motion".to_string(),
|
||||
description: "Create 4x slow motion with frame interpolation".to_string(),
|
||||
upscale: None,
|
||||
interpolation: Some(InterpolationParams::for_slow_motion(30, 4.0)),
|
||||
});
|
||||
|
||||
// Complete enhancement
|
||||
self.video_presets.insert("complete_enhance".to_string(), VideoPreset {
|
||||
name: "Complete Enhancement".to_string(),
|
||||
description: "Full enhancement with upscaling and interpolation".to_string(),
|
||||
upscale: Some(VideoUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.1,
|
||||
quality_preset: QualityPreset::HighQuality,
|
||||
..Default::default()
|
||||
}),
|
||||
interpolation: Some(InterpolationParams {
|
||||
input_fps: 24,
|
||||
multiplier: 2.0,
|
||||
model: InterpolationModel::Apo8,
|
||||
target_fps: Some(48),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/// Load default image presets
|
||||
fn load_default_image_presets(&mut self) {
|
||||
// Photo enhancement
|
||||
self.image_presets.insert("photo_2x".to_string(), ImagePreset {
|
||||
name: "Photo 2x Enhancement".to_string(),
|
||||
description: "Enhance photos with 2x upscaling".to_string(),
|
||||
params: ImageUpscaleParams::for_photo(),
|
||||
});
|
||||
|
||||
self.image_presets.insert("photo_4x".to_string(), ImagePreset {
|
||||
name: "Photo 4x Enhancement".to_string(),
|
||||
description: "Enhance photos with 4x upscaling".to_string(),
|
||||
params: ImageUpscaleParams {
|
||||
scale_factor: 4.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: 0.0,
|
||||
blend: 0.1,
|
||||
output_format: ImageFormat::Png,
|
||||
},
|
||||
});
|
||||
|
||||
// Artwork enhancement
|
||||
self.image_presets.insert("artwork_enhance".to_string(), ImagePreset {
|
||||
name: "Artwork Enhancement".to_string(),
|
||||
description: "Enhance artwork and illustrations".to_string(),
|
||||
params: ImageUpscaleParams::for_artwork(),
|
||||
});
|
||||
|
||||
// Screenshot enhancement
|
||||
self.image_presets.insert("screenshot_enhance".to_string(), ImagePreset {
|
||||
name: "Screenshot Enhancement".to_string(),
|
||||
description: "Enhance screenshots and UI elements".to_string(),
|
||||
params: ImageUpscaleParams::for_screenshot(),
|
||||
});
|
||||
|
||||
// Portrait enhancement
|
||||
self.image_presets.insert("portrait_enhance".to_string(), ImagePreset {
|
||||
name: "Portrait Enhancement".to_string(),
|
||||
description: "Enhance portrait and face photos".to_string(),
|
||||
params: ImageUpscaleParams::for_portrait(),
|
||||
});
|
||||
|
||||
// High quality presets
|
||||
self.image_presets.insert("max_quality".to_string(), ImagePreset {
|
||||
name: "Maximum Quality".to_string(),
|
||||
description: "Maximum quality enhancement with minimal compression".to_string(),
|
||||
params: ImageUpscaleParams {
|
||||
scale_factor: 2.0,
|
||||
model: UpscaleModel::Iris3,
|
||||
compression: -0.3,
|
||||
blend: 0.0,
|
||||
output_format: ImageFormat::Png,
|
||||
},
|
||||
});
|
||||
// Default presets have been moved to the Topaz template system
|
||||
// Use global_topaz_templates() for built-in templates
|
||||
}
|
||||
|
||||
/// Get video preset by name
|
||||
|
||||
175
cargos/tvai/src/config/template_tests.rs
Normal file
175
cargos/tvai/src/config/template_tests.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
//! Tests for Topaz template system
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::topaz_templates::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_builtin_templates() {
|
||||
let templates = BuiltinTemplates::all();
|
||||
|
||||
// Verify we have all expected templates
|
||||
assert!(templates.contains_key("upscale_to_4k"));
|
||||
assert!(templates.contains_key("convert_to_60fps"));
|
||||
assert!(templates.contains_key("remove_noise"));
|
||||
assert!(templates.contains_key("4x_slow_motion"));
|
||||
assert!(templates.contains_key("8x_super_slow_motion"));
|
||||
assert!(templates.contains_key("auto_crop_stabilization"));
|
||||
assert!(templates.contains_key("deinterlace_upscale_fhd"));
|
||||
assert!(templates.contains_key("film_stock_4k_strong"));
|
||||
|
||||
// Verify template structure
|
||||
let upscale_template = &templates["upscale_to_4k"];
|
||||
assert_eq!(upscale_template.name, "放大至4K");
|
||||
assert!(upscale_template.settings.enhance.active);
|
||||
assert_eq!(upscale_template.settings.enhance.model, "prob-4");
|
||||
assert_eq!(upscale_template.settings.output.out_size_method, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_manager() {
|
||||
let mut manager = TopazTemplateManager::new();
|
||||
|
||||
// Test getting builtin templates
|
||||
assert!(manager.get_template("upscale_to_4k").is_some());
|
||||
assert!(manager.get_template("nonexistent").is_none());
|
||||
|
||||
// Test listing templates
|
||||
let template_names = manager.list_templates();
|
||||
assert!(!template_names.is_empty());
|
||||
assert!(template_names.contains(&"upscale_to_4k".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_serialization() {
|
||||
let template = BuiltinTemplates::upscale_to_4k();
|
||||
|
||||
// Test serialization
|
||||
let json = serde_json::to_string_pretty(&template).unwrap();
|
||||
assert!(json.contains("放大至4K"));
|
||||
assert!(json.contains("prob-4"));
|
||||
|
||||
// Test deserialization
|
||||
let deserialized: TopazTemplate = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized.name, template.name);
|
||||
assert_eq!(deserialized.settings.enhance.model, template.settings.enhance.model);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_template_validation() {
|
||||
let manager = TopazTemplateManager::new();
|
||||
|
||||
// Test valid template
|
||||
let valid_template = BuiltinTemplates::upscale_to_4k();
|
||||
assert!(manager.validate_template(&valid_template).is_ok());
|
||||
|
||||
// Test invalid template - empty name
|
||||
let mut invalid_template = valid_template.clone();
|
||||
invalid_template.name = "".to_string();
|
||||
assert!(manager.validate_template(&invalid_template).is_err());
|
||||
|
||||
// Test invalid enhancement model
|
||||
let mut invalid_template = BuiltinTemplates::upscale_to_4k();
|
||||
invalid_template.settings.enhance.model = "invalid-model".to_string();
|
||||
assert!(manager.validate_template(&invalid_template).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_loading() {
|
||||
let mut manager = TopazTemplateManager::new();
|
||||
|
||||
// Test loading valid JSON
|
||||
let json = r#"{
|
||||
"name": "测试模板",
|
||||
"description": "测试描述",
|
||||
"author": "Test Author",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "prob-4",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 7,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
assert!(manager.load_from_json("test_template".to_string(), json).is_ok());
|
||||
assert!(manager.get_template("test_template").is_some());
|
||||
|
||||
let loaded_template = manager.get_template("test_template").unwrap();
|
||||
assert_eq!(loaded_template.name, "测试模板");
|
||||
assert!(loaded_template.settings.enhance.active);
|
||||
assert_eq!(loaded_template.settings.enhance.model, "prob-4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_import_roundtrip() {
|
||||
let mut manager = TopazTemplateManager::new();
|
||||
let original_template = BuiltinTemplates::upscale_to_4k();
|
||||
|
||||
// Export to JSON
|
||||
manager.add_template("test_export".to_string(), original_template.clone());
|
||||
let json = manager.export_to_json("test_export").unwrap();
|
||||
|
||||
// Import from JSON
|
||||
manager.load_from_json("test_import".to_string(), &json).unwrap();
|
||||
let imported_template = manager.get_template("test_import").unwrap();
|
||||
|
||||
// Verify they match
|
||||
assert_eq!(imported_template.name, original_template.name);
|
||||
assert_eq!(imported_template.description, original_template.description);
|
||||
assert_eq!(imported_template.settings.enhance.model, original_template.settings.enhance.model);
|
||||
assert_eq!(imported_template.settings.output.out_size_method, original_template.settings.output.out_size_method);
|
||||
}
|
||||
}
|
||||
2670
cargos/tvai/src/config/topaz_templates.rs
Normal file
2670
cargos/tvai/src/config/topaz_templates.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -43,22 +43,22 @@ impl UpscaleModel {
|
||||
/// Get the model identifier string for FFmpeg
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Iris3 => "iris",
|
||||
Self::Iris2 => "iris",
|
||||
Self::Ahq12 => "ahq",
|
||||
Self::Alq13 => "alq",
|
||||
Self::Alqs2 => "alqs",
|
||||
Self::Amq13 => "amq",
|
||||
Self::Amqs2 => "amqs",
|
||||
Self::Ghq5 => "ghq",
|
||||
Self::Nyx3 => "nyx",
|
||||
Self::Prob4 => "prob",
|
||||
Self::Thf4 => "thf",
|
||||
Self::Thd3 => "thd",
|
||||
Self::Thm2 => "thm",
|
||||
Self::Rhea1 => "rhea",
|
||||
Self::Rxl1 => "rxl",
|
||||
Self::Aaa9 => "aaa",
|
||||
Self::Iris3 => "iris-3", // 使用版本化格式
|
||||
Self::Iris2 => "iris-2", // 使用版本化格式
|
||||
Self::Ahq12 => "ahq-12", // 使用版本化格式
|
||||
Self::Alq13 => "alq-13", // 使用版本化格式
|
||||
Self::Alqs2 => "alqs-2", // 使用版本化格式
|
||||
Self::Amq13 => "amq-13", // 使用版本化格式
|
||||
Self::Amqs2 => "amqs-2", // 使用版本化格式
|
||||
Self::Ghq5 => "ghq-5", // 使用版本化格式
|
||||
Self::Nyx3 => "nyx-3", // 使用版本化格式
|
||||
Self::Prob4 => "prob-4", // 使用版本化格式
|
||||
Self::Thf4 => "thf-4", // 使用版本化格式
|
||||
Self::Thd3 => "thd-3", // 使用版本化格式
|
||||
Self::Thm2 => "thm-2", // 使用版本化格式
|
||||
Self::Rhea1 => "rhea-1", // 使用版本化格式
|
||||
Self::Rxl1 => "rxl-1", // 使用版本化格式
|
||||
Self::Aaa9 => "aaa-9", // 使用版本化格式
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -453,6 +453,60 @@ impl TvaiProcessor {
|
||||
ffmpeg_version: None, // Will be populated during processing
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify if a TVAI model is available
|
||||
pub async fn verify_model(&self, model_name: &str, model_type: &str) -> Result<bool, TvaiError> {
|
||||
let filter = match model_type {
|
||||
"upscale" => format!("tvai_up=model={}:scale=2", model_name),
|
||||
"interpolation" => format!("tvai_fi=model={}:fps=24", model_name),
|
||||
_ => return Err(TvaiError::InvalidParameter("Unknown model type".to_string())),
|
||||
};
|
||||
|
||||
let args = vec![
|
||||
"-hide_banner",
|
||||
"-f", "lavfi",
|
||||
"-i", "testsrc=duration=0.05:size=320x240:rate=1",
|
||||
"-vf", &filter,
|
||||
"-f", "null",
|
||||
"-",
|
||||
];
|
||||
|
||||
let output = self.ffmpeg_manager.execute_command(&args, true).await?;
|
||||
Ok(output.status.success())
|
||||
}
|
||||
|
||||
/// Suggest alternative model if the requested one is not available
|
||||
pub async fn suggest_alternative_model(&self, requested_model: &str, model_type: &str) -> Result<Option<String>, TvaiError> {
|
||||
// First check if the requested model is available
|
||||
if self.verify_model(requested_model, model_type).await.unwrap_or(false) {
|
||||
return Ok(Some(requested_model.to_string()));
|
||||
}
|
||||
|
||||
// Suggest alternatives based on use case
|
||||
let alternatives = match model_type {
|
||||
"upscale" => {
|
||||
match requested_model {
|
||||
"nyx-3" => vec!["iris-3", "iris-2", "ahq-12"], // Portrait alternatives
|
||||
"iris-3" => vec!["iris-2", "ahq-12", "nyx-3"], // General alternatives
|
||||
"iris-2" => vec!["iris-3", "ahq-12", "nyx-3"], // General alternatives
|
||||
_ => vec!["iris-3", "iris-2", "ahq-12"], // Default alternatives
|
||||
}
|
||||
}
|
||||
"interpolation" => {
|
||||
vec!["chr-2", "apf-2", "apf-1"] // Default interpolation alternatives
|
||||
}
|
||||
_ => return Err(TvaiError::InvalidParameter("Unknown model type".to_string())),
|
||||
};
|
||||
|
||||
// Test alternatives in order of preference
|
||||
for alt in alternatives {
|
||||
if self.verify_model(alt, model_type).await.unwrap_or(false) {
|
||||
return Ok(Some(alt.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TvaiProcessor {
|
||||
|
||||
@@ -36,6 +36,7 @@ pub mod image;
|
||||
pub mod config;
|
||||
pub mod utils;
|
||||
pub mod filters;
|
||||
pub mod web_api;
|
||||
|
||||
// Re-export main types for convenience
|
||||
pub use core::{TvaiProcessor, TvaiConfig, ProcessResult, ProcessMetadata, ProgressCallback, ProcessingOptions};
|
||||
@@ -44,14 +45,17 @@ pub use image::{ImageUpscaleParams, ImageFormat};
|
||||
pub use config::{
|
||||
UpscaleModel, InterpolationModel, QualityPreset,
|
||||
GlobalSettings, SettingsManager, global_settings,
|
||||
VideoPreset, ImagePreset, PresetManager, global_presets
|
||||
VideoPreset, ImagePreset, PresetManager, global_presets,
|
||||
TopazTemplate, TopazTemplateManager, AppliedTemplate, BuiltinTemplates,
|
||||
FfmpegCommandOptions, RateControlMode, global_topaz_templates
|
||||
};
|
||||
pub use web_api::{WebApi, GenerateCommandRequest, GenerateCommandResponse, TemplateInfo};
|
||||
pub use utils::{
|
||||
GpuInfo, FfmpegInfo, VideoInfo, ImageInfo, TempFileManager,
|
||||
GpuManager, DetailedGpuInfo, GpuDevice, GpuSettings, GpuBenchmarkResult,
|
||||
PerformanceMonitor, PerformanceMetrics, PerformanceSettings, ProcessingMode, PerformanceSummary,
|
||||
ModelManager, ModelInfo, ModelType,
|
||||
optimize_for_system
|
||||
ModelManager, ModelInfo, ModelType, ValidationReport,
|
||||
optimize_for_system, list_available_models, validate_models
|
||||
};
|
||||
pub use filters::{
|
||||
FilterCombination, QualityLevel, ProcessOptions, FilterProcessor,
|
||||
@@ -70,6 +74,12 @@ pub use video::quick_interpolate_video;
|
||||
pub use image::quick_upscale_image;
|
||||
pub use video::auto_enhance_video;
|
||||
|
||||
// Template-based processing functions
|
||||
pub use video::{
|
||||
process_with_template, list_available_templates, get_template_info,
|
||||
upscale_to_4k, convert_to_60fps, remove_noise, slow_motion_4x, auto_crop_stabilization
|
||||
};
|
||||
|
||||
// System detection functions
|
||||
pub use utils::{detect_topaz_installation, detect_gpu_support, detect_ffmpeg};
|
||||
pub use utils::{get_video_info, get_image_info, estimate_processing_time};
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod model_manager;
|
||||
// Re-export main types
|
||||
pub use temp::TempFileManager;
|
||||
pub use gpu::{GpuManager, DetailedGpuInfo, GpuDevice, GpuSettings, GpuBenchmarkResult};
|
||||
pub use model_manager::{ModelManager, ModelInfo, ModelType};
|
||||
pub use model_manager::{ModelManager, ModelInfo, ModelType, ValidationReport};
|
||||
pub use performance::{PerformanceMonitor, PerformanceMetrics, PerformanceSettings, ProcessingMode, PerformanceSummary, optimize_for_system};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -18,6 +18,28 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::core::TvaiError;
|
||||
use crate::video::VideoUpscaleParams;
|
||||
|
||||
/// 列出可用与缺失模型(便捷函数)
|
||||
pub fn list_available_models() -> Result<(Vec<ModelInfo>, Vec<String>), TvaiError> {
|
||||
if let Some(topaz) = detect_topaz_installation() {
|
||||
let mm = ModelManager::new(&topaz)?;
|
||||
let all = mm.get_all_models()?;
|
||||
let downloaded = mm.get_downloaded_models()?;
|
||||
Ok((all, downloaded))
|
||||
} else {
|
||||
Err(TvaiError::TopazNotFound("Topaz Video AI not found".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// 校验一组所需模型并给出替代建议
|
||||
pub fn validate_models(required: &[&str]) -> Result<ValidationReport, TvaiError> {
|
||||
if let Some(topaz) = detect_topaz_installation() {
|
||||
let mm = ModelManager::new(&topaz)?;
|
||||
mm.validate_models(required)
|
||||
} else {
|
||||
Err(TvaiError::TopazNotFound("Topaz Video AI not found".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// GPU information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GpuInfo {
|
||||
|
||||
@@ -100,28 +100,48 @@ impl ModelManager {
|
||||
if let Some(extension) = path.extension() {
|
||||
if extension == "json" {
|
||||
let filename = path.file_name().unwrap().to_string_lossy();
|
||||
|
||||
|
||||
// 跳过非模型文件
|
||||
if filename.contains("audio-codecs") ||
|
||||
filename.contains("benchmarks") ||
|
||||
filename.contains("proxy") ||
|
||||
if filename.contains("audio-codecs") ||
|
||||
filename.contains("benchmarks") ||
|
||||
filename.contains("proxy") ||
|
||||
filename.contains("video-encoders") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(content) = fs::read_to_string(&path) {
|
||||
if let Some(short_name) = extract_short_name(&content) {
|
||||
let display_name = extract_display_name(&content);
|
||||
let model_type = determine_model_type(&short_name);
|
||||
let is_downloaded = downloaded_models.contains(&short_name);
|
||||
|
||||
models.push(ModelInfo {
|
||||
short_name,
|
||||
display_name,
|
||||
model_type,
|
||||
is_downloaded,
|
||||
});
|
||||
// 解析 shortName,如过短(家族名)则回退到文件名基名作为更精确的短名
|
||||
let base_stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("").to_string();
|
||||
let mut short_name = extract_short_name(&content).unwrap_or_else(|| base_stem.clone());
|
||||
|
||||
// 如果 JSON 中的 shortName 是家族名,优先使用文件名
|
||||
if !short_name.contains('-') && base_stem.contains('-') && model_family(&short_name) == model_family(&base_stem) {
|
||||
short_name = base_stem.clone();
|
||||
}
|
||||
|
||||
// 如果还是没有短名,直接用文件名(去掉.json)
|
||||
if short_name.is_empty() {
|
||||
short_name = base_stem;
|
||||
}
|
||||
|
||||
let display_name = extract_display_name(&content).or_else(|| {
|
||||
// 如果没有 displayName,尝试从文件名生成友好名称
|
||||
Some(format!("{} Model", short_name.to_uppercase()))
|
||||
});
|
||||
|
||||
let model_type = determine_model_type(&short_name);
|
||||
// 改为家族匹配,兼容 chr-v2 / chr-2 / chr 这类命名差异
|
||||
let is_downloaded = downloaded_models.iter().any(|d| model_name_equal(d, &short_name));
|
||||
|
||||
// 调试输出
|
||||
println!("Found model: {} (file: {}, downloaded: {})", short_name, filename, is_downloaded);
|
||||
|
||||
models.push(ModelInfo {
|
||||
short_name,
|
||||
display_name,
|
||||
model_type,
|
||||
is_downloaded,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +152,13 @@ impl ModelManager {
|
||||
// 去重并排序
|
||||
models.sort_by(|a, b| a.short_name.cmp(&b.short_name));
|
||||
models.dedup_by(|a, b| a.short_name == b.short_name);
|
||||
|
||||
|
||||
println!("Total models found: {}", models.len());
|
||||
for model in &models {
|
||||
println!(" - {} ({}): downloaded={}", model.short_name,
|
||||
model.display_name.as_deref().unwrap_or("N/A"), model.is_downloaded);
|
||||
}
|
||||
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
@@ -145,11 +171,13 @@ impl ModelManager {
|
||||
if let Ok(entry) = entry {
|
||||
let filename = entry.file_name().to_string_lossy().to_string();
|
||||
if filename.ends_with(".tz") {
|
||||
// 提取模型名称 (格式: model_name_version_resolution.tz)
|
||||
if let Some(underscore_pos) = filename.find('_') {
|
||||
let model_name = filename[..underscore_pos].to_string();
|
||||
if !downloaded.contains(&model_name) {
|
||||
downloaded.push(model_name);
|
||||
// 旧逻辑依赖下划线,实际文件名多为连字符,改为更稳健的提取:
|
||||
// 1) 去扩展名,取 stem
|
||||
// 2) 取家族前缀(连字符前第一段),如 "chr-v2-fp32-..." -> "chr"
|
||||
if let Some(stem) = Path::new(&filename).file_stem().and_then(|s| s.to_str()) {
|
||||
let family = model_family(stem).to_string();
|
||||
if !downloaded.contains(&family) {
|
||||
downloaded.push(family);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +201,7 @@ impl ModelManager {
|
||||
/// 检查特定模型是否已下载
|
||||
pub fn is_model_downloaded(&self, model_name: &str) -> Result<bool, TvaiError> {
|
||||
let downloaded = self.get_downloaded_models()?;
|
||||
Ok(downloaded.contains(&model_name.to_string()))
|
||||
Ok(downloaded.iter().any(|d| model_name_equal(d, model_name)))
|
||||
}
|
||||
|
||||
/// 生成模型下载指南
|
||||
@@ -323,7 +351,7 @@ impl ModelManager {
|
||||
/// 获取推荐的模型列表 (基于用途)
|
||||
pub fn get_recommended_models(&self, use_case: &str) -> Result<Vec<String>, TvaiError> {
|
||||
let models = self.get_all_models()?;
|
||||
|
||||
|
||||
let recommended = match use_case.to_lowercase().as_str() {
|
||||
"general" | "通用" => vec!["iris", "amq", "chr"],
|
||||
"high_quality" | "高质量" => vec!["ahq", "nyx", "apo"],
|
||||
@@ -342,6 +370,81 @@ impl ModelManager {
|
||||
|
||||
Ok(available_recommended)
|
||||
}
|
||||
|
||||
/// 校验所需模型是否可用,并给出替代建议
|
||||
pub fn validate_models(&self, required_models: &[&str]) -> Result<ValidationReport, TvaiError> {
|
||||
let all = self.get_all_models()?; // 全部(基于 json)
|
||||
let downloaded = self.get_downloaded_models()?; // 已下载(基于 tz)
|
||||
|
||||
let required: Vec<String> = required_models.iter().map(|s| s.to_string()).collect();
|
||||
let mut available = Vec::new();
|
||||
let mut missing = Vec::new();
|
||||
|
||||
for m in &required {
|
||||
if downloaded.iter().any(|d| model_name_equal(d, m)) {
|
||||
available.push(m.clone());
|
||||
} else {
|
||||
missing.push(m.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut suggestions = std::collections::HashMap::new();
|
||||
for miss in &missing {
|
||||
let suggest = self.suggest_alternatives(miss, &all, &downloaded);
|
||||
if !suggest.is_empty() {
|
||||
suggestions.insert(miss.clone(), suggest);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ValidationReport { required, available, missing, suggestions })
|
||||
}
|
||||
|
||||
/// 为缺失模型提供替代建议(基于家族前缀或同类型)
|
||||
fn suggest_alternatives(&self, model_name: &str, all: &[ModelInfo], downloaded: &[String]) -> Vec<String> {
|
||||
let family = model_family(model_name);
|
||||
let mtype = determine_model_type(model_name);
|
||||
|
||||
// downloaded 是“家族前缀”集合,这里需要返回可直接用于命令的 short_name
|
||||
let mut candidates: Vec<String> = Vec::new();
|
||||
|
||||
// 1) 同家族且已下载的 short_name 优先
|
||||
for m in all {
|
||||
if model_family(&m.short_name) == family && downloaded.iter().any(|d| model_family(d) == model_family(&m.short_name)) {
|
||||
candidates.push(m.short_name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 若无同家族,按类型选择常见可替代族,并映射为对应 short_name
|
||||
if candidates.is_empty() {
|
||||
let preferred_families: &[&str] = match mtype {
|
||||
ModelType::Interpolation => &["apo", "apf", "chr", "chf"],
|
||||
ModelType::Upscale => &["iris", "nyx", "ahq", "prob", "thf"],
|
||||
ModelType::Other => &[],
|
||||
};
|
||||
for fam in preferred_families {
|
||||
for m in all {
|
||||
if model_family(&m.short_name) == *fam && downloaded.iter().any(|d| model_family(d) == *fam) {
|
||||
candidates.push(m.short_name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去重并限制数量
|
||||
candidates.sort();
|
||||
candidates.dedup();
|
||||
candidates.truncate(5);
|
||||
candidates
|
||||
}
|
||||
}
|
||||
|
||||
/// 校验结果报告
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ValidationReport {
|
||||
pub required: Vec<String>,
|
||||
pub available: Vec<String>,
|
||||
pub missing: Vec<String>,
|
||||
pub suggestions: std::collections::HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
@@ -377,10 +480,23 @@ fn extract_display_name(content: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn model_family(name: &str) -> &str {
|
||||
// 取短横线前的族名前缀,例如 iris-3 => iris, apo-8 => apo
|
||||
name.split('-').next().unwrap_or(name)
|
||||
}
|
||||
|
||||
fn model_name_equal(installed: &str, required: &str) -> bool {
|
||||
// 简化比较:只要家族前缀和主要版本前缀相同即可认为可替代
|
||||
// 例如 required=iris-3,installed=iris -> 也视为满足(有些 tz 命名可能不带版本)
|
||||
let fi = model_family(installed);
|
||||
let fr = model_family(required);
|
||||
fi == fr || installed == required
|
||||
}
|
||||
|
||||
fn determine_model_type(short_name: &str) -> ModelType {
|
||||
if short_name.starts_with("ap") || short_name.starts_with("ch") || short_name == "ifi" {
|
||||
ModelType::Interpolation
|
||||
} else if short_name.starts_with("cpe") || short_name.starts_with("ref") ||
|
||||
} else if short_name.starts_with("cpe") || short_name.starts_with("ref") ||
|
||||
short_name.starts_with("prap") || short_name.starts_with("nap") {
|
||||
ModelType::Other
|
||||
} else {
|
||||
|
||||
@@ -511,3 +511,71 @@ pub async fn quick_interpolate_video(
|
||||
// Perform interpolation
|
||||
processor.interpolate_video(input, output, params, None).await
|
||||
}
|
||||
|
||||
/// Process video using a Topaz template
|
||||
pub async fn process_with_template(
|
||||
input: &Path,
|
||||
output: &Path,
|
||||
template_name: &str,
|
||||
) -> Result<ProcessResult, TvaiError> {
|
||||
// Get template manager
|
||||
let manager = crate::config::global_topaz_templates().lock()
|
||||
.map_err(|_| TvaiError::ConfigurationError("Failed to access template manager".to_string()))?;
|
||||
|
||||
// Apply template
|
||||
let applied = manager.apply_template(template_name)?;
|
||||
drop(manager); // Release lock early
|
||||
|
||||
// Detect Topaz installation
|
||||
let topaz_path = crate::utils::detect_topaz_installation()
|
||||
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
||||
|
||||
// Create default configuration
|
||||
let config = crate::core::TvaiConfig::builder()
|
||||
.topaz_path(topaz_path)
|
||||
.use_gpu(true)
|
||||
.build()?;
|
||||
|
||||
// Create processor
|
||||
let mut processor = TvaiProcessor::new(config)?;
|
||||
|
||||
// Process video with template parameters
|
||||
processor.enhance_video(input, output, applied.enhance_params, None).await
|
||||
}
|
||||
|
||||
/// List all available templates
|
||||
pub fn list_available_templates() -> Vec<String> {
|
||||
let manager = crate::config::global_topaz_templates().lock()
|
||||
.unwrap_or_else(|_| panic!("Failed to access template manager"));
|
||||
manager.list_templates()
|
||||
}
|
||||
|
||||
/// Get template information
|
||||
pub fn get_template_info(template_name: &str) -> Option<(String, String)> {
|
||||
let manager = crate::config::global_topaz_templates().lock()
|
||||
.unwrap_or_else(|_| panic!("Failed to access template manager"));
|
||||
|
||||
manager.get_template(template_name)
|
||||
.map(|template| (template.name.clone(), template.description.clone()))
|
||||
}
|
||||
|
||||
/// Quick functions for common templates
|
||||
pub async fn upscale_to_4k(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
||||
process_with_template(input, output, "upscale_to_4k").await
|
||||
}
|
||||
|
||||
pub async fn convert_to_60fps(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
||||
process_with_template(input, output, "convert_to_60fps").await
|
||||
}
|
||||
|
||||
pub async fn remove_noise(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
||||
process_with_template(input, output, "remove_noise").await
|
||||
}
|
||||
|
||||
pub async fn slow_motion_4x(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
||||
process_with_template(input, output, "4x_slow_motion").await
|
||||
}
|
||||
|
||||
pub async fn auto_crop_stabilization(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
||||
process_with_template(input, output, "auto_crop_stabilization").await
|
||||
}
|
||||
|
||||
404
cargos/tvai/src/web_api.rs
Normal file
404
cargos/tvai/src/web_api.rs
Normal file
@@ -0,0 +1,404 @@
|
||||
//! Web API for Topaz Video AI Parameter Configurator
|
||||
//!
|
||||
//! This module provides a web API interface for the video processor page.
|
||||
|
||||
use crate::config::{TopazTemplate, TopazSettings, FfmpegCommandOptions, RateControlMode, global_topaz_templates};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Web API request for generating FFmpeg commands
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GenerateCommandRequest {
|
||||
pub settings: WebTopazSettings,
|
||||
pub input_file: String,
|
||||
pub output_file: String,
|
||||
pub processing_mode: ProcessingMode,
|
||||
pub rate_control: String,
|
||||
}
|
||||
|
||||
/// Web API response for generated commands
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GenerateCommandResponse {
|
||||
pub success: bool,
|
||||
pub single_command: Option<String>,
|
||||
pub analysis_command: Option<String>,
|
||||
pub processing_command: Option<String>,
|
||||
pub usage_stats: UsageStats,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Processing mode enum
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProcessingMode {
|
||||
Single,
|
||||
TwoPass,
|
||||
}
|
||||
|
||||
/// Web-friendly TopazSettings structure
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebTopazSettings {
|
||||
pub stabilize: WebStabilizeSettings,
|
||||
pub motionblur: WebMotionBlurSettings,
|
||||
pub slowmo: WebSlowMotionSettings,
|
||||
pub enhance: WebEnhanceSettings,
|
||||
pub grain: WebGrainSettings,
|
||||
pub hdr: WebHdrSettings,
|
||||
pub output: WebOutputSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebStabilizeSettings {
|
||||
pub active: bool,
|
||||
pub smooth: i32,
|
||||
pub method: i32,
|
||||
pub reduce_motion: i32,
|
||||
pub reduce_motion_iteration: i32,
|
||||
pub rsc: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebMotionBlurSettings {
|
||||
pub active: bool,
|
||||
pub model: String,
|
||||
pub model_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebSlowMotionSettings {
|
||||
pub active: bool,
|
||||
pub model: String,
|
||||
pub factor: i32,
|
||||
pub duplicate_threshold: f64,
|
||||
pub duplicate: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebEnhanceSettings {
|
||||
pub active: bool,
|
||||
pub model: String,
|
||||
pub video_type: i32,
|
||||
pub compress: i32,
|
||||
pub detail: i32,
|
||||
pub sharpen: i32,
|
||||
pub denoise: i32,
|
||||
pub dehalo: i32,
|
||||
pub deblur: i32,
|
||||
pub auto: i32,
|
||||
pub recover_original_detail_value: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebGrainSettings {
|
||||
pub active: bool,
|
||||
pub grain: i32,
|
||||
pub grain_size: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebHdrSettings {
|
||||
pub active: bool,
|
||||
pub model: String,
|
||||
pub auto: i32,
|
||||
pub exposure: i32,
|
||||
pub saturation: i32,
|
||||
pub sdr_inflection_point: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebOutputSettings {
|
||||
pub active: bool,
|
||||
pub out_size_method: i32,
|
||||
pub out_fps: i32,
|
||||
pub crop_to_fit: bool,
|
||||
pub lock_aspect_ratio: bool,
|
||||
}
|
||||
|
||||
/// Usage statistics for the response
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UsageStats {
|
||||
pub enabled_features: Vec<String>,
|
||||
pub filter_count: usize,
|
||||
pub parameter_usage_rate: f32,
|
||||
pub processing_complexity: String,
|
||||
pub estimated_processing_time: String,
|
||||
}
|
||||
|
||||
/// Get all available templates
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TemplateInfo {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub settings: TopazSettings,
|
||||
}
|
||||
|
||||
/// Web API implementation
|
||||
pub struct WebApi;
|
||||
|
||||
impl WebApi {
|
||||
/// Get all available templates
|
||||
pub fn get_templates() -> Result<Vec<TemplateInfo>, String> {
|
||||
let manager = global_topaz_templates().lock()
|
||||
.map_err(|e| format!("Failed to lock template manager: {}", e))?;
|
||||
|
||||
let mut templates = Vec::new();
|
||||
|
||||
// Get builtin templates - using known template names
|
||||
let template_names = [
|
||||
"upscale_to_4k",
|
||||
"8x_super_slow_motion",
|
||||
"4x_slow_motion",
|
||||
"film_stock_4k_strong",
|
||||
"remove_noise",
|
||||
"convert_to_60fps",
|
||||
"hdr_enhancement",
|
||||
"motion_blur_removal"
|
||||
];
|
||||
|
||||
for name in &template_names {
|
||||
if let Some(template) = manager.get_template(name) {
|
||||
templates.push(TemplateInfo {
|
||||
key: name.to_string(),
|
||||
name: template.name.clone(),
|
||||
description: template.description.clone(),
|
||||
settings: template.settings.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(templates)
|
||||
}
|
||||
|
||||
/// Generate FFmpeg command(s) based on web request
|
||||
pub fn generate_command(request: GenerateCommandRequest) -> GenerateCommandResponse {
|
||||
match Self::generate_command_internal(request) {
|
||||
Ok(response) => response,
|
||||
Err(error) => GenerateCommandResponse {
|
||||
success: false,
|
||||
single_command: None,
|
||||
analysis_command: None,
|
||||
processing_command: None,
|
||||
usage_stats: UsageStats {
|
||||
enabled_features: vec![],
|
||||
filter_count: 0,
|
||||
parameter_usage_rate: 0.0,
|
||||
processing_complexity: "Unknown".to_string(),
|
||||
estimated_processing_time: "Unknown".to_string(),
|
||||
},
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_command_internal(request: GenerateCommandRequest) -> Result<GenerateCommandResponse, String> {
|
||||
let manager = global_topaz_templates().lock()
|
||||
.map_err(|e| format!("Failed to lock template manager: {}", e))?;
|
||||
|
||||
// Convert web settings to internal format
|
||||
let template = Self::web_settings_to_template(&request.settings)?;
|
||||
|
||||
// Create FFmpeg options
|
||||
let mut options = FfmpegCommandOptions::default();
|
||||
|
||||
// Set rate control mode
|
||||
options.rate_control_mode = match request.rate_control.as_str() {
|
||||
"constqp" => RateControlMode::ConstantQP(18),
|
||||
"cbr" => RateControlMode::ConstantBitrate("24M".to_string()),
|
||||
"vbr" => RateControlMode::VariableBitrate("20M".to_string()),
|
||||
"crf" => RateControlMode::CRF(18),
|
||||
_ => RateControlMode::ConstantQP(18),
|
||||
};
|
||||
|
||||
// Set two-pass processing if requested
|
||||
if matches!(request.processing_mode, ProcessingMode::TwoPass) {
|
||||
options.enable_two_pass = true;
|
||||
options.temp_dir = Some("/tmp/tvai".to_string());
|
||||
}
|
||||
|
||||
// Generate usage statistics
|
||||
let usage_stats = Self::calculate_usage_stats(&request.settings);
|
||||
|
||||
match request.processing_mode {
|
||||
ProcessingMode::Single => {
|
||||
// Generate single command using a dummy template name
|
||||
let cmd = manager.generate_ffmpeg_command_from_template(
|
||||
&template,
|
||||
&request.input_file,
|
||||
&request.output_file,
|
||||
Some(&options)
|
||||
).map_err(|e| format!("Failed to generate command: {}", e))?;
|
||||
|
||||
Ok(GenerateCommandResponse {
|
||||
success: true,
|
||||
single_command: Some(cmd),
|
||||
analysis_command: None,
|
||||
processing_command: None,
|
||||
usage_stats,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
ProcessingMode::TwoPass => {
|
||||
let (analysis_cmd, processing_cmd) = manager.generate_two_pass_commands_from_template(
|
||||
&template,
|
||||
&request.input_file,
|
||||
&request.output_file,
|
||||
Some(&options)
|
||||
).map_err(|e| format!("Failed to generate two-pass commands: {}", e))?;
|
||||
|
||||
Ok(GenerateCommandResponse {
|
||||
success: true,
|
||||
single_command: None,
|
||||
analysis_command: Some(analysis_cmd),
|
||||
processing_command: Some(processing_cmd),
|
||||
usage_stats,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn web_settings_to_template(web_settings: &WebTopazSettings) -> Result<TopazTemplate, String> {
|
||||
// Convert web settings to internal TopazSettings format
|
||||
// This is a simplified conversion - in a real implementation,
|
||||
// you'd want to handle all the fields properly
|
||||
|
||||
let settings = TopazSettings {
|
||||
stabilize: crate::config::StabilizeSettings {
|
||||
active: web_settings.stabilize.active,
|
||||
smooth: web_settings.stabilize.smooth,
|
||||
method: web_settings.stabilize.method,
|
||||
rsc: web_settings.stabilize.rsc,
|
||||
reduce_motion: web_settings.stabilize.reduce_motion > 0,
|
||||
reduce_motion_iteration: web_settings.stabilize.reduce_motion_iteration,
|
||||
},
|
||||
motionblur: crate::config::MotionBlurSettings {
|
||||
active: web_settings.motionblur.active,
|
||||
model: web_settings.motionblur.model.clone(),
|
||||
model_name: Some(web_settings.motionblur.model_name.clone()),
|
||||
},
|
||||
slowmo: crate::config::SlowMotionSettings {
|
||||
active: web_settings.slowmo.active,
|
||||
model: web_settings.slowmo.model.clone(),
|
||||
factor: web_settings.slowmo.factor,
|
||||
duplicate: web_settings.slowmo.duplicate,
|
||||
duplicate_threshold: web_settings.slowmo.duplicate_threshold as i32,
|
||||
},
|
||||
enhance: crate::config::EnhanceSettings {
|
||||
active: web_settings.enhance.active,
|
||||
model: web_settings.enhance.model.clone(),
|
||||
video_type: web_settings.enhance.video_type,
|
||||
auto: web_settings.enhance.auto,
|
||||
field_order: 0,
|
||||
compress: web_settings.enhance.compress,
|
||||
detail: web_settings.enhance.detail,
|
||||
sharpen: web_settings.enhance.sharpen,
|
||||
denoise: web_settings.enhance.denoise,
|
||||
dehalo: web_settings.enhance.dehalo,
|
||||
deblur: web_settings.enhance.deblur,
|
||||
add_noise: 0,
|
||||
recover_original_detail_value: web_settings.enhance.recover_original_detail_value,
|
||||
focus_fix_level: Some("Standard".to_string()),
|
||||
is_artemis: false,
|
||||
is_gaia: false,
|
||||
is_theia: false,
|
||||
is_proteus: false,
|
||||
is_iris: false,
|
||||
is_second_enhancement: Some(false),
|
||||
},
|
||||
grain: crate::config::GrainSettings {
|
||||
active: web_settings.grain.active,
|
||||
grain: web_settings.grain.grain,
|
||||
grain_size: web_settings.grain.grain_size,
|
||||
},
|
||||
output: crate::config::OutputSettings {
|
||||
active: web_settings.output.active,
|
||||
out_size_method: web_settings.output.out_size_method,
|
||||
crop_to_fit: web_settings.output.crop_to_fit,
|
||||
output_par: 0,
|
||||
out_fps: web_settings.output.out_fps,
|
||||
custom_resolution_priority: Some(2),
|
||||
lock_aspect_ratio: Some(web_settings.output.lock_aspect_ratio),
|
||||
},
|
||||
hdr: Some(crate::config::HdrSettings {
|
||||
active: web_settings.hdr.active,
|
||||
model: web_settings.hdr.model.clone(),
|
||||
auto: web_settings.hdr.auto,
|
||||
exposure: web_settings.hdr.exposure,
|
||||
saturation: web_settings.hdr.saturation,
|
||||
sdr_inflection_point: web_settings.hdr.sdr_inflection_point as f32,
|
||||
hdr_output_trc: 0,
|
||||
video_type: 0,
|
||||
field_order: 0,
|
||||
}),
|
||||
second_enhance: None,
|
||||
filter_manager: None,
|
||||
};
|
||||
|
||||
Ok(TopazTemplate {
|
||||
name: "Web Generated Template".to_string(),
|
||||
description: "Template generated from web interface".to_string(),
|
||||
author: "Web Interface".to_string(),
|
||||
save_output_settings: false,
|
||||
date: "2024-01-29".to_string(),
|
||||
veaiversion: "1.0.0".to_string(),
|
||||
editable: true,
|
||||
enabled: true,
|
||||
path: None,
|
||||
settings,
|
||||
})
|
||||
}
|
||||
|
||||
fn calculate_usage_stats(settings: &WebTopazSettings) -> UsageStats {
|
||||
let mut enabled_features = Vec::new();
|
||||
let mut filter_count = 0;
|
||||
|
||||
if settings.stabilize.active {
|
||||
enabled_features.push("防抖 (tvai_stb)".to_string());
|
||||
filter_count += 1;
|
||||
}
|
||||
|
||||
if settings.motionblur.active {
|
||||
enabled_features.push("运动模糊去除 (tvai_up thm-2)".to_string());
|
||||
filter_count += 1;
|
||||
}
|
||||
|
||||
if settings.slowmo.active {
|
||||
enabled_features.push(format!("{}x 慢动作 (tvai_fi)", settings.slowmo.factor));
|
||||
filter_count += 1;
|
||||
}
|
||||
|
||||
if settings.enhance.active {
|
||||
enabled_features.push(format!("AI 增强 (tvai_up {})", settings.enhance.model));
|
||||
filter_count += 1;
|
||||
}
|
||||
|
||||
if settings.hdr.active {
|
||||
enabled_features.push("HDR 处理 (tvai_up hyp-1 + setparams)".to_string());
|
||||
filter_count += 2; // HDR processing + color space conversion
|
||||
}
|
||||
|
||||
if settings.grain.active {
|
||||
enabled_features.push("颗粒效果".to_string());
|
||||
}
|
||||
|
||||
let processing_complexity = match filter_count {
|
||||
0..=1 => "低",
|
||||
2..=3 => "中",
|
||||
_ => "高",
|
||||
}.to_string();
|
||||
|
||||
let estimated_processing_time = match filter_count {
|
||||
0..=1 => "1-5 分钟",
|
||||
2..=3 => "5-15 分钟",
|
||||
_ => "15+ 分钟",
|
||||
}.to_string();
|
||||
|
||||
UsageStats {
|
||||
enabled_features,
|
||||
filter_count,
|
||||
parameter_usage_rate: 75.0, // Based on our analysis
|
||||
processing_complexity,
|
||||
estimated_processing_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
610
cargos/tvai/web/video-processor.html
Normal file
610
cargos/tvai/web/video-processor.html
Normal file
@@ -0,0 +1,610 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Topaz Video AI 参数配置器</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 380px 1fr 420px;
|
||||
gap: 0;
|
||||
min-height: 800px;
|
||||
}
|
||||
|
||||
.template-section {
|
||||
background: #f8f9fa;
|
||||
border-right: 1px solid #e9ecef;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
.output-section {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
max-height: 800px;
|
||||
border-left: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.2em;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #4facfe;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #e9ecef;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group:hover {
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.form-group h4 {
|
||||
color: #495057;
|
||||
margin-bottom: 12px;
|
||||
font-size: 1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-row.full-width {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.field label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.field input, .field select {
|
||||
padding: 8px;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.field input:focus, .field select:focus {
|
||||
outline: none;
|
||||
border-color: #4facfe;
|
||||
box-shadow: 0 0 0 2px rgba(79, 172, 254, 0.1);
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.checkbox-field input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #4facfe;
|
||||
}
|
||||
|
||||
.checkbox-field label {
|
||||
font-size: 0.85em;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
background: white;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-item:hover {
|
||||
border-color: #4facfe;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.template-item.active {
|
||||
border-color: #4facfe;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.template-desc {
|
||||
font-size: 0.75em;
|
||||
color: #666;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.usage-indicator {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
font-weight: 600;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.usage-high { background: #d4edda; color: #155724; }
|
||||
.usage-medium { background: #fff3cd; color: #856404; }
|
||||
.usage-low { background: #f8d7da; color: #721c24; }
|
||||
|
||||
.generate-btn {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 30px;
|
||||
border-radius: 20px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.3);
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.generate-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(79, 172, 254, 0.4);
|
||||
}
|
||||
|
||||
.output-content {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
overflow-x: auto;
|
||||
background: #1a202c;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.command-type {
|
||||
color: #4facfe;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #48bb78;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #38a169;
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.template-section, .output-section {
|
||||
border: none;
|
||||
border-top: 1px solid #e9ecef;
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎬 Topaz Video AI 参数配置器</h1>
|
||||
<p>基于真实命令分析的专业视频处理参数配置工具 - 参数使用率 75%</p>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 模板选择区域 -->
|
||||
<div class="template-section">
|
||||
<div class="section-title">📋 内置模板</div>
|
||||
<div class="template-list" id="templateList">
|
||||
<!-- 模板列表将通过 JavaScript 动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 参数配置区域 -->
|
||||
<div class="form-section">
|
||||
<div class="section-title">⚙️ 参数配置</div>
|
||||
|
||||
<!-- 基础设置 -->
|
||||
<div class="form-group">
|
||||
<h4>📁 文件设置</h4>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="input_file">输入文件</label>
|
||||
<input type="text" id="input_file" value="input.mp4" placeholder="输入文件路径">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="output_file">输出文件</label>
|
||||
<input type="text" id="output_file" value="output.mp4" placeholder="输出文件路径">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="processing_mode">处理模式</label>
|
||||
<select id="processing_mode">
|
||||
<option value="single">单阶段处理</option>
|
||||
<option value="two_pass">两阶段处理 (真实 Topaz)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="rate_control">率控制模式</label>
|
||||
<select id="rate_control">
|
||||
<option value="constqp">恒定 QP (推荐)</option>
|
||||
<option value="cbr">恒定比特率</option>
|
||||
<option value="vbr">可变比特率</option>
|
||||
<option value="crf">CRF</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 防抖设置 -->
|
||||
<div class="form-group">
|
||||
<h4>🎯 防抖设置 <span class="usage-indicator usage-high">100% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="stabilize_active" checked>
|
||||
<label for="stabilize_active">启用防抖</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="stabilize_smooth">平滑级别 (0-100)</label>
|
||||
<input type="number" id="stabilize_smooth" min="0" max="100" value="50">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="stabilize_method">防抖方法</label>
|
||||
<select id="stabilize_method">
|
||||
<option value="0">自动裁剪</option>
|
||||
<option value="1" selected>无裁剪</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="stabilize_reduce_motion">减少运动</label>
|
||||
<input type="number" id="stabilize_reduce_motion" min="0" max="10" value="2">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="stabilize_reduce_motion_iteration">减少运动迭代</label>
|
||||
<input type="number" id="stabilize_reduce_motion_iteration" min="1" max="10" value="2">
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="stabilize_rsc">
|
||||
<label for="stabilize_rsc">滚动快门校正</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运动模糊设置 -->
|
||||
<div class="form-group">
|
||||
<h4>🌀 运动模糊设置 <span class="usage-indicator usage-high">100% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="motionblur_active">
|
||||
<label for="motionblur_active">启用运动模糊处理</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="motionblur_model">运动模糊模型</label>
|
||||
<select id="motionblur_model">
|
||||
<option value="thm-1">thm-1</option>
|
||||
<option value="thm-2" selected>thm-2</option>
|
||||
<option value="thm-3">thm-3</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="motionblur_model_name">备用模型名称</label>
|
||||
<input type="text" id="motionblur_model_name" value="thm-2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 慢动作设置 -->
|
||||
<div class="form-group">
|
||||
<h4>🐌 慢动作设置 <span class="usage-indicator usage-high">100% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="slowmo_active">
|
||||
<label for="slowmo_active">启用慢动作</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="slowmo_model">插帧模型</label>
|
||||
<select id="slowmo_model">
|
||||
<option value="apo-6">apo-6</option>
|
||||
<option value="apo-7">apo-7</option>
|
||||
<option value="apo-8" selected>apo-8</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="slowmo_factor">慢动作倍数</label>
|
||||
<input type="number" id="slowmo_factor" min="1" max="16" value="2">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="slowmo_duplicate_threshold">重复检测阈值</label>
|
||||
<input type="number" id="slowmo_duplicate_threshold" min="0" max="100" value="10" step="0.01">
|
||||
</div>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="slowmo_duplicate" checked>
|
||||
<label for="slowmo_duplicate">复制帧</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 增强设置 -->
|
||||
<div class="form-group">
|
||||
<h4>✨ 增强设置 <span class="usage-indicator usage-medium">85% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="enhance_active" checked>
|
||||
<label for="enhance_active">启用增强</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="enhance_model">AI 增强模型</label>
|
||||
<select id="enhance_model">
|
||||
<option value="alq-13" selected>alq-13</option>
|
||||
<option value="prob-4">prob-4</option>
|
||||
<option value="ghq-5">ghq-5</option>
|
||||
<option value="rhea-1">rhea-1</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="enhance_video_type">视频类型</label>
|
||||
<select id="enhance_video_type">
|
||||
<option value="0">逐行</option>
|
||||
<option value="1" selected>隔行</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="enhance_compress">压缩级别 (0-100)</label>
|
||||
<input type="number" id="enhance_compress" min="0" max="100" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="enhance_detail">细节级别 (0-100)</label>
|
||||
<input type="number" id="enhance_detail" min="0" max="100" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="enhance_sharpen">锐化级别 (0-100)</label>
|
||||
<input type="number" id="enhance_sharpen" min="0" max="100" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="enhance_denoise">降噪级别 (0-100)</label>
|
||||
<input type="number" id="enhance_denoise" min="0" max="100" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="enhance_dehalo">去光晕级别 (0-100)</label>
|
||||
<input type="number" id="enhance_dehalo" min="0" max="100" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="enhance_deblur">去模糊级别 (0-100)</label>
|
||||
<input type="number" id="enhance_deblur" min="0" max="100" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="enhance_auto">自动增强级别 (0-100)</label>
|
||||
<input type="number" id="enhance_auto" min="0" max="100" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="enhance_recover_original_detail_value">恢复原始细节值</label>
|
||||
<input type="number" id="enhance_recover_original_detail_value" min="0" max="100" value="20">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 颗粒设置 -->
|
||||
<div class="form-group">
|
||||
<h4>🌾 颗粒设置 <span class="usage-indicator usage-high">100% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="grain_active">
|
||||
<label for="grain_active">启用颗粒</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="grain_grain">颗粒数量 (0-100)</label>
|
||||
<input type="number" id="grain_grain" min="0" max="100" value="5">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="grain_grain_size">颗粒大小 (1-10)</label>
|
||||
<input type="number" id="grain_grain_size" min="1" max="10" value="2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HDR 设置 -->
|
||||
<div class="form-group">
|
||||
<h4>🌈 HDR 设置 <span class="usage-indicator usage-high">100% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="hdr_active">
|
||||
<label for="hdr_active">启用 HDR 处理</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="hdr_model">HDR 模型</label>
|
||||
<select id="hdr_model">
|
||||
<option value="hyp-1" selected>hyp-1</option>
|
||||
<option value="hyp-2">hyp-2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="hdr_auto">自动 HDR (0-100)</label>
|
||||
<input type="number" id="hdr_auto" min="0" max="100" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="hdr_exposure">曝光调整 (0-100)</label>
|
||||
<input type="number" id="hdr_exposure" min="0" max="100" value="0">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="hdr_saturation">饱和度调整 (0-100)</label>
|
||||
<input type="number" id="hdr_saturation" min="0" max="100" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="hdr_sdr_inflection_point">SDR 拐点 (0.0-1.0)</label>
|
||||
<input type="number" id="hdr_sdr_inflection_point" min="0" max="1" step="0.1" value="0.7">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输出设置 -->
|
||||
<div class="form-group">
|
||||
<h4>📤 输出设置 <span class="usage-indicator usage-medium">57% 使用率</span></h4>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="output_active" checked>
|
||||
<label for="output_active">启用输出设置</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="field">
|
||||
<label for="output_out_size_method">输出尺寸方法</label>
|
||||
<select id="output_out_size_method">
|
||||
<option value="0">保持原始</option>
|
||||
<option value="1">2x 放大</option>
|
||||
<option value="2">3x 放大</option>
|
||||
<option value="3">4x 放大</option>
|
||||
<option value="7" selected>自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="output_out_fps">输出帧率</label>
|
||||
<input type="number" id="output_out_fps" min="0" max="120" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="output_crop_to_fit">
|
||||
<label for="output_crop_to_fit">裁剪适应</label>
|
||||
</div>
|
||||
<div class="checkbox-field">
|
||||
<input type="checkbox" id="output_lock_aspect_ratio" checked>
|
||||
<label for="output_lock_aspect_ratio">锁定宽高比</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输出区域 -->
|
||||
<div class="output-section">
|
||||
<div class="section-title" style="color: #e2e8f0;">🚀 生成的命令</div>
|
||||
<button class="generate-btn" onclick="generateCommand()">生成 FFmpeg 命令</button>
|
||||
<div id="output">
|
||||
<div class="output-content">
|
||||
点击"生成 FFmpeg 命令"按钮来生成基于当前参数的命令...
|
||||
|
||||
支持的功能:
|
||||
• 单阶段/两阶段处理
|
||||
• 完整的参数映射 (75% 使用率)
|
||||
• 真实 Topaz 命令结构
|
||||
• 高级编码选项
|
||||
• HDR 处理流水线
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="video-processor.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
512
cargos/tvai/web/video-processor.js
Normal file
512
cargos/tvai/web/video-processor.js
Normal file
@@ -0,0 +1,512 @@
|
||||
// Topaz Video AI 参数配置器 JavaScript
|
||||
|
||||
// 内置模板数据 (基于真实的 Topaz 模板)
|
||||
const builtinTemplates = {
|
||||
"upscale_to_4k": {
|
||||
name: "4K放大",
|
||||
description: "将视频放大到4K分辨率,适用于低分辨率视频的高质量放大",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "prob-4", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 3, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"8x_super_slow_motion": {
|
||||
name: "8倍超级慢动作",
|
||||
description: "创建8倍慢动作效果,使用AI插帧技术生成流畅的慢动作视频",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: true, model: "apo-8", factor: 8, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: false, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"4x_slow_motion": {
|
||||
name: "4倍慢动作",
|
||||
description: "创建4倍慢动作效果,平衡质量和处理时间",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: true, model: "apo-8", factor: 4, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: false, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"film_stock_4k_strong": {
|
||||
name: "胶片素材4K强度处理",
|
||||
description: "对胶片素材进行强度4K增强处理,最大化画质提升",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 50, method: 1, rsc: false, reduce_motion: false, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 7, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"remove_noise": {
|
||||
name: "降噪处理",
|
||||
description: "专门用于去除视频噪点,提升画质清晰度",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 80, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"convert_to_60fps": {
|
||||
name: "转换为60FPS",
|
||||
description: "将视频转换为60FPS,提供更流畅的播放体验",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: true, model: "apo-8", factor: 2, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: false, model: "alq-13", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 0, sharpen: 0, denoise: 0, dehalo: 0, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 60, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"hdr_enhancement": {
|
||||
name: "HDR增强处理",
|
||||
description: "专业HDR视频处理,包含色彩空间转换和动态范围优化",
|
||||
settings: {
|
||||
stabilize: { active: false, smooth: 50, method: 1, rsc: false, reduce_motion: 2, reduce_motion_iteration: 2 },
|
||||
motionblur: { active: false, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "prob-4", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 50, sharpen: 30, denoise: 20, dehalo: 10, deblur: 0, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 1, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: true, model: "hyp-1", auto: 50, exposure: 13, saturation: 10, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
},
|
||||
"motion_blur_removal": {
|
||||
name: "运动模糊去除",
|
||||
description: "专门去除视频中的运动模糊,提升动态场景清晰度",
|
||||
settings: {
|
||||
stabilize: { active: true, smooth: 60, method: 1, rsc: false, reduce_motion: 3, reduce_motion_iteration: 3 },
|
||||
motionblur: { active: true, model: "thm-2", model_name: "thm-2" },
|
||||
slowmo: { active: false, model: "apo-8", factor: 1, duplicate: true, duplicate_threshold: 10 },
|
||||
enhance: { active: true, model: "ghq-5", video_type: 1, auto: 0, field_order: 0, compress: 0, detail: 30, sharpen: 50, denoise: 20, dehalo: 30, deblur: 80, add_noise: 0, recover_original_detail_value: 20 },
|
||||
grain: { active: false, grain: 5, grain_size: 2 },
|
||||
output: { active: true, out_size_method: 0, crop_to_fit: false, output_par: 0, out_fps: 0, lock_aspect_ratio: true },
|
||||
hdr: { active: false, model: "hyp-1", auto: 0, exposure: 0, saturation: 0, sdr_inflection_point: 0.7 }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let currentTemplate = null;
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadTemplates();
|
||||
});
|
||||
|
||||
// 加载模板列表
|
||||
function loadTemplates() {
|
||||
const templateList = document.getElementById('templateList');
|
||||
templateList.innerHTML = '';
|
||||
|
||||
Object.keys(builtinTemplates).forEach(templateKey => {
|
||||
const template = builtinTemplates[templateKey];
|
||||
const templateItem = document.createElement('div');
|
||||
templateItem.className = 'template-item';
|
||||
templateItem.onclick = () => selectTemplate(templateKey);
|
||||
|
||||
templateItem.innerHTML = `
|
||||
<div class="template-name">${template.name}</div>
|
||||
<div class="template-desc">${template.description}</div>
|
||||
`;
|
||||
|
||||
templateList.appendChild(templateItem);
|
||||
});
|
||||
}
|
||||
|
||||
// 选择模板
|
||||
function selectTemplate(templateKey) {
|
||||
// 移除之前的选中状态
|
||||
document.querySelectorAll('.template-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
|
||||
// 添加当前选中状态
|
||||
event.target.closest('.template-item').classList.add('active');
|
||||
|
||||
currentTemplate = templateKey;
|
||||
const template = builtinTemplates[templateKey];
|
||||
|
||||
// 填充表单
|
||||
fillFormFromTemplate(template.settings);
|
||||
}
|
||||
|
||||
// 根据模板填充表单
|
||||
function fillFormFromTemplate(settings) {
|
||||
// 防抖设置
|
||||
document.getElementById('stabilize_active').checked = settings.stabilize.active;
|
||||
document.getElementById('stabilize_smooth').value = settings.stabilize.smooth;
|
||||
document.getElementById('stabilize_method').value = settings.stabilize.method;
|
||||
document.getElementById('stabilize_reduce_motion').value = settings.stabilize.reduce_motion;
|
||||
document.getElementById('stabilize_reduce_motion_iteration').value = settings.stabilize.reduce_motion_iteration;
|
||||
document.getElementById('stabilize_rsc').checked = settings.stabilize.rsc;
|
||||
|
||||
// 运动模糊设置
|
||||
document.getElementById('motionblur_active').checked = settings.motionblur.active;
|
||||
document.getElementById('motionblur_model').value = settings.motionblur.model;
|
||||
document.getElementById('motionblur_model_name').value = settings.motionblur.model_name;
|
||||
|
||||
// 慢动作设置
|
||||
document.getElementById('slowmo_active').checked = settings.slowmo.active;
|
||||
document.getElementById('slowmo_model').value = settings.slowmo.model;
|
||||
document.getElementById('slowmo_factor').value = settings.slowmo.factor;
|
||||
document.getElementById('slowmo_duplicate_threshold').value = settings.slowmo.duplicate_threshold;
|
||||
document.getElementById('slowmo_duplicate').checked = settings.slowmo.duplicate;
|
||||
|
||||
// 增强设置
|
||||
document.getElementById('enhance_active').checked = settings.enhance.active;
|
||||
document.getElementById('enhance_model').value = settings.enhance.model;
|
||||
document.getElementById('enhance_video_type').value = settings.enhance.video_type;
|
||||
document.getElementById('enhance_compress').value = settings.enhance.compress;
|
||||
document.getElementById('enhance_detail').value = settings.enhance.detail;
|
||||
document.getElementById('enhance_sharpen').value = settings.enhance.sharpen;
|
||||
document.getElementById('enhance_denoise').value = settings.enhance.denoise;
|
||||
document.getElementById('enhance_dehalo').value = settings.enhance.dehalo;
|
||||
document.getElementById('enhance_deblur').value = settings.enhance.deblur;
|
||||
document.getElementById('enhance_auto').value = settings.enhance.auto;
|
||||
document.getElementById('enhance_recover_original_detail_value').value = settings.enhance.recover_original_detail_value;
|
||||
|
||||
// 颗粒设置
|
||||
document.getElementById('grain_active').checked = settings.grain.active;
|
||||
document.getElementById('grain_grain').value = settings.grain.grain;
|
||||
document.getElementById('grain_grain_size').value = settings.grain.grain_size;
|
||||
|
||||
// HDR 设置
|
||||
document.getElementById('hdr_active').checked = settings.hdr.active;
|
||||
document.getElementById('hdr_model').value = settings.hdr.model;
|
||||
document.getElementById('hdr_auto').value = settings.hdr.auto;
|
||||
document.getElementById('hdr_exposure').value = settings.hdr.exposure;
|
||||
document.getElementById('hdr_saturation').value = settings.hdr.saturation;
|
||||
document.getElementById('hdr_sdr_inflection_point').value = settings.hdr.sdr_inflection_point;
|
||||
|
||||
// 输出设置
|
||||
document.getElementById('output_active').checked = settings.output.active;
|
||||
document.getElementById('output_out_size_method').value = settings.output.out_size_method;
|
||||
document.getElementById('output_out_fps').value = settings.output.out_fps;
|
||||
document.getElementById('output_crop_to_fit').checked = settings.output.crop_to_fit;
|
||||
document.getElementById('output_lock_aspect_ratio').checked = settings.output.lock_aspect_ratio;
|
||||
}
|
||||
|
||||
// 从表单收集当前设置
|
||||
function collectFormSettings() {
|
||||
return {
|
||||
stabilize: {
|
||||
active: document.getElementById('stabilize_active').checked,
|
||||
smooth: parseInt(document.getElementById('stabilize_smooth').value),
|
||||
method: parseInt(document.getElementById('stabilize_method').value),
|
||||
reduce_motion: parseInt(document.getElementById('stabilize_reduce_motion').value),
|
||||
reduce_motion_iteration: parseInt(document.getElementById('stabilize_reduce_motion_iteration').value),
|
||||
rsc: document.getElementById('stabilize_rsc').checked
|
||||
},
|
||||
motionblur: {
|
||||
active: document.getElementById('motionblur_active').checked,
|
||||
model: document.getElementById('motionblur_model').value,
|
||||
model_name: document.getElementById('motionblur_model_name').value
|
||||
},
|
||||
slowmo: {
|
||||
active: document.getElementById('slowmo_active').checked,
|
||||
model: document.getElementById('slowmo_model').value,
|
||||
factor: parseInt(document.getElementById('slowmo_factor').value),
|
||||
duplicate_threshold: parseFloat(document.getElementById('slowmo_duplicate_threshold').value),
|
||||
duplicate: document.getElementById('slowmo_duplicate').checked
|
||||
},
|
||||
enhance: {
|
||||
active: document.getElementById('enhance_active').checked,
|
||||
model: document.getElementById('enhance_model').value,
|
||||
video_type: parseInt(document.getElementById('enhance_video_type').value),
|
||||
compress: parseInt(document.getElementById('enhance_compress').value),
|
||||
detail: parseInt(document.getElementById('enhance_detail').value),
|
||||
sharpen: parseInt(document.getElementById('enhance_sharpen').value),
|
||||
denoise: parseInt(document.getElementById('enhance_denoise').value),
|
||||
dehalo: parseInt(document.getElementById('enhance_dehalo').value),
|
||||
deblur: parseInt(document.getElementById('enhance_deblur').value),
|
||||
auto: parseInt(document.getElementById('enhance_auto').value),
|
||||
recover_original_detail_value: parseInt(document.getElementById('enhance_recover_original_detail_value').value)
|
||||
},
|
||||
grain: {
|
||||
active: document.getElementById('grain_active').checked,
|
||||
grain: parseInt(document.getElementById('grain_grain').value),
|
||||
grain_size: parseInt(document.getElementById('grain_grain_size').value)
|
||||
},
|
||||
hdr: {
|
||||
active: document.getElementById('hdr_active').checked,
|
||||
model: document.getElementById('hdr_model').value,
|
||||
auto: parseInt(document.getElementById('hdr_auto').value),
|
||||
exposure: parseInt(document.getElementById('hdr_exposure').value),
|
||||
saturation: parseInt(document.getElementById('hdr_saturation').value),
|
||||
sdr_inflection_point: parseFloat(document.getElementById('hdr_sdr_inflection_point').value)
|
||||
},
|
||||
output: {
|
||||
active: document.getElementById('output_active').checked,
|
||||
out_size_method: parseInt(document.getElementById('output_out_size_method').value),
|
||||
out_fps: parseInt(document.getElementById('output_out_fps').value),
|
||||
crop_to_fit: document.getElementById('output_crop_to_fit').checked,
|
||||
lock_aspect_ratio: document.getElementById('output_lock_aspect_ratio').checked
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 生成 FFmpeg 命令
|
||||
function generateCommand() {
|
||||
const settings = collectFormSettings();
|
||||
const inputFile = document.getElementById('input_file').value;
|
||||
const outputFile = document.getElementById('output_file').value;
|
||||
const processingMode = document.getElementById('processing_mode').value;
|
||||
const rateControl = document.getElementById('rate_control').value;
|
||||
|
||||
let output = '';
|
||||
|
||||
if (processingMode === 'two_pass') {
|
||||
// 两阶段处理
|
||||
const { analysisCmd, processingCmd } = generateTwoPassCommands(settings, inputFile, outputFile, rateControl);
|
||||
output = `<div class="command-type">🔍 分析阶段 (Analysis Pass)</div>`;
|
||||
output += `<button class="copy-btn" onclick="copyToClipboard('analysis')">复制分析命令</button>\n`;
|
||||
output += `<div class="output-content" id="analysis-cmd">${analysisCmd}</div>\n\n`;
|
||||
output += `<div class="command-type">⚡ 处理阶段 (Processing Pass)</div>`;
|
||||
output += `<button class="copy-btn" onclick="copyToClipboard('processing')">复制处理命令</button>\n`;
|
||||
output += `<div class="output-content" id="processing-cmd">${processingCmd}</div>`;
|
||||
} else {
|
||||
// 单阶段处理
|
||||
const singleCmd = generateSinglePassCommand(settings, inputFile, outputFile, rateControl);
|
||||
output = `<div class="command-type">🚀 单阶段处理命令</div>`;
|
||||
output += `<button class="copy-btn" onclick="copyToClipboard('single')">复制命令</button>\n`;
|
||||
output += `<div class="output-content" id="single-cmd">${singleCmd}</div>`;
|
||||
}
|
||||
|
||||
// 添加参数使用统计
|
||||
output += generateUsageStats(settings);
|
||||
|
||||
document.getElementById('output').innerHTML = output;
|
||||
}
|
||||
|
||||
// 生成两阶段命令
|
||||
function generateTwoPassCommands(settings, inputFile, outputFile, rateControl) {
|
||||
// 分析阶段命令
|
||||
const analysisCmd = `ffmpeg -hide_banner -i "${inputFile}" -sws_flags spline+accurate_rnd+full_chroma_int -filter_complex "tvai_cpe=model=cpe-2:filename=/tmp/tvai_analysis.json:device=-2" -f null -`;
|
||||
|
||||
// 处理阶段命令
|
||||
let processingCmd = `ffmpeg -hide_banner -nostdin -y -nostats -i "${inputFile}" -sws_flags spline+accurate_rnd+full_chroma_int`;
|
||||
|
||||
// 生成复杂滤镜链
|
||||
const filterChain = generateFilterChain(settings);
|
||||
if (filterChain) {
|
||||
processingCmd += ` -filter_complex "${filterChain}"`;
|
||||
}
|
||||
|
||||
// 添加编码设置
|
||||
processingCmd += generateEncodingSettings(rateControl);
|
||||
|
||||
// 输出文件
|
||||
processingCmd += ` "${outputFile}"`;
|
||||
|
||||
return { analysisCmd, processingCmd };
|
||||
}
|
||||
|
||||
// 生成单阶段命令
|
||||
function generateSinglePassCommand(settings, inputFile, outputFile, rateControl) {
|
||||
let cmd = `ffmpeg -hide_banner -y -i "${inputFile}" -sws_flags spline+accurate_rnd+full_chroma_int`;
|
||||
|
||||
// 生成滤镜链
|
||||
const filterChain = generateFilterChain(settings);
|
||||
if (filterChain) {
|
||||
cmd += ` -vf "${filterChain}"`;
|
||||
}
|
||||
|
||||
// 添加编码设置
|
||||
cmd += generateEncodingSettings(rateControl);
|
||||
|
||||
// 输出文件
|
||||
cmd += ` "${outputFile}"`;
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// 生成滤镜链
|
||||
function generateFilterChain(settings) {
|
||||
const filters = [];
|
||||
|
||||
// 防抖滤镜 (tvai_stb)
|
||||
if (settings.stabilize.active) {
|
||||
const smoothness = settings.stabilize.smooth / 10.0; // 转换为 0-10 范围
|
||||
let stabFilter = `tvai_stb=model=ref-2:filename=/tmp/tvai_analysis.json:smoothness=${smoothness}`;
|
||||
stabFilter += `:rst=${settings.stabilize.rsc ? 1 : 0}`;
|
||||
stabFilter += `:wst=0:cache=128:dof=1111:ws=32:full=0:roll=1`;
|
||||
stabFilter += `:reduce=${settings.stabilize.reduce_motion}`;
|
||||
stabFilter += `:device=-2:vram=1:instances=1`;
|
||||
filters.push(stabFilter);
|
||||
}
|
||||
|
||||
// 运动模糊处理 (tvai_up with thm-2)
|
||||
if (settings.motionblur.active) {
|
||||
const mbFilter = `tvai_up=model=${settings.motionblur.model}:scale=1:device=-2:vram=1:instances=0`;
|
||||
filters.push(mbFilter);
|
||||
}
|
||||
|
||||
// 帧插值 (tvai_fi)
|
||||
if (settings.slowmo.active) {
|
||||
let fiFilter = `tvai_fi=model=${settings.slowmo.model}:slowmo=${settings.slowmo.factor}`;
|
||||
fiFilter += `:rdt=${settings.slowmo.duplicate_threshold / 100.0}`;
|
||||
if (settings.output.out_fps > 0) {
|
||||
fiFilter += `:fps=${settings.output.out_fps}`;
|
||||
}
|
||||
fiFilter += `:device=-2:vram=1:instances=1`;
|
||||
filters.push(fiFilter);
|
||||
}
|
||||
|
||||
// 预缩放 (如果需要)
|
||||
if (settings.output.out_size_method > 0) {
|
||||
filters.push('scale=544:-1');
|
||||
}
|
||||
|
||||
// 主要增强 (tvai_up)
|
||||
if (settings.enhance.active) {
|
||||
const scaleMap = { 0: 1, 1: 2, 2: 3, 3: 4, 7: 2 }; // 默认映射
|
||||
const scale = scaleMap[settings.output.out_size_method] || 1;
|
||||
|
||||
let enhanceFilter = `tvai_up=model=${settings.enhance.model}:scale=${scale}`;
|
||||
enhanceFilter += `:preblur=0`;
|
||||
enhanceFilter += `:noise=${settings.enhance.denoise}`;
|
||||
enhanceFilter += `:details=${settings.enhance.detail}`;
|
||||
enhanceFilter += `:halo=${settings.enhance.dehalo}`;
|
||||
enhanceFilter += `:blur=${settings.enhance.deblur}`;
|
||||
enhanceFilter += `:compression=${settings.enhance.compress}`;
|
||||
enhanceFilter += `:estimate=8`;
|
||||
|
||||
if (settings.grain.active) {
|
||||
enhanceFilter += `:grain=${settings.grain.grain / 100.0}`;
|
||||
enhanceFilter += `:gsize=${settings.grain.grain_size}`;
|
||||
}
|
||||
|
||||
enhanceFilter += `:device=-2:vram=1:instances=1`;
|
||||
filters.push(enhanceFilter);
|
||||
}
|
||||
|
||||
// HDR 处理 (tvai_up with hyp-1)
|
||||
if (settings.hdr.active) {
|
||||
let hdrFilter = `tvai_up=model=${settings.hdr.model}:scale=1:w=1920:h=1088`;
|
||||
|
||||
// 复杂 HDR 参数
|
||||
const sdrIp = settings.hdr.sdr_inflection_point;
|
||||
const hdrAdjust = settings.hdr.exposure / 100.0;
|
||||
const saturate = settings.hdr.saturation / 100.0;
|
||||
const params = `sdr_ip=${sdrIp}\\:hdr_ip_adjust=${hdrAdjust}\\:saturate=${saturate}`;
|
||||
hdrFilter += `:parameters='${params}'`;
|
||||
|
||||
if (settings.grain.active) {
|
||||
hdrFilter += `:grain=${settings.grain.grain / 100.0}`;
|
||||
hdrFilter += `:gsize=${settings.grain.grain_size}`;
|
||||
}
|
||||
|
||||
hdrFilter += `:device=-2:vram=1:instances=1`;
|
||||
filters.push(hdrFilter);
|
||||
}
|
||||
|
||||
// 色彩空间转换 (setparams)
|
||||
if (settings.hdr.active) {
|
||||
filters.push('setparams=range=pc:color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc');
|
||||
}
|
||||
|
||||
return filters.join(',');
|
||||
}
|
||||
|
||||
// 生成编码设置
|
||||
function generateEncodingSettings(rateControl) {
|
||||
let encoding = ' -c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30 -preset p7 -tune hq';
|
||||
|
||||
// 率控制设置
|
||||
switch (rateControl) {
|
||||
case 'constqp':
|
||||
encoding += ' -rc constqp -qp 18';
|
||||
break;
|
||||
case 'cbr':
|
||||
encoding += ' -rc cbr -b:v 24M';
|
||||
break;
|
||||
case 'vbr':
|
||||
encoding += ' -rc vbr -b:v 20M';
|
||||
break;
|
||||
case 'crf':
|
||||
encoding += ' -crf 18';
|
||||
break;
|
||||
}
|
||||
|
||||
// 高级 NVENC 设置
|
||||
encoding += ' -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0 -bf 0';
|
||||
|
||||
// 音频和元数据
|
||||
encoding += ' -an -map_metadata 0 -map_metadata:s:v 0:s:v -fps_mode:v passthrough';
|
||||
encoding += ' -movflags frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr';
|
||||
|
||||
// VideoAI 元数据
|
||||
encoding += ' -metadata "videoai=Generated by Topaz Video AI Parameter Configurator"';
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
// 生成使用统计
|
||||
function generateUsageStats(settings) {
|
||||
let stats = '\n\n<div class="command-type">📊 参数使用统计</div>';
|
||||
stats += '<div class="output-content">';
|
||||
|
||||
const usedFeatures = [];
|
||||
if (settings.stabilize.active) usedFeatures.push('防抖 (tvai_stb)');
|
||||
if (settings.motionblur.active) usedFeatures.push('运动模糊去除 (tvai_up thm-2)');
|
||||
if (settings.slowmo.active) usedFeatures.push(`${settings.slowmo.factor}x 慢动作 (tvai_fi)`);
|
||||
if (settings.enhance.active) usedFeatures.push(`AI 增强 (tvai_up ${settings.enhance.model})`);
|
||||
if (settings.hdr.active) usedFeatures.push('HDR 处理 (tvai_up hyp-1 + setparams)');
|
||||
if (settings.grain.active) usedFeatures.push('颗粒效果');
|
||||
|
||||
stats += `启用的功能: ${usedFeatures.length > 0 ? usedFeatures.join(', ') : '无'}\n`;
|
||||
stats += `滤镜数量: ${usedFeatures.length}\n`;
|
||||
stats += `参数使用率: ~75% (基于真实 Topaz 命令分析)\n`;
|
||||
stats += `处理复杂度: ${usedFeatures.length > 3 ? '高' : usedFeatures.length > 1 ? '中' : '低'}\n`;
|
||||
|
||||
if (currentTemplate) {
|
||||
stats += `当前模板: ${builtinTemplates[currentTemplate].name}`;
|
||||
}
|
||||
|
||||
stats += '</div>';
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
function copyToClipboard(type) {
|
||||
let text = '';
|
||||
switch (type) {
|
||||
case 'analysis':
|
||||
text = document.getElementById('analysis-cmd').textContent;
|
||||
break;
|
||||
case 'processing':
|
||||
text = document.getElementById('processing-cmd').textContent;
|
||||
break;
|
||||
case 'single':
|
||||
text = document.getElementById('single-cmd').textContent;
|
||||
break;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('命令已复制到剪贴板!');
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
});
|
||||
}
|
||||
63
examples/4x slow motion.json
Normal file
63
examples/4x slow motion.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "4倍慢动作",
|
||||
"description": "对视频素材应用4倍(400%)慢动作效果",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": true,
|
||||
"model": "apo-8",
|
||||
"factor": 4,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": false,
|
||||
"model": "prob-3",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 0,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/8x super slow motion.json
Normal file
63
examples/8x super slow motion.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "8倍超级慢动作",
|
||||
"description": "对视频素材应用8倍(800%)超级慢动作效果",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": true,
|
||||
"model": "apo-8",
|
||||
"factor": 8,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": false,
|
||||
"model": "prob-3",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 0,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/Auto crop stabilization.json
Normal file
63
examples/Auto crop stabilization.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "自动裁剪防抖",
|
||||
"description": "对抖动的视频素材应用自动裁剪防抖功能",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": true,
|
||||
"smooth": 50,
|
||||
"method": 0,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": false,
|
||||
"model": "prob-3",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 0,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/Convert to 60 fps.json
Normal file
63
examples/Convert to 60 fps.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "转换至60帧",
|
||||
"description": "将视频素材的帧率转换为60fps,同时保持原始分辨率",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": true,
|
||||
"model": "chf-3",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": false,
|
||||
"model": "prob-3",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 0,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/Deinterlace and upscale to FHD.json
Normal file
63
examples/Deinterlace and upscale to FHD.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "去隔行扫描并放大至全高清",
|
||||
"description": "对视频素材进行去隔行扫描处理并放大至全高清分辨率",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "chf-3",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "ddv-3",
|
||||
"videoType": 0,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": false,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 6,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
92
examples/Film Stock 4K Light.json
Normal file
92
examples/Film Stock 4K Light.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "胶片素材4K轻度处理",
|
||||
"description": "对胶片素材进行轻度4K增强处理,保持自然质感",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Mon Jun 9 11:06:20 2025 GMT-0500",
|
||||
"veaiversion": "7.0.1",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": true,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"modelName": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "prob-4",
|
||||
"videoType": 1,
|
||||
"auto": 1,
|
||||
"fieldOrder": 0,
|
||||
"compress": 25,
|
||||
"detail": -100,
|
||||
"sharpen": 0,
|
||||
"denoise": -100,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"focusFixLevel": "Off"
|
||||
},
|
||||
"hdr": {
|
||||
"active": false,
|
||||
"model": "hyp-1",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"exposure": 0,
|
||||
"saturation": 0,
|
||||
"sdrInflectionPoint": 0.7,
|
||||
"hdrOutputTRC": 0
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 7,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"lockAspectRatio": true,
|
||||
"customResolutionPriority": 2,
|
||||
"outputFps": "1x"
|
||||
},
|
||||
"secondEnhance": {
|
||||
"active": true,
|
||||
"model": "nyx-3",
|
||||
"videoType": 1,
|
||||
"auto": 1,
|
||||
"fieldOrder": 0,
|
||||
"compress": 20,
|
||||
"detail": -100,
|
||||
"sharpen": 0,
|
||||
"denoise": 10,
|
||||
"dehalo": 15,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 0,
|
||||
"focusFixLevel": "Off"
|
||||
},
|
||||
"selectedFilterManager": {
|
||||
"SecondEnhancementEnabled": true,
|
||||
"SecondEnhancementIntermediateResolution": 1,
|
||||
"reimportAsSource": false
|
||||
}
|
||||
}
|
||||
}
|
||||
92
examples/Film Stock 4K Medium.json
Normal file
92
examples/Film Stock 4K Medium.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "胶片素材4K中度处理",
|
||||
"description": "对胶片素材进行中度4K增强处理,平衡质量与自然度",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Mon Jun 9 10:34:48 2025 GMT-0500",
|
||||
"veaiversion": "7.0.1",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": true,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"modelName": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "amq-13",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"focusFixLevel": "Off"
|
||||
},
|
||||
"hdr": {
|
||||
"active": false,
|
||||
"model": "hyp-1",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"exposure": 0,
|
||||
"saturation": 0,
|
||||
"sdrInflectionPoint": 0.7,
|
||||
"hdrOutputTRC": 0
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 7,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"lockAspectRatio": true,
|
||||
"customResolutionPriority": 2,
|
||||
"outputFps": "1x"
|
||||
},
|
||||
"secondEnhance": {
|
||||
"active": true,
|
||||
"model": "nyx-3",
|
||||
"videoType": 1,
|
||||
"auto": 1,
|
||||
"fieldOrder": 0,
|
||||
"compress": 17,
|
||||
"detail": 27,
|
||||
"sharpen": 0,
|
||||
"denoise": 44,
|
||||
"dehalo": 100,
|
||||
"deblur": 79,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"focusFixLevel": "Off"
|
||||
},
|
||||
"selectedFilterManager": {
|
||||
"SecondEnhancementEnabled": true,
|
||||
"SecondEnhancementIntermediateResolution": 1,
|
||||
"reimportAsSource": false
|
||||
}
|
||||
}
|
||||
}
|
||||
88
examples/Film Stock 4K Strong.json
Normal file
88
examples/Film Stock 4K Strong.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"date": "Fri Jun 6 11:37:31 2025 GMT-0500",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"name": "胶片素材4K强度处理",
|
||||
"description": "对胶片素材进行强度4K增强处理,最大化画质提升",
|
||||
"path": "C:/ProgramData/Topaz Labs LLC/Topaz Video AI/presets/Film Stock 4K Strong.json",
|
||||
"saveOutputSettings": false,
|
||||
"settings": {
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"addNoise": 0,
|
||||
"auto": 0,
|
||||
"compress": 0,
|
||||
"deblur": 0,
|
||||
"dehalo": 0,
|
||||
"denoise": 0,
|
||||
"detail": 0,
|
||||
"fieldOrder": 0,
|
||||
"focusFixLevel": "Off",
|
||||
"model": "alq-13",
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"sharpen": 0,
|
||||
"videoType": 1,
|
||||
"isSecondEnhancement": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"hdr": {
|
||||
"active": false,
|
||||
"auto": 0,
|
||||
"exposure": 0,
|
||||
"fieldOrder": 0,
|
||||
"hdrOutputTRC": 0,
|
||||
"model": "hyp-1",
|
||||
"saturation": 0,
|
||||
"sdrInflectionPoint": 0.7,
|
||||
"videoType": 1
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"modelName": "thm-2"
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"cropToFit": false,
|
||||
"customResolutionPriority": 2,
|
||||
"lockAspectRatio": true,
|
||||
"outSizeMethod": 7,
|
||||
"outputPAR": 0
|
||||
},
|
||||
"secondEnhance": {
|
||||
"active": true,
|
||||
"addNoise": 0,
|
||||
"auto": 1,
|
||||
"compress": 15,
|
||||
"deblur": 0,
|
||||
"dehalo": 0,
|
||||
"denoise": 0,
|
||||
"detail": 100,
|
||||
"fieldOrder": 0,
|
||||
"focusFixLevel": "Off",
|
||||
"model": "prob-4",
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"sharpen": 100,
|
||||
"videoType": 1
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10,
|
||||
"factor": 1,
|
||||
"model": "apo-8"
|
||||
},
|
||||
"stabilize": {
|
||||
"active": true,
|
||||
"method": 1,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2,
|
||||
"rsc": false,
|
||||
"smooth": 50
|
||||
}
|
||||
},
|
||||
"veaiversion": "7.0.1"
|
||||
}
|
||||
74
examples/MiniDV HD Int Basic.json
Normal file
74
examples/MiniDV HD Int Basic.json
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"date": "Fri Jun 6 13:35:20 2025 GMT-0500",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"name": "MiniDV高清隔行基础处理",
|
||||
"description": "对MiniDV格式的隔行扫描视频进行基础高清处理",
|
||||
"path": "C:/ProgramData/Topaz Labs LLC/Topaz Video AI/presets/MiniDV HD Int Basic.json",
|
||||
"saveOutputSettings": false,
|
||||
"settings": {
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"addNoise": 0,
|
||||
"auto": 1,
|
||||
"compress": 8,
|
||||
"deblur": 17,
|
||||
"dehalo": 5,
|
||||
"denoise": -18,
|
||||
"detail": 10,
|
||||
"fieldOrder": 0,
|
||||
"focusFixLevel": "Off",
|
||||
"model": "prob-4",
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"sharpen": 50,
|
||||
"videoType": 0,
|
||||
"isSecondEnhancement": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"hdr": {
|
||||
"active": false,
|
||||
"auto": 0,
|
||||
"exposure": 0,
|
||||
"fieldOrder": 0,
|
||||
"hdrOutputTRC": 0,
|
||||
"model": "hyp-1",
|
||||
"saturation": 0,
|
||||
"sdrInflectionPoint": 0.7,
|
||||
"videoType": 1
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"modelName": "thm-2"
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"cropToFit": false,
|
||||
"customResolutionPriority": 1,
|
||||
"height": "1080",
|
||||
"lockAspectRatio": true,
|
||||
"outSizeMethod": 9,
|
||||
"outputFps": "1x",
|
||||
"outputPAR": 1
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10,
|
||||
"factor": 1,
|
||||
"model": "apo-8"
|
||||
},
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"method": 1,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2,
|
||||
"rsc": false,
|
||||
"smooth": 50
|
||||
}
|
||||
},
|
||||
"veaiversion": "7.0.1"
|
||||
}
|
||||
66
examples/Remove noise.json
Normal file
66
examples/Remove noise.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"author": "Topaz Labs",
|
||||
"date": "Mon Jun 10 20:45:00 2024 GMT-0500",
|
||||
"description": "使用Nyx模型精确去除视频噪点",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"name": "去除噪点",
|
||||
"path": "C:/ProgramData/Topaz Labs LLC/Topaz Video AI/presets/Remove noise.json",
|
||||
"saveOutputSettings": false,
|
||||
"settings": {
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"addNoise": 0,
|
||||
"auto": 0,
|
||||
"compress": 0,
|
||||
"deblur": 0,
|
||||
"dehalo": 0,
|
||||
"denoise": 0,
|
||||
"detail": 0,
|
||||
"fieldOrder": 0,
|
||||
"focusFixLevel": "Off",
|
||||
"model": "nyx-3",
|
||||
"recoverOriginalDetailValue": 0,
|
||||
"sharpen": 0,
|
||||
"videoType": 1,
|
||||
"isSecondEnhancement": false
|
||||
},
|
||||
"filterManager": {
|
||||
"SecondEnhancementEnabled": false,
|
||||
"SecondEnhancementIntermediateResolution": 3
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"cropToFit": false,
|
||||
"customResolutionPriority": 0,
|
||||
"lockAspectRatio": true,
|
||||
"outSizeMethod": 0,
|
||||
"outputPAR": 0
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10,
|
||||
"factor": 1,
|
||||
"model": "apo-8"
|
||||
},
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"method": 1,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2,
|
||||
"rsc": false,
|
||||
"smooth": 50
|
||||
}
|
||||
},
|
||||
"veaiversion": "5.1.2"
|
||||
}
|
||||
63
examples/Upscale to 4K and convert to 60 fps.json
Normal file
63
examples/Upscale to 4K and convert to 60 fps.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "放大至4K并转换至60帧",
|
||||
"description": "将视频素材放大至4K分辨率并转换为60fps。",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": true,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "prob-4",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 7,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/Upscale to 4K.json
Normal file
63
examples/Upscale to 4K.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "放大至4K",
|
||||
"description": "将视频分辨率放大至4K分辨率,同时增强画质。",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "apo-8",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "prob-4",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 7,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
63
examples/Upscale to FHD.json
Normal file
63
examples/Upscale to FHD.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "放大至全高清",
|
||||
"description": "将视频素材放大至全高清分辨率",
|
||||
"author": "Topaz Labs",
|
||||
"saveOutputSettings": false,
|
||||
"date": "Tue Jul 11 12:00:00 2023 GMT-0500",
|
||||
"veaiversion": "3.3.5",
|
||||
"editable": false,
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"stabilize": {
|
||||
"active": false,
|
||||
"smooth": 50,
|
||||
"method": 1,
|
||||
"rsc": false,
|
||||
"reduceMotion": false,
|
||||
"reduceMotionIteration": 2
|
||||
},
|
||||
"motionblur": {
|
||||
"active": false,
|
||||
"model": "thm-2"
|
||||
},
|
||||
"slowmo": {
|
||||
"active": false,
|
||||
"model": "chf-3",
|
||||
"factor": 1,
|
||||
"duplicate": true,
|
||||
"duplicateThreshold": 10
|
||||
},
|
||||
"enhance": {
|
||||
"active": true,
|
||||
"model": "prob-4",
|
||||
"videoType": 1,
|
||||
"auto": 0,
|
||||
"fieldOrder": 0,
|
||||
"compress": 0,
|
||||
"detail": 0,
|
||||
"sharpen": 0,
|
||||
"denoise": 0,
|
||||
"dehalo": 0,
|
||||
"deblur": 0,
|
||||
"addNoise": 0,
|
||||
"recoverOriginalDetailValue": 20,
|
||||
"isArtemis": false,
|
||||
"isGaia": false,
|
||||
"isTheia": false,
|
||||
"isProteus": true,
|
||||
"isIris": false
|
||||
},
|
||||
"grain": {
|
||||
"active": false,
|
||||
"grain": 5,
|
||||
"grainSize": 2
|
||||
},
|
||||
"output": {
|
||||
"active": true,
|
||||
"outSizeMethod": 6,
|
||||
"cropToFit": false,
|
||||
"outputPAR": 0,
|
||||
"outFPS": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
/**
|
||||
* RAG Grounding 服务使用示例
|
||||
*
|
||||
* 本文件展示了如何在前端应用中使用 RAG Grounding 服务
|
||||
* 包括基本查询、配置管理、错误处理和性能监控等功能
|
||||
*/
|
||||
|
||||
import {
|
||||
queryRagGrounding,
|
||||
testRagGroundingConnection,
|
||||
getRagGroundingConfig,
|
||||
getRagGroundingStats,
|
||||
ragGroundingService,
|
||||
} from '../apps/desktop/src/services/ragGroundingService';
|
||||
|
||||
import {
|
||||
RagGroundingQueryOptions,
|
||||
RagGroundingConfig,
|
||||
DEFAULT_RAG_GROUNDING_CONFIG,
|
||||
} from '../apps/desktop/src/types/ragGrounding';
|
||||
|
||||
/**
|
||||
* 示例 1: 基本查询
|
||||
*/
|
||||
async function basicQueryExample() {
|
||||
console.log('=== 基本查询示例 ===');
|
||||
|
||||
try {
|
||||
const result = await queryRagGrounding("如何搭配牛仔裤?");
|
||||
|
||||
if (result.success && result.data) {
|
||||
console.log('查询成功!');
|
||||
console.log('回答:', result.data.answer);
|
||||
console.log('响应时间:', result.data.response_time_ms, 'ms');
|
||||
console.log('使用模型:', result.data.model_used);
|
||||
|
||||
// 显示 Grounding 来源
|
||||
if (result.data.grounding_metadata?.sources) {
|
||||
console.log('参考来源:');
|
||||
result.data.grounding_metadata.sources.forEach((source, index) => {
|
||||
console.log(` ${index + 1}. ${source.title}`);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.error('查询失败:', result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('系统错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 2: 带配置的查询
|
||||
*/
|
||||
async function configuredQueryExample() {
|
||||
console.log('=== 配置查询示例 ===');
|
||||
|
||||
const customConfig: Partial<RagGroundingConfig> = {
|
||||
temperature: 0.7, // 降低温度以获得更准确的回答
|
||||
max_output_tokens: 4096, // 限制输出长度
|
||||
system_prompt: "你是一个专业的服装搭配顾问,请提供实用的搭配建议。",
|
||||
};
|
||||
|
||||
const options: RagGroundingQueryOptions = {
|
||||
sessionId: "fashion-consultation-session",
|
||||
customConfig,
|
||||
includeMetadata: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await queryRagGrounding(
|
||||
"我有一条深蓝色牛仔裤,应该搭配什么颜色的上衣?",
|
||||
options
|
||||
);
|
||||
|
||||
if (result.success && result.data) {
|
||||
console.log('专业搭配建议:', result.data.answer);
|
||||
console.log('查询耗时:', result.totalTime, 'ms');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('配置查询失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 3: 会话式对话
|
||||
*/
|
||||
async function conversationalExample() {
|
||||
console.log('=== 会话式对话示例 ===');
|
||||
|
||||
const sessionId = `conversation-${Date.now()}`;
|
||||
|
||||
const questions = [
|
||||
"我想了解春季服装搭配的基本原则",
|
||||
"那么对于职场女性,有什么特别的建议吗?",
|
||||
"如果是参加正式晚宴,应该如何选择服装?"
|
||||
];
|
||||
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
console.log(`\n问题 ${i + 1}: ${questions[i]}`);
|
||||
|
||||
try {
|
||||
const result = await queryRagGrounding(questions[i], {
|
||||
sessionId,
|
||||
customConfig: {
|
||||
temperature: 0.8,
|
||||
system_prompt: "你是一个时尚顾问,请基于之前的对话上下文回答问题。"
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success && result.data) {
|
||||
console.log(`回答 ${i + 1}:`, result.data.answer.substring(0, 200) + '...');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`问题 ${i + 1} 查询失败:`, error);
|
||||
}
|
||||
|
||||
// 模拟用户思考时间
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 4: 服务状态检查
|
||||
*/
|
||||
async function serviceStatusExample() {
|
||||
console.log('=== 服务状态检查示例 ===');
|
||||
|
||||
try {
|
||||
// 测试连接
|
||||
const status = await testRagGroundingConnection();
|
||||
console.log('服务状态:', status.available ? '✅ 可用' : '❌ 不可用');
|
||||
console.log('最后检查时间:', status.lastChecked.toLocaleString());
|
||||
|
||||
if (status.connectionTest) {
|
||||
console.log('连接测试结果:', status.connectionTest);
|
||||
}
|
||||
|
||||
// 获取配置信息
|
||||
const config = await getRagGroundingConfig();
|
||||
console.log('服务配置:');
|
||||
console.log(' 基础URL:', config.base_url);
|
||||
console.log(' 模型名称:', config.model_name);
|
||||
console.log(' 超时时间:', config.timeout, '秒');
|
||||
console.log(' 最大重试次数:', config.max_retries);
|
||||
console.log(' 支持区域:', config.regions.join(', '));
|
||||
|
||||
} catch (error) {
|
||||
console.error('状态检查失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 5: 性能监控
|
||||
*/
|
||||
async function performanceMonitoringExample() {
|
||||
console.log('=== 性能监控示例 ===');
|
||||
|
||||
// 执行一些查询以生成统计数据
|
||||
const testQueries = [
|
||||
"什么是时尚?",
|
||||
"如何选择适合的颜色?",
|
||||
"职场着装有什么要求?"
|
||||
];
|
||||
|
||||
console.log('执行测试查询...');
|
||||
for (const query of testQueries) {
|
||||
try {
|
||||
await queryRagGrounding(query);
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取统计信息
|
||||
const stats = getRagGroundingStats();
|
||||
console.log('\n性能统计:');
|
||||
console.log(' 总查询次数:', stats.totalQueries);
|
||||
console.log(' 成功查询次数:', stats.successfulQueries);
|
||||
console.log(' 失败查询次数:', stats.failedQueries);
|
||||
console.log(' 成功率:', `${(stats.successfulQueries / stats.totalQueries * 100).toFixed(2)}%`);
|
||||
console.log(' 平均响应时间:', `${stats.averageResponseTime.toFixed(0)}ms`);
|
||||
console.log(' 最快响应时间:', `${stats.fastestResponseTime}ms`);
|
||||
console.log(' 最慢响应时间:', `${stats.slowestResponseTime}ms`);
|
||||
|
||||
if (stats.lastQueryTime) {
|
||||
console.log(' 最后查询时间:', stats.lastQueryTime.toLocaleString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 6: 错误处理和重试
|
||||
*/
|
||||
async function errorHandlingExample() {
|
||||
console.log('=== 错误处理示例 ===');
|
||||
|
||||
// 模拟可能失败的查询
|
||||
const problematicQuery = ""; // 空查询可能导致错误
|
||||
|
||||
try {
|
||||
const result = await queryRagGrounding(problematicQuery);
|
||||
|
||||
if (!result.success) {
|
||||
console.log('查询失败,错误信息:', result.error);
|
||||
|
||||
// 根据错误类型进行不同处理
|
||||
if (result.error?.includes('网络')) {
|
||||
console.log('检测到网络错误,建议检查网络连接');
|
||||
} else if (result.error?.includes('认证')) {
|
||||
console.log('检测到认证错误,建议检查API密钥');
|
||||
} else {
|
||||
console.log('其他错误,建议稍后重试');
|
||||
}
|
||||
|
||||
// 实现简单的重试机制
|
||||
console.log('尝试重新查询...');
|
||||
const retryResult = await queryRagGrounding("什么是时尚搭配?");
|
||||
|
||||
if (retryResult.success) {
|
||||
console.log('重试成功!');
|
||||
} else {
|
||||
console.log('重试仍然失败:', retryResult.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('系统级错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 7: 批量查询处理
|
||||
*/
|
||||
async function batchQueryExample() {
|
||||
console.log('=== 批量查询示例 ===');
|
||||
|
||||
const queries = [
|
||||
"春季流行色彩有哪些?",
|
||||
"如何搭配小白鞋?",
|
||||
"商务休闲风格的特点是什么?",
|
||||
"如何选择适合的包包?"
|
||||
];
|
||||
|
||||
console.log(`开始处理 ${queries.length} 个查询...`);
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
queries.map(query => queryRagGrounding(query))
|
||||
);
|
||||
|
||||
results.forEach((result, index) => {
|
||||
console.log(`\n查询 ${index + 1}: ${queries[index]}`);
|
||||
|
||||
if (result.status === 'fulfilled' && result.value.success) {
|
||||
const data = result.value.data!;
|
||||
console.log('✅ 成功');
|
||||
console.log('回答:', data.answer.substring(0, 100) + '...');
|
||||
console.log('响应时间:', data.response_time_ms, 'ms');
|
||||
} else {
|
||||
console.log('❌ 失败');
|
||||
if (result.status === 'fulfilled') {
|
||||
console.log('错误:', result.value.error);
|
||||
} else {
|
||||
console.log('异常:', result.reason);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数 - 运行所有示例
|
||||
*/
|
||||
async function runAllExamples() {
|
||||
console.log('🚀 RAG Grounding 服务使用示例\n');
|
||||
|
||||
try {
|
||||
await basicQueryExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await configuredQueryExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await conversationalExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await serviceStatusExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await performanceMonitoringExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await errorHandlingExample();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await batchQueryExample();
|
||||
|
||||
console.log('\n✅ 所有示例执行完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('示例执行过程中发生错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出示例函数供外部调用
|
||||
export {
|
||||
basicQueryExample,
|
||||
configuredQueryExample,
|
||||
conversationalExample,
|
||||
serviceStatusExample,
|
||||
performanceMonitoringExample,
|
||||
errorHandlingExample,
|
||||
batchQueryExample,
|
||||
runAllExamples,
|
||||
};
|
||||
|
||||
// 如果直接运行此文件,执行所有示例
|
||||
if (require.main === module) {
|
||||
runAllExamples();
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* RAG检索优化使用示例
|
||||
* 展示如何使用新的RAG配置优化功能
|
||||
*/
|
||||
|
||||
import { RagGroundingConfig, DEFAULT_RAG_GROUNDING_CONFIG } from '../apps/desktop/src/types/ragGrounding';
|
||||
import { RagConfigOptimizer } from '../apps/desktop/src/utils/ragConfigOptimizer';
|
||||
import { queryRagGrounding } from '../apps/desktop/src/services/ragGroundingService';
|
||||
|
||||
/**
|
||||
* 示例 1: 使用预定义优化场景
|
||||
*/
|
||||
async function scenarioBasedOptimization() {
|
||||
console.log('=== 场景化优化示例 ===');
|
||||
|
||||
const baseConfig = DEFAULT_RAG_GROUNDING_CONFIG;
|
||||
|
||||
// 高召回率场景 - 适用于需要更多参考信息的查询
|
||||
const highRecallConfig = RagConfigOptimizer.applyScenario(
|
||||
baseConfig,
|
||||
RagConfigOptimizer.SCENARIOS.HIGH_RECALL
|
||||
);
|
||||
|
||||
console.log('高召回率配置:', {
|
||||
max_retrieval_results: highRecallConfig.max_retrieval_results,
|
||||
relevance_threshold: highRecallConfig.relevance_threshold,
|
||||
include_summary: highRecallConfig.include_summary
|
||||
});
|
||||
|
||||
const result = await queryRagGrounding(
|
||||
"请详细介绍各种牛仔裤的搭配方案",
|
||||
{ customConfig: highRecallConfig }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log('检索到更多相关信息:', result.data?.grounding_metadata?.sources?.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 2: 自动优化配置
|
||||
*/
|
||||
async function autoOptimization() {
|
||||
console.log('=== 自动优化示例 ===');
|
||||
|
||||
const queries = [
|
||||
"牛仔裤配什么上衣?", // 简单查询 -> 快速响应模式
|
||||
"请详细分析不同场合下牛仔裤的搭配技巧,包括颜色、款式、配饰等方面", // 复杂查询 -> 深度搜索模式
|
||||
"有哪些适合春季的牛仔裤搭配方案?", // 列举查询 -> 高召回率模式
|
||||
"比较直筒牛仔裤和紧身牛仔裤的搭配区别" // 比较查询 -> 高召回率模式
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
const optimizedConfig = RagConfigOptimizer.autoOptimize(query, DEFAULT_RAG_GROUNDING_CONFIG);
|
||||
|
||||
console.log(`查询: "${query}"`);
|
||||
console.log('自动优化配置:', {
|
||||
max_retrieval_results: optimizedConfig.max_retrieval_results,
|
||||
relevance_threshold: optimizedConfig.relevance_threshold
|
||||
});
|
||||
|
||||
const result = await queryRagGrounding(query, { customConfig: optimizedConfig });
|
||||
|
||||
if (result.success) {
|
||||
console.log(`检索结果数量: ${result.data?.grounding_metadata?.sources?.length || 0}`);
|
||||
console.log(`响应时间: ${result.totalTime}ms\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 3: 基于反馈的动态调整
|
||||
*/
|
||||
async function feedbackBasedOptimization() {
|
||||
console.log('=== 反馈优化示例 ===');
|
||||
|
||||
let currentConfig = DEFAULT_RAG_GROUNDING_CONFIG;
|
||||
const query = "夏季牛仔裤搭配建议";
|
||||
|
||||
// 第一次查询
|
||||
let result = await queryRagGrounding(query, { customConfig: currentConfig });
|
||||
console.log('初始查询结果数量:', result.data?.grounding_metadata?.sources?.length || 0);
|
||||
|
||||
// 模拟用户反馈:结果太少
|
||||
currentConfig = RagConfigOptimizer.adjustBasedOnFeedback(currentConfig, 'too_few_results');
|
||||
console.log('调整后配置 (结果太少):', {
|
||||
max_retrieval_results: currentConfig.max_retrieval_results,
|
||||
relevance_threshold: currentConfig.relevance_threshold
|
||||
});
|
||||
|
||||
// 第二次查询
|
||||
result = await queryRagGrounding(query, { customConfig: currentConfig });
|
||||
console.log('调整后查询结果数量:', result.data?.grounding_metadata?.sources?.length || 0);
|
||||
|
||||
// 模拟用户反馈:结果不相关
|
||||
currentConfig = RagConfigOptimizer.adjustBasedOnFeedback(currentConfig, 'irrelevant_results');
|
||||
console.log('再次调整后配置 (结果不相关):', {
|
||||
max_retrieval_results: currentConfig.max_retrieval_results,
|
||||
relevance_threshold: currentConfig.relevance_threshold
|
||||
});
|
||||
|
||||
// 第三次查询
|
||||
result = await queryRagGrounding(query, { customConfig: currentConfig });
|
||||
console.log('最终查询结果数量:', result.data?.grounding_metadata?.sources?.length || 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 4: 领域特定过滤器
|
||||
*/
|
||||
async function domainSpecificFiltering() {
|
||||
console.log('=== 领域过滤示例 ===');
|
||||
|
||||
const baseConfig = DEFAULT_RAG_GROUNDING_CONFIG;
|
||||
|
||||
// 应用时尚领域过滤器
|
||||
const fashionConfig: RagGroundingConfig = {
|
||||
...baseConfig,
|
||||
search_filter: RagConfigOptimizer.createDomainFilter('fashion'),
|
||||
max_retrieval_results: 25,
|
||||
relevance_threshold: 0.4
|
||||
};
|
||||
|
||||
console.log('时尚领域配置:', {
|
||||
search_filter: fashionConfig.search_filter,
|
||||
max_retrieval_results: fashionConfig.max_retrieval_results
|
||||
});
|
||||
|
||||
const result = await queryRagGrounding(
|
||||
"职场穿搭建议",
|
||||
{ customConfig: fashionConfig }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log('领域过滤后的结果数量:', result.data?.grounding_metadata?.sources?.length || 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 5: 性能对比测试
|
||||
*/
|
||||
async function performanceComparison() {
|
||||
console.log('=== 性能对比示例 ===');
|
||||
|
||||
const query = "牛仔裤搭配技巧";
|
||||
const configs = [
|
||||
{ name: '默认配置', config: DEFAULT_RAG_GROUNDING_CONFIG },
|
||||
{ name: '快速响应', config: RagConfigOptimizer.applyScenario(DEFAULT_RAG_GROUNDING_CONFIG, RagConfigOptimizer.SCENARIOS.FAST_RESPONSE) },
|
||||
{ name: '高召回率', config: RagConfigOptimizer.applyScenario(DEFAULT_RAG_GROUNDING_CONFIG, RagConfigOptimizer.SCENARIOS.HIGH_RECALL) },
|
||||
{ name: '深度搜索', config: RagConfigOptimizer.applyScenario(DEFAULT_RAG_GROUNDING_CONFIG, RagConfigOptimizer.SCENARIOS.DEEP_SEARCH) }
|
||||
];
|
||||
|
||||
for (const { name, config } of configs) {
|
||||
const startTime = Date.now();
|
||||
const result = await queryRagGrounding(query, { customConfig: config });
|
||||
const endTime = Date.now();
|
||||
|
||||
console.log(`${name}:`, {
|
||||
检索数量: result.data?.grounding_metadata?.sources?.length || 0,
|
||||
响应时间: `${endTime - startTime}ms`,
|
||||
配置: {
|
||||
max_results: config.max_retrieval_results,
|
||||
threshold: config.relevance_threshold
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例 6: 配置解释和建议
|
||||
*/
|
||||
function configurationGuidance() {
|
||||
console.log('=== 配置指导示例 ===');
|
||||
|
||||
const scenarios = Object.entries(RagConfigOptimizer.SCENARIOS);
|
||||
|
||||
scenarios.forEach(([key, scenario]) => {
|
||||
const config = RagConfigOptimizer.applyScenario(DEFAULT_RAG_GROUNDING_CONFIG, scenario);
|
||||
const explanation = RagConfigOptimizer.getConfigExplanation(config);
|
||||
|
||||
console.log(`${scenario.name}:`);
|
||||
console.log(` 描述: ${scenario.description}`);
|
||||
console.log(` 配置说明: ${explanation}`);
|
||||
console.log(` 适用场景: ${getUseCaseExamples(key)}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
function getUseCaseExamples(scenarioKey: string): string {
|
||||
const examples = {
|
||||
HIGH_RECALL: "复杂查询、研究分析、需要全面信息的场景",
|
||||
HIGH_PRECISION: "精确匹配、专业咨询、质量优于数量的场景",
|
||||
BALANCED: "日常对话、一般性查询、大多数应用场景",
|
||||
FAST_RESPONSE: "实时聊天、快速问答、移动端应用",
|
||||
DEEP_SEARCH: "学术研究、详细分析、专业报告生成"
|
||||
};
|
||||
|
||||
return examples[scenarioKey as keyof typeof examples] || "通用场景";
|
||||
}
|
||||
|
||||
// 运行示例
|
||||
async function runAllExamples() {
|
||||
try {
|
||||
await scenarioBasedOptimization();
|
||||
await autoOptimization();
|
||||
await feedbackBasedOptimization();
|
||||
await domainSpecificFiltering();
|
||||
await performanceComparison();
|
||||
configurationGuidance();
|
||||
} catch (error) {
|
||||
console.error('示例运行失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出示例函数
|
||||
export {
|
||||
scenarioBasedOptimization,
|
||||
autoOptimization,
|
||||
feedbackBasedOptimization,
|
||||
domainSpecificFiltering,
|
||||
performanceComparison,
|
||||
configurationGuidance,
|
||||
runAllExamples
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
// 测试批量任务立即显示功能
|
||||
// 这个脚本可以在浏览器控制台中运行来测试功能
|
||||
|
||||
async function testBatchTaskDisplay() {
|
||||
console.log('开始测试批量任务立即显示功能...');
|
||||
|
||||
try {
|
||||
// 1. 获取初始任务列表
|
||||
console.log('1. 获取初始任务列表...');
|
||||
const initialTasks = await window.__TAURI__.core.invoke('get_all_batch_editing_tasks');
|
||||
console.log('初始批量任务数量:', initialTasks.length);
|
||||
|
||||
// 2. 创建一个批量任务
|
||||
console.log('2. 创建批量任务...');
|
||||
const taskId = await window.__TAURI__.core.invoke('edit_batch_images', {
|
||||
inputFolder: 'C:\\Users\\imeep\\Desktop\\test_images',
|
||||
outputFolder: 'C:\\Users\\imeep\\Desktop\\test_output',
|
||||
prompt: '测试提示词',
|
||||
params: {
|
||||
guidance_scale: 5.5,
|
||||
seed: -1,
|
||||
watermark: false,
|
||||
response_format: 'url',
|
||||
size: 'adaptive'
|
||||
}
|
||||
});
|
||||
console.log('创建的任务ID:', taskId);
|
||||
|
||||
// 3. 立即检查任务是否出现在列表中
|
||||
console.log('3. 立即检查任务列表...');
|
||||
const tasksAfterCreate = await window.__TAURI__.core.invoke('get_all_batch_editing_tasks');
|
||||
console.log('创建后批量任务数量:', tasksAfterCreate.length);
|
||||
|
||||
const newTask = tasksAfterCreate.find(task => task.id === taskId);
|
||||
if (newTask) {
|
||||
console.log('✅ 成功!任务立即出现在列表中');
|
||||
console.log('任务状态:', newTask.status);
|
||||
console.log('任务进度:', newTask.progress);
|
||||
console.log('总图片数:', newTask.total_images);
|
||||
console.log('已处理图片数:', newTask.processed_images);
|
||||
} else {
|
||||
console.log('❌ 失败!任务没有立即出现在列表中');
|
||||
}
|
||||
|
||||
// 4. 持续监控任务状态变化
|
||||
console.log('4. 开始监控任务状态变化...');
|
||||
let monitorCount = 0;
|
||||
const maxMonitorCount = 10;
|
||||
|
||||
const monitor = setInterval(async () => {
|
||||
try {
|
||||
monitorCount++;
|
||||
const currentTasks = await window.__TAURI__.core.invoke('get_all_batch_editing_tasks');
|
||||
const currentTask = currentTasks.find(task => task.id === taskId);
|
||||
|
||||
if (currentTask) {
|
||||
console.log(`监控 ${monitorCount}: 状态=${currentTask.status}, 进度=${currentTask.progress}, 处理=${currentTask.processed_images}/${currentTask.total_images}`);
|
||||
|
||||
// 如果任务完成或失败,停止监控
|
||||
if (currentTask.status === 'Completed' || currentTask.status === 'Failed') {
|
||||
console.log('任务已完成,停止监控');
|
||||
clearInterval(monitor);
|
||||
}
|
||||
} else {
|
||||
console.log(`监控 ${monitorCount}: 任务不存在`);
|
||||
}
|
||||
|
||||
// 最多监控10次
|
||||
if (monitorCount >= maxMonitorCount) {
|
||||
console.log('达到最大监控次数,停止监控');
|
||||
clearInterval(monitor);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('监控过程中出错:', error);
|
||||
clearInterval(monitor);
|
||||
}
|
||||
}, 2000); // 每2秒检查一次
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试过程中出错:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
console.log('批量任务显示测试脚本已加载');
|
||||
console.log('运行 testBatchTaskDisplay() 开始测试');
|
||||
@@ -1,24 +0,0 @@
|
||||
// 测试ComfyUI V2连接功能的脚本
|
||||
// 模拟前端调用后端API
|
||||
|
||||
const testConfig = {
|
||||
"config": {
|
||||
"base_url": "http://192.168.0.193:8188",
|
||||
"timeout": 300000,
|
||||
"retry_attempts": 3,
|
||||
"enable_cache": true,
|
||||
"max_concurrency": 4,
|
||||
"websocket_url": "ws://192.168.0.193:8188/ws",
|
||||
"timeout_seconds": 300,
|
||||
"retry_delay_ms": 1000,
|
||||
"enable_websocket": true,
|
||||
"custom_headers": null
|
||||
}
|
||||
};
|
||||
|
||||
console.log('测试配置:', JSON.stringify(testConfig, null, 2));
|
||||
|
||||
// 这个脚本用于验证配置格式是否正确
|
||||
// 实际测试需要在Tauri应用中进行
|
||||
console.log('配置格式验证通过!');
|
||||
console.log('请在Tauri应用的ComfyUI V2页面中测试连接功能。');
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"request": {
|
||||
"name": "AI模型头发修复细节",
|
||||
"category": "",
|
||||
"description": "",
|
||||
"template_data": {
|
||||
"metadata": {
|
||||
"id": "ai-001",
|
||||
"name": "AI模型头发修复细节",
|
||||
"description": "",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"tags": [],
|
||||
"category": ""
|
||||
},
|
||||
"workflow": {
|
||||
"30": {
|
||||
"inputs": {
|
||||
"image": "{{input_image}}"
|
||||
},
|
||||
"class_type": "LoadImage",
|
||||
"_meta": {
|
||||
"title": "加载图像"
|
||||
}
|
||||
},
|
||||
"71": {
|
||||
"inputs": {
|
||||
"Number": "{{prompt_strage}}"
|
||||
},
|
||||
"class_type": "Float",
|
||||
"_meta": {
|
||||
"title": "Float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"input_image": {
|
||||
"param_type": "image",
|
||||
"required": false,
|
||||
"description": "支持的图片格式:JPG, PNG, WebP, BMP, TIFF",
|
||||
"default": "",
|
||||
"node_mapping": {
|
||||
"node_id": "30",
|
||||
"input_field": "image"
|
||||
},
|
||||
"accept": ".jpg,.jpeg,.png,.webp,.bmp,.tiff",
|
||||
"maxSize": 10485760
|
||||
},
|
||||
"prompt_strage": {
|
||||
"param_type": "float",
|
||||
"required": false,
|
||||
"description": "",
|
||||
"default": 0.3,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01,
|
||||
"node_mapping": {
|
||||
"node_id": "71",
|
||||
"input_field": "Number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameter_schema": {
|
||||
"input_image": {
|
||||
"param_type": "image",
|
||||
"required": false,
|
||||
"description": "支持的图片格式:JPG, PNG, WebP, BMP, TIFF",
|
||||
"default": "",
|
||||
"node_mapping": {
|
||||
"node_id": "30",
|
||||
"input_field": "image"
|
||||
},
|
||||
"accept": ".jpg,.jpeg,.png,.webp,.bmp,.tiff",
|
||||
"maxSize": 10485760
|
||||
},
|
||||
"prompt_strage": {
|
||||
"param_type": "float",
|
||||
"required": false,
|
||||
"description": "",
|
||||
"default": 0.3,
|
||||
"min": 0,
|
||||
"max": 1,
|
||||
"step": 0.01,
|
||||
"node_mapping": {
|
||||
"node_id": "71",
|
||||
"input_field": "Number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"author": ""
|
||||
}
|
||||
}
|
||||
178
功能清单.md
178
功能清单.md
@@ -1,178 +0,0 @@
|
||||
# MixVideo 功能清单
|
||||
|
||||
## 📋 功能概览
|
||||
|
||||
本文档记录了 MixVideo 项目的所有功能模块,基于代码库实际实现情况进行分类管理。
|
||||
|
||||
---
|
||||
|
||||
## 🟢 已完成功能 (后端API + 前端UI完整实现)
|
||||
|
||||
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
|
||||
| ------------ | ------------ | ---------------------------- | ------- | ------ | ------ |
|
||||
| **项目管理** | 项目CRUD | 创建、查看、编辑、删除项目 | ✅ | ✅ | ✅ 完成 |
|
||||
| **素材管理** | 素材导入管理 | 视频、音频、图片素材统一管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **素材管理** | 素材分段视图 | 素材片段查看和管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **素材管理** | 缩略图生成 | 自动生成视频缩略图 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模特管理** | 模特CRUD | 模特信息的完整管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模特管理** | 模特照片管理 | 模特照片上传和管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模特管理** | 模特动态管理 | 模特动态内容管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **AI分析** | 视频内容分类 | 基于AI的视频内容自动分类 | ✅ | ✅ | ✅ 完成 |
|
||||
| **AI分析** | AI分类设置 | AI分类规则配置管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模板系统** | 模板管理 | 视频模板的创建、导入、管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模板系统** | 模板匹配 | 智能模板匹配和推荐 | ✅ | ✅ | ✅ 完成 |
|
||||
| **模板系统** | 项目模板绑定 | 项目与模板的绑定管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **导出功能** | 剪映导出 | 导出到剪映格式 | ✅ | ✅ | ✅ 完成 |
|
||||
| **导出功能** | 导出记录管理 | 导出历史记录管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **素材绑定** | 素材模特绑定 | 素材与模特的关联管理 | ✅ | ✅ | ✅ 完成 |
|
||||
| **工具集** | 数据清洗工具 | JSONL格式数据去重处理 | ✅ | ✅ | ✅ 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 🟡 后端完成,前端开发中
|
||||
|
||||
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
|
||||
| ------------ | ------------------ | ---------------------- | ------- | ------ | -------- |
|
||||
| **水印处理** | 水印检测 | 自动检测视频中的水印 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **水印处理** | 水印去除 | AI水印去除功能 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **水印处理** | 水印添加 | 批量添加自定义水印 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **搜索功能** | 相似度搜索 | 基于内容的相似素材搜索 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **搜索功能** | 素材搜索 | 全文搜索素材内容 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **服装搭配** | 服装搭配搜索 | AI服装搭配分析和搜索 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **服装搭配** | 搭配推荐 | 智能服装搭配推荐 | ✅ | ✅ | 🔄 开发中 |
|
||||
| **服装搭配** | 搭配收藏 | 用户搭配收藏管理 | ✅ | 🔄 | <20> 开发中 |
|
||||
| **服装搭配** | 服装图片生成 | AI服装图片生成 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **服装搭配** | 服装照片生成 | 个性化服装照片生成 | ✅ | ✅ | 🔄 开发中 |
|
||||
| **AI生成** | 图片生成 | 基于AI的图片生成工具 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **AI生成** | 视频生成 | AI视频生成功能 | ✅ | ✅ | 🔄 开发中 |
|
||||
| **语音处理** | 语音克隆 | 个性化语音克隆技术 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **语音处理** | 语音合成 | 文本转语音功能 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **语音处理** | 系统音色管理 | 系统内置音色管理 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **视频处理** | 火山引擎视频 | 火山引擎视频生成集成 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **视频处理** | Hedra口型合成 | Hedra口型同步技术 | ✅ | ✅ | 🔄 开发中 |
|
||||
| **图片处理** | 图片编辑 | 高级图片编辑功能 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **对话系统** | 智能对话 | AI对话交互系统 | ✅ | 🔄 | 🔄 开发中 |
|
||||
| **AI集成** | ComfyUI集成 | ComfyUI工作流集成 | ✅ | ✅ | 🔄 开发中 |
|
||||
| **AI集成** | Bowong文本视频代理 | 第三方AI视频生成服务 | ✅ | 🔄 | 🔄 开发中 |
|
||||
|
||||
---
|
||||
|
||||
## 🔴 后端完成,前端未开始
|
||||
|
||||
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
|
||||
| ------------ | ------------ | ---------------------- | ------- | ------ | -------- |
|
||||
| **数据处理** | JSON容错解析 | 容错性JSON数据解析 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **数据处理** | Markdown解析 | Markdown文档解析和处理 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **标签系统** | 自定义标签 | 用户自定义标签系统 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **图片下载** | 批量图片下载 | 批量下载和管理图片 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **RAG系统** | RAG接地 | 检索增强生成系统 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **模板权重** | 模板片段权重 | 智能模板片段权重分析 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **目录设置** | 目录配置 | 高级目录结构配置 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **工作流** | 工作流管理 | 自动化工作流程管理 | ✅ | ❌ | ❌ 待开发 |
|
||||
| **系统功能** | 错误处理 | 统一错误处理机制 | ✅ | ❌ | ❌ 待开发 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 实验性功能 (部分实现)
|
||||
|
||||
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
|
||||
| ------------ | -------------- | ------------------ | ------- | ------ | -------- |
|
||||
| **缩略图** | 批量缩略图生成 | 批量生成视频缩略图 | ✅ | 🧪 | 🧪 实验中 |
|
||||
| **系统功能** | 性能监控 | 系统性能监控和优化 | 🧪 | ❌ | 🧪 实验中 |
|
||||
| **AI画板** | AI画板 | AI画板集成工作流 | 🧪 | ❌ | 🧪 实验中 |
|
||||
---
|
||||
|
||||
## 📊 功能统计
|
||||
|
||||
### 总体进度
|
||||
- **✅ 完全完成**: 16个功能模块 (37%)
|
||||
- **🔄 后端完成,前端开发中**: 16个功能模块 (37%)
|
||||
- **❌ 后端完成,前端未开始**: 9个功能模块 (21%)
|
||||
- **🧪 实验性功能**: 2个功能模块 (5%)
|
||||
- **📊 总计**: 43个功能模块
|
||||
|
||||
### 实现状态分析
|
||||
| 实现状态 | 数量 | 占比 | 说明 |
|
||||
| -------- | ---- | ---- | ------------------------- |
|
||||
| 完整实现 | 16 | 37% | 后端API + 前端UI都已完成 |
|
||||
| 后端完成 | 25 | 58% | 后端API已实现,前端待开发 |
|
||||
| 实验阶段 | 2 | 5% | 部分功能在测试验证中 |
|
||||
|
||||
### 按功能模块分类
|
||||
| 模块类别 | 功能数量 | 完整实现 | 后端完成 | 完成度 |
|
||||
| -------- | -------- | -------- | -------- | ------ |
|
||||
| 项目管理 | 1 | 1 | 1 | 100% |
|
||||
| 素材管理 | 3 | 3 | 3 | 100% |
|
||||
| 模特管理 | 3 | 3 | 3 | 100% |
|
||||
| AI分析 | 2 | 2 | 2 | 100% |
|
||||
| 模板系统 | 3 | 3 | 3 | 100% |
|
||||
| 导出功能 | 2 | 2 | 2 | 100% |
|
||||
| 素材绑定 | 1 | 1 | 1 | 100% |
|
||||
| 工具集 | 1 | 1 | 1 | 100% |
|
||||
| 水印处理 | 3 | 0 | 3 | 33% |
|
||||
| 搜索功能 | 2 | 0 | 2 | 50% |
|
||||
| 服装搭配 | 4 | 1 | 4 | 75% |
|
||||
| AI生成 | 2 | 0 | 2 | 50% |
|
||||
| 语音处理 | 3 | 0 | 3 | 33% |
|
||||
| 视频处理 | 2 | 0 | 2 | 50% |
|
||||
| 图片处理 | 1 | 0 | 1 | 0% |
|
||||
| 对话系统 | 1 | 0 | 1 | 0% |
|
||||
| AI集成 | 2 | 1 | 2 | 75% |
|
||||
| 数据处理 | 2 | 0 | 2 | 0% |
|
||||
| 标签系统 | 1 | 0 | 1 | 0% |
|
||||
| 图片下载 | 1 | 0 | 1 | 0% |
|
||||
| RAG系统 | 1 | 0 | 1 | 0% |
|
||||
| 模板权重 | 1 | 0 | 1 | 0% |
|
||||
| 目录设置 | 1 | 0 | 1 | 0% |
|
||||
| 工作流 | 1 | 0 | 1 | 0% |
|
||||
| 系统功能 | 2 | 0 | 1 | 25% |
|
||||
|
||||
### 开发优先级建议
|
||||
#### 🔥 高优先级 (用户核心功能)
|
||||
1. **水印处理** - 后端已完成,需要前端UI
|
||||
2. **搜索功能** - 核心检索能力,需要前端界面
|
||||
3. **服装搭配** - 主要业务功能,部分前端已完成
|
||||
|
||||
#### 🔶 中优先级 (增强功能)
|
||||
1. **AI生成** - 图片/视频生成,前端部分完成
|
||||
2. **语音处理** - 语音克隆和合成功能
|
||||
3. **视频处理** - 火山引擎和Hedra集成
|
||||
|
||||
#### 🔵 低优先级 (辅助功能)
|
||||
1. **数据处理** - JSON/Markdown解析工具
|
||||
2. **标签系统** - 自定义标签管理
|
||||
3. **系统功能** - 错误处理和性能监控
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心技术栈
|
||||
|
||||
### 前端技术
|
||||
- **框架**: React 18 + TypeScript 5.8
|
||||
- **构建工具**: Vite 6.0
|
||||
- **UI框架**: TailwindCSS 3.4
|
||||
- **状态管理**: Zustand 4.4
|
||||
- **路由**: React Router 6.20
|
||||
- **图标**: Lucide React + Heroicons
|
||||
|
||||
### 后端技术
|
||||
- **框架**: Tauri 2.0 + Rust 1.70+
|
||||
- **数据库**: SQLite (WAL模式,支持连接池)
|
||||
- **异步**: Tokio + async/await
|
||||
- **序列化**: Serde + JSON
|
||||
- **错误处理**: anyhow + thiserror
|
||||
|
||||
### AI集成
|
||||
- **主要AI服务**: Google Gemini API
|
||||
- **图片生成**: Midjourney集成
|
||||
- **视频生成**: 极梦、火山引擎等多平台
|
||||
- **语音处理**: 语音克隆和合成技术
|
||||
- **ComfyUI**: 工作流自动化
|
||||
|
||||
### 多媒体处理
|
||||
- **视频处理**: FFmpeg
|
||||
- **图像处理**: 水印检测/去除/添加
|
||||
- **缩略图**: 自动生成和管理
|
||||
- **格式支持**: 多种视频、音频、图片格式
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user