using System.Windows.Input; using VideoConcat.Models; using MessageBox = System.Windows.MessageBox; using VideoConcat.Common.Tools; using System.IO; using Microsoft.Expression.Drawing.Core; using FFMpegCore; using FFMpegCore.Enums; using static VideoConcat.Models.VideoModel; using System.Windows.Threading; using System.Windows; namespace VideoConcat.ViewModels { public class VideoViewModel { private VideoModel _videoModel; public List ConcatVideos { get; set; } = []; public VideoModel VideoModel { get { return _videoModel; } set { _videoModel = value; } } public VideoViewModel() { VideoModel = new VideoModel { FolderInfos = [], ConcatVideos = [], MaxNum = 0, CanStart = false, IsStart = false, BtnOpenFolderCommand = new Command() { DoExcue = obj => { FolderBrowserDialog folderBrowserDialog = new(); if (folderBrowserDialog.ShowDialog() == DialogResult.OK) { VideoModel.FolderPath = folderBrowserDialog.SelectedPath; LogUtils.Info($"获取视频文件夹,视频路径:{VideoModel.FolderPath}"); ListFolder(VideoModel.FolderPath); } } }, BtnChooseAuditImageCommand = new Command() { DoExcue = obj => { // 创建一个 OpenFileDialog 实例 OpenFileDialog openFileDialog = new() { // 设置文件对话框的标题 Title = "选择广审图片", // 设置文件筛选器,只允许选择文本文件和图像文件 Filter = "图片文件[*.png;*.jpg;*.jpeg;*.bmp]|*.png;*.jpg;*.jpeg;*.bmp", }; // 显示文件对话框并获取结果 DialogResult result = openFileDialog.ShowDialog(); // 检查用户是否点击了打开按钮 if (result == DialogResult.OK) { // 获取用户选择的文件路径列表 VideoModel.AuditImagePath = openFileDialog.FileName; } } }, BtnStartVideoConcatCommand = new Command() { DoExcue = obj => { Task.Run(action: async () => { VideoModel.Dispatcher.Invoke(() => { VideoModel.ConcatVideos.Clear(); VideoModel.IsStart = true; }); if (Directory.Exists($"{VideoModel.FolderPath}\\output") == false) { Directory.CreateDirectory($"{VideoModel.FolderPath}\\output"); } //开始时间 DateTime startTime = DateTime.Now; LogUtils.Info("开始合并视频,进行视频拼接组合"); List> combinations = []; List currentCombination = []; List> videoLists = []; VideoModel.FolderInfos.ForEach(folderInfo => { videoLists.Add(folderInfo.VideoPaths); }); VideoCombine.GenerateCombinations(videoLists, 0, currentCombination, combinations); List> result = []; Random random = new(); // 复制原列表,避免修改原列表 List> tempList = new(combinations); string[] _converVideoPath = []; List _clearPath = []; for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++) { int index = random.Next(tempList.Count); result.Add(tempList[index]); _converVideoPath = [.. _converVideoPath, .. tempList[index]]; tempList.RemoveAt(index); } SemaphoreSlim semaphore = new(10); // Limit to 3 threads List _tasks = []; foreach (var _path in _converVideoPath) { await semaphore.WaitAsync(); // Wait when more than 3 threads are running var _task = Task.Run(() => { try { _clearPath.Add(VideoCombine.ConvertVideos(_path)); } finally { semaphore.Release(); // Work is done, signal to semaphore that more work is possible } }); _tasks.Add(_task); } await Task.WhenAll(_tasks).ContinueWith((task) => { LogUtils.Info($"转换完成,用时{(DateTime.Now - startTime).TotalSeconds}秒"); }); LogUtils.Info("开始拼接视频"); List taskList = []; semaphore = new(10); foreach (List combination in result) { await semaphore.WaitAsync(); var _task = Task.Run(() => { try { string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4"; string _outPutName = Path.Combine($"{VideoModel.FolderPath}", "output", _tempFileName); ; var temporaryVideoParts = combination.Select((_videoPath) => { string _tempPath = Path.GetDirectoryName(_videoPath) ?? ""; //GlobalFFOptions.Current.TemporaryFilesFolder return Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(_videoPath)}{FileExtension.Ts}"); }).ToArray(); bool _isSuccess = false; try { _isSuccess = FFMpegArguments .FromConcatInput(temporaryVideoParts) .OutputToFile(_outPutName, true, options => options .CopyChannel() .WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc)) .ProcessSynchronously(); } catch (Exception ex) { _isSuccess = false; LogUtils.Error("拼接视频失败", ex); } //bool _isSuccess = FFMpeg.Join(_outPutName, [.. combination]); VideoModel.Dispatcher.Invoke(() => { IMediaAnalysis _mediaInfo = FFProbe.Analyse(_outPutName); FileInfo fileInfo = new(_outPutName); VideoModel.ConcatVideos.Add(new ConcatVideo() { Index = VideoModel.ConcatVideos.Count + 1, FileName = _tempFileName, Size = $"{fileInfo.Length / 1024 / 1024}MB", Seconds = ((int)_mediaInfo.Duration.TotalSeconds), Status = _isSuccess ? "拼接成功" : "拼接失败", Progress = "100%", }); }); if (_isSuccess && VideoModel.AuditImagePath != "") { // 使用 FFMpegCore 执行添加图片到视频的操作 try { // 配置 FFmpeg 二进制文件位置(如果 FFmpeg 不在系统路径中) // GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "path/to/ffmpeg/bin" }); string _outPutNameImg = $"{VideoModel.FolderPath}\\output\\{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4"; string _customArg = "-filter_complex \"[0:v][1:v] overlay=0:H-h\" "; // 使用 FFMpegArguments 构建命令 bool _isCoverSuccess = FFMpegArguments .FromFileInput(_outPutName) .AddFileInput(VideoModel.AuditImagePath) .OutputToFile( _outPutNameImg, true, options => options.WithCustomArgument(_customArg) ) .ProcessSynchronously(); LogUtils.Info($"图片已成功添加到视频中,输出文件:{_outPutName}"); } catch (Exception ex) { LogUtils.Error($"图片添加到视频中失败", ex); } } LogUtils.Info($"当前视频: {string.Join(";", combination)} 合并成功"); } catch (Exception ex) { LogUtils.Error($"视频:{string.Join(";", combination)} 合并失败", ex); } finally { semaphore.Release(); } }); taskList.Add(_task); } await Task.WhenAll(taskList).ContinueWith((s) => { //结束时间 DateTime endTime = DateTime.Now; LogUtils.Info($"所有视频拼接完成,用时{(endTime - startTime).TotalSeconds}秒"); VideoModel.IsStart = false; VideoCombine.Cleanup(_clearPath); MessageBox.Show("所有视频拼接完成"); }); }); } } }; } private void ListFolder(string path) { DirectoryInfo dir = new(path); try { DirectoryInfo dirD = dir as DirectoryInfo; DirectoryInfo[] folders = dirD.GetDirectories(); VideoModel.FolderInfos.Clear(); //获取文件夹下所有视频文件 foreach (DirectoryInfo Folder in folders) { if (Folder.Name != "output") { string[] files = Directory.GetFiles(Folder.FullName, "*.mp4"); LogUtils.Info($"{Folder.Name}下有{files.Length}个视频文件"); VideoModel.FolderInfos.Add(new VideoModel.FolderInfo { DirectoryInfo = Folder, Num = files.Length, VideoPaths = new List(files) }); } } VideoModel.UpdateSum(); } catch (Exception ex) { MessageBox.Show(ex.Message); return; } } } class Command : ICommand { public event EventHandler? CanExecuteChanged; public bool CanExecute(object? parameter) => true; public void Execute(object? parameter) { DoExcue?.Invoke(parameter); } public Action? DoExcue { get; set; } } }