VideoConcat/docs/架构说明.md
2026-01-01 15:39:54 +08:00

12 KiB
Raw Blame History

VideoConcat 架构说明文档

架构概述

VideoConcat 采用经典的 MVVMModel-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

关键代码:

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: 是否可以开始(基于数量和状态)

状态管理:

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: 视频格式转换

    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

  • 视频组合工具
  • 提供组合生成和格式转换功能

核心方法:

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 LayerAPI层

位置: 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 控制并发

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 的文件名缓存:

string _tempMd5Name = GetLargeFileMD5(videoPath);
var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");

if (File.Exists(destinationPath))
{
    return destinationPath; // 直接返回缓存文件
}

优势:

  • 避免重复转换相同文件
  • 提高处理速度
  • 减少 CPU 和磁盘 I/O

错误处理

异常捕获

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 进行日志管理
  • 日志文件按日期命名

资源管理

临时文件清理

public static void Cleanup(List<string> pathList)
{
    foreach (var path in pathList)
    {
        if (File.Exists(path))
        {
            File.Delete(path);
        }
    }
}

文件流管理

使用 using 语句确保资源释放:

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