510 lines
12 KiB
Markdown
510 lines
12 KiB
Markdown
# VideoConcat 架构说明文档
|
||
|
||
## 架构概述
|
||
|
||
VideoConcat 采用经典的 MVVM(Model-View-ViewModel)架构模式,结合服务层和工具层,实现清晰的职责分离。
|
||
|
||
## 架构层次
|
||
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ View Layer │ (Views/)
|
||
│ (XAML + Code-Behind) │
|
||
└──────────────┬──────────────────────┘
|
||
│ Data Binding
|
||
┌──────────────▼──────────────────────┐
|
||
│ ViewModel Layer │ (ViewModels/)
|
||
│ (业务逻辑 + 命令处理) │
|
||
└──────────────┬──────────────────────┘
|
||
│
|
||
┌──────────────▼──────────────────────┐
|
||
│ Model Layer │ (Models/)
|
||
│ (数据模型 + 业务状态) │
|
||
└──────────────┬──────────────────────┘
|
||
│
|
||
┌──────────────▼──────────────────────┐
|
||
│ Service Layer │ (Services/)
|
||
│ (核心业务逻辑处理) │
|
||
└──────────────┬──────────────────────┘
|
||
│
|
||
┌──────────────▼──────────────────────┐
|
||
│ Tools Layer │ (Common/Tools/)
|
||
│ (工具类 + 辅助功能) │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
## 各层详细说明
|
||
|
||
### 1. View Layer(视图层)
|
||
|
||
**位置**: `Views/`
|
||
|
||
**职责**:
|
||
- 定义用户界面(XAML)
|
||
- 处理 UI 交互事件
|
||
- 数据绑定到 ViewModel
|
||
|
||
**主要文件**:
|
||
|
||
#### MainWindow.xaml / MainWindow.xaml.cs
|
||
- 主窗口,包含导航功能
|
||
- 通过 RadioButton 切换不同功能视图
|
||
- 动态加载 VideoWindow 或 ExtractWindow
|
||
|
||
**关键代码**:
|
||
```csharp
|
||
private void RadioButton_Checked(object sender, RoutedEventArgs e)
|
||
{
|
||
if (radioButton.Name == "extract")
|
||
{
|
||
var existingView = new ExtractWindow();
|
||
AddViewToColumn(mainGrid, existingView);
|
||
}
|
||
if (radioButton.Name == "video")
|
||
{
|
||
var existingView = new VideoWindow();
|
||
AddViewToColumn(mainGrid, existingView);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### VideoWindow.xaml / VideoWindow.xaml.cs
|
||
- 视频拼接功能界面
|
||
- 显示文件夹列表、视频列表、拼接结果
|
||
|
||
#### ExtractWindow.xaml / ExtractWindow.xaml.cs
|
||
- 视频抽帧和元数据修改功能界面
|
||
- 显示视频文件列表和处理结果
|
||
|
||
#### LoginWindow.xaml / LoginWindow.xaml.cs
|
||
- 用户登录界面
|
||
|
||
### 2. ViewModel Layer(视图模型层)
|
||
|
||
**位置**: `ViewModels/`
|
||
|
||
**职责**:
|
||
- 处理用户交互逻辑
|
||
- 协调 Model 和 Service
|
||
- 实现命令模式
|
||
- 管理 UI 状态
|
||
|
||
**主要文件**:
|
||
|
||
#### VideoViewModel.cs
|
||
- 视频拼接功能的视图模型
|
||
- 实现文件夹选择、视频组合生成、拼接执行等逻辑
|
||
|
||
**核心流程**:
|
||
1. 用户选择文件夹 → `ListFolder()` 扫描视频文件
|
||
2. 生成视频组合 → `GenerateCombinations()` 或顺序组合
|
||
3. 转换视频格式 → 并发转换为 TS 格式
|
||
4. 拼接视频 → 使用 FFmpeg concat
|
||
5. 添加水印(可选)→ 图片叠加
|
||
|
||
**关键特性**:
|
||
- 使用 `SemaphoreSlim` 控制并发数量(10个线程)
|
||
- 使用 `Task.Run` 进行异步处理
|
||
- 通过 `Dispatcher.Invoke` 更新 UI 线程
|
||
|
||
#### ExtractWindowViewModel.cs
|
||
- 视频抽帧和元数据修改的视图模型
|
||
- 实现批量处理逻辑
|
||
|
||
#### MainWindowViewModel.cs
|
||
- 主窗口视图模型(当前较简单)
|
||
|
||
### 3. Model Layer(模型层)
|
||
|
||
**位置**: `Models/`
|
||
|
||
**职责**:
|
||
- 定义数据模型
|
||
- 实现 INotifyPropertyChanged 接口
|
||
- 管理业务状态
|
||
|
||
**主要文件**:
|
||
|
||
#### VideoModel.cs
|
||
- 视频拼接功能的数据模型
|
||
- 包含文件夹信息、视频列表、拼接结果等
|
||
|
||
**关键属性**:
|
||
- `FolderInfos`: 文件夹信息集合
|
||
- `ConcatVideos`: 拼接结果集合
|
||
- `IsJoinType1Selected` / `IsJoinType2Selected`: 拼接模式
|
||
- `CanStart`: 是否可以开始(基于数量和状态)
|
||
|
||
**状态管理**:
|
||
```csharp
|
||
public void SetCanStart()
|
||
{
|
||
if (Num > 0 && Num <= MaxNum && IsStart == false)
|
||
{
|
||
CanStart = true;
|
||
}
|
||
else
|
||
{
|
||
CanStart = false;
|
||
}
|
||
IsCanOperate = !IsStart;
|
||
}
|
||
```
|
||
|
||
#### ExtractWindowModel.cs
|
||
- 抽帧功能的数据模型
|
||
- 包含视频文件列表和处理状态
|
||
|
||
#### MainWindowModel.cs
|
||
- 主窗口数据模型
|
||
|
||
### 4. Service Layer(服务层)
|
||
|
||
**位置**: `Services/`
|
||
|
||
**职责**:
|
||
- 实现核心业务逻辑
|
||
- 封装视频处理操作
|
||
- 与 FFmpeg 交互
|
||
|
||
**主要文件**:
|
||
|
||
#### VideoService.cs
|
||
- 视频服务,提供视频转换和拼接功能
|
||
|
||
**核心方法**:
|
||
|
||
1. **ConvertVideos**: 视频格式转换
|
||
```csharp
|
||
public static string ConvertVideos(string videoPath)
|
||
{
|
||
// 1. 分析视频信息
|
||
var video = FFProbe.Analyse(videoPath);
|
||
|
||
// 2. 生成 MD5 缓存文件名
|
||
string _tempMd5Name = GetLargeFileMD5(videoPath);
|
||
var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
|
||
|
||
// 3. 检查缓存
|
||
if (File.Exists(destinationPath))
|
||
return destinationPath;
|
||
|
||
// 4. 转换视频
|
||
FFMpeg.Convert(videoPath, destinationPath, VideoType.Ts);
|
||
|
||
return destinationPath;
|
||
}
|
||
```
|
||
|
||
2. **JoinVideos**: 视频拼接
|
||
- 使用 `FFMpegArguments.FromConcatInput()` 进行拼接
|
||
- 支持音频重新编码
|
||
- 可选添加图片水印
|
||
|
||
3. **GetLargeFileMD5**: 大文件 MD5 计算
|
||
- 使用流式读取,避免内存溢出
|
||
- 8KB 缓冲区优化
|
||
|
||
#### VideoProcess.cs
|
||
- 视频处理服务,提供抽帧、裁剪、元数据修改等功能
|
||
|
||
**核心方法**:
|
||
|
||
1. **RemoveFrameRandomeAsync**: 异步删除随机帧
|
||
- 处理 HEVC 编码(自动转换)
|
||
- 分割视频并重新合并
|
||
|
||
2. **ModifyByMetadata**: 修改元数据
|
||
- 添加唯一注释改变 MD5
|
||
- 使用流复制,不重新编码
|
||
|
||
3. **SubVideo / SubAudio**: 视频/音频裁剪
|
||
|
||
#### BaseService.cs
|
||
- 基础服务类(当前为空,可扩展)
|
||
|
||
### 5. Tools Layer(工具层)
|
||
|
||
**位置**: `Common/Tools/`
|
||
|
||
**职责**:
|
||
- 提供通用工具方法
|
||
- 封装第三方库调用
|
||
- 辅助功能实现
|
||
|
||
**主要文件**:
|
||
|
||
#### VideoCombine.cs
|
||
- 视频组合工具
|
||
- 提供组合生成和格式转换功能
|
||
|
||
**核心方法**:
|
||
```csharp
|
||
public static void GenerateCombinations(
|
||
List<List<string>> videoLists,
|
||
int index,
|
||
List<string> currentCombination,
|
||
List<List<string>> result)
|
||
{
|
||
// 递归生成所有可能的视频组合(笛卡尔积)
|
||
if (index == videoLists.Count)
|
||
{
|
||
result.Add([.. currentCombination]);
|
||
return;
|
||
}
|
||
|
||
foreach (string video in videoLists[index])
|
||
{
|
||
currentCombination.Add(video);
|
||
GenerateCombinations(videoLists, index + 1, currentCombination, result);
|
||
currentCombination.RemoveAt(currentCombination.Count - 1);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### LogUtils.cs
|
||
- 日志工具类
|
||
- 基于 log4net 封装
|
||
- 提供 Info、Debug、Error、Warn、Fatal 等方法
|
||
|
||
#### HttpUtils.cs
|
||
- HTTP 请求工具
|
||
- 封装 HttpClient
|
||
- 提供 GetAsync 和 PostAsync 方法
|
||
|
||
#### Config.cs
|
||
- 配置管理工具
|
||
- 读取和更新 App.config 配置
|
||
|
||
### 6. API Layer(API层)
|
||
|
||
**位置**: `Common/Api/`
|
||
|
||
**职责**:
|
||
- 定义 API 接口
|
||
- 封装 HTTP 请求
|
||
|
||
**主要文件**:
|
||
|
||
#### SystemApi.cs
|
||
- 系统 API
|
||
- 提供登录接口
|
||
|
||
## 数据流
|
||
|
||
### 视频拼接流程
|
||
|
||
```
|
||
用户操作
|
||
↓
|
||
VideoWindow (View)
|
||
↓ (命令绑定)
|
||
VideoViewModel
|
||
↓
|
||
1. ListFolder() → 扫描文件夹
|
||
↓
|
||
2. GenerateCombinations() → 生成组合
|
||
↓
|
||
3. ConvertVideos() → 转换格式 (并发)
|
||
↓
|
||
4. JoinVideos() → 拼接视频 (并发)
|
||
↓
|
||
5. 更新 VideoModel.ConcatVideos
|
||
↓
|
||
UI 自动更新 (数据绑定)
|
||
```
|
||
|
||
### 视频抽帧流程
|
||
|
||
```
|
||
用户操作
|
||
↓
|
||
ExtractWindow (View)
|
||
↓ (命令绑定)
|
||
ExtractWindowViewModel
|
||
↓
|
||
1. ListFolder() → 扫描视频文件
|
||
↓
|
||
2. RemoveFrameRandomeAsync() → 抽帧处理 (并发)
|
||
↓
|
||
3. VideoProcess.RemoveFrameRandomeAsync()
|
||
↓
|
||
4. 输出到 out 文件夹
|
||
↓
|
||
UI 更新状态
|
||
```
|
||
|
||
## 设计模式
|
||
|
||
### 1. MVVM 模式
|
||
- **Model**: 数据模型和业务状态
|
||
- **View**: XAML 界面
|
||
- **ViewModel**: 连接 View 和 Model
|
||
|
||
### 2. 命令模式
|
||
- 自定义 `Command` 类实现 `ICommand` 接口
|
||
- 将 UI 操作封装为命令
|
||
|
||
### 3. 观察者模式
|
||
- 使用 `INotifyPropertyChanged` 实现属性变更通知
|
||
- 使用 `ObservableCollection` 实现集合变更通知
|
||
|
||
### 4. 单例模式
|
||
- `LogUtils` 使用静态方法,类似单例
|
||
|
||
## 并发处理
|
||
|
||
### SemaphoreSlim 控制并发
|
||
|
||
```csharp
|
||
SemaphoreSlim semaphore = new(10); // 限制10个并发
|
||
|
||
foreach (var item in items)
|
||
{
|
||
await semaphore.WaitAsync();
|
||
var task = Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
// 处理逻辑
|
||
}
|
||
finally
|
||
{
|
||
semaphore.Release();
|
||
}
|
||
});
|
||
tasks.Add(task);
|
||
}
|
||
|
||
await Task.WhenAll(tasks);
|
||
```
|
||
|
||
### 异步处理
|
||
|
||
- 使用 `async/await` 进行异步操作
|
||
- 使用 `Task.Run` 在后台线程执行耗时操作
|
||
- 使用 `Dispatcher.Invoke` 更新 UI 线程
|
||
|
||
## 缓存机制
|
||
|
||
### 视频转换缓存
|
||
|
||
基于 MD5 的文件名缓存:
|
||
```csharp
|
||
string _tempMd5Name = GetLargeFileMD5(videoPath);
|
||
var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
|
||
|
||
if (File.Exists(destinationPath))
|
||
{
|
||
return destinationPath; // 直接返回缓存文件
|
||
}
|
||
```
|
||
|
||
**优势**:
|
||
- 避免重复转换相同文件
|
||
- 提高处理速度
|
||
- 减少 CPU 和磁盘 I/O
|
||
|
||
## 错误处理
|
||
|
||
### 异常捕获
|
||
|
||
```csharp
|
||
try
|
||
{
|
||
// 主要处理逻辑
|
||
FFMpeg.Convert(videoPath, destinationPath, VideoType.Ts);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 备用方案
|
||
LogUtils.Info("视频转换失败!尝试另外一种转换");
|
||
try
|
||
{
|
||
FFMpegArguments
|
||
.FromFileInput(videoPath)
|
||
.OutputToFile(destinationPath, true, o => o
|
||
.WithVideoCodec("libx264")
|
||
// ... 其他参数
|
||
)
|
||
.ProcessSynchronously(true);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogUtils.Error($"{videoPath} 转换失败", ex);
|
||
LogUtils.Error($"{videoPath} 转换再次失败", e);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 日志记录
|
||
|
||
- 所有错误都记录到日志文件
|
||
- 使用 log4net 进行日志管理
|
||
- 日志文件按日期命名
|
||
|
||
## 资源管理
|
||
|
||
### 临时文件清理
|
||
|
||
```csharp
|
||
public static void Cleanup(List<string> pathList)
|
||
{
|
||
foreach (var path in pathList)
|
||
{
|
||
if (File.Exists(path))
|
||
{
|
||
File.Delete(path);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 文件流管理
|
||
|
||
使用 `using` 语句确保资源释放:
|
||
```csharp
|
||
using var md5 = MD5.Create();
|
||
using var stream = File.OpenRead(filePath);
|
||
// ... 处理逻辑
|
||
```
|
||
|
||
## 扩展性设计
|
||
|
||
### 1. 服务层扩展
|
||
- 可以添加新的 Service 类
|
||
- 继承 BaseService(如需要)
|
||
|
||
### 2. 工具类扩展
|
||
- 在 `Common/Tools/` 中添加新工具类
|
||
- 保持工具类的静态方法设计
|
||
|
||
### 3. API 扩展
|
||
- 在 `Common/Api/` 中添加新的 API 类
|
||
- 使用 HttpUtils 进行请求
|
||
|
||
### 4. 功能扩展
|
||
- 添加新的 View 和 ViewModel
|
||
- 在主窗口中添加导航入口
|
||
|
||
## 性能优化建议
|
||
|
||
1. **并发控制**: 已使用 SemaphoreSlim 限制并发
|
||
2. **缓存机制**: 已实现基于 MD5 的转换缓存
|
||
3. **异步处理**: 已使用 async/await 避免阻塞
|
||
4. **流式处理**: 大文件使用流式读取
|
||
|
||
**可进一步优化**:
|
||
- 添加进度报告机制
|
||
- 优化大文件 MD5 计算(可考虑分块并行)
|
||
- 添加视频预览功能(减少不必要的处理)
|
||
- 实现断点续传(大文件处理)
|
||
|
||
## 测试建议
|
||
|
||
1. **单元测试**: 为 Service 和 Tools 层添加单元测试
|
||
2. **集成测试**: 测试完整的视频处理流程
|
||
3. **性能测试**: 测试并发处理和大量文件处理
|
||
4. **异常测试**: 测试各种异常情况(文件不存在、格式不支持等)
|
||
|