# 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. **异常测试**: 测试各种异常情况（文件不存在、格式不支持等）

