diff --git a/Services/Video/VideoProcess.cs b/Services/Video/VideoProcess.cs index f6a5cbc..9783a07 100644 --- a/Services/Video/VideoProcess.cs +++ b/Services/Video/VideoProcess.cs @@ -1,5 +1,6 @@ using FFMpegCore; using FFMpegCore.Enums; +using Standard; using System; using System.IO; using System.Threading.Tasks; @@ -21,7 +22,8 @@ namespace VideoConcat.Services.Video { if (!File.Exists(inputPath)) return false; - + // 创建临时目录 + string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); try { // 1. 获取视频信息 @@ -35,8 +37,6 @@ namespace VideoConcat.Services.Video bool isHevc = videoStream.CodecName == "hevc"; - // 2. 创建临时目录 - string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempDir); if (isHevc) @@ -45,19 +45,19 @@ namespace VideoConcat.Services.Video { // 临时文件路径 - string videoConvert = Path.Combine(tempDir, "convert.mp4"); + string videoConvert = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); await FFMpegArguments.FromFileInput(inputPath) .OutputToFile(videoConvert, true, opt => // 设置输出格式 opt.WithVideoCodec("libx264") ).ProcessAsynchronously(); - mediaInfo = await FFProbe.AnalyseAsync(videoConvert); + mediaInfo = await FFProbe.AnalyseAsync(videoConvert); inputPath = videoConvert; } catch (Exception) { - File.Delete(outputPath); + throw new Exception("转换失败!"); } } @@ -65,27 +65,72 @@ namespace VideoConcat.Services.Video var totalDuration = mediaInfo.Duration.TotalSeconds; // 计算帧时间参数 double frameRate = videoStream.FrameRate; - double frameDuration = 1.0 / frameRate; // 一帧时长(秒) + double frameDuration = Math.Round(1.0 / frameRate, 6); // 一帧时长(秒) int totalFram = (int)(totalDuration * frameRate); // 2. 随机生成要删除的帧的时间点(避开最后一帧,防止越界) var random = new Random(); var randomFrame = random.Next(20, totalFram - 10); - double frameTime = (randomFrame - 1) * frameDuration; // 目标帧开始时间 - double nextFrameTime = frameTime + frameDuration; // 下一帧开始时间 + double frameTime = Math.Round((randomFrame - 1) * frameDuration,6); // 目标帧开始时间 + double nextFrameTime = Math.Round(randomFrame * frameDuration,6); // 下一帧开始时间 + // 临时文件路径 + string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); + string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); + string audioPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac"); + string videoNoaudioPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); + string finalyPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); + + string audioPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac"); + string audioPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac"); + + + // 分离视频流(不含音频) + await FFMpegArguments + .FromFileInput(inputPath) + .OutputToFile(videoNoaudioPath, true, opt => opt.WithCustomArgument("-an -c:v copy")) + .ProcessAsynchronously(); + + if (!File.Exists(videoNoaudioPath)) + return false; + + // 分离音频流(不含视频)(如有音频) + if (mediaInfo.PrimaryAudioStream != null) + { + await FFMpegArguments + .FromFileInput(inputPath) + .OutputToFile(audioPath, true, opt => opt.WithCustomArgument("-vn -c:a copy")) + .ProcessAsynchronously(); + + if (!File.Exists(audioPath)) + return false; + } + + + inputPath = videoNoaudioPath; + + - // 临时文件路径 - string videoPart1 = Path.Combine(tempDir, "video_part1.mp4"); - string videoPart2 = Path.Combine(tempDir, "video_part2.mp4"); bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, frameTime); bool hasSubVideo2 = SubVideo(inputPath, videoPart2, nextFrameTime, totalDuration); - if (hasSubVideo1 && hasSubVideo2) + if (!hasSubVideo1 || !hasSubVideo2) { - return JoinVideo(outputPath, [videoPart1, videoPart2]); + return false; } - return false; + + + bool isJoinSuccess = JoinVideo(finalyPath, [videoPart1, videoPart2]); + if (!isJoinSuccess) + { + return false; + } + + return await FFMpegArguments.FromFileInput(finalyPath) + .AddFileInput(audioPath) + .OutputToFile(outputPath, true, opt => + opt.WithCustomArgument("-c copy -shortest -y")) + .ProcessAsynchronously(); } catch (Exception ex) { @@ -93,17 +138,37 @@ namespace VideoConcat.Services.Video Console.WriteLine($"操作失败: {ex.Message}"); return false; } + finally + { + string[] files = Directory.GetFiles(tempDir); + foreach (string file in files) + { + File.Delete(file); + } + File.Delete(tempDir); + } } public static bool SubVideo(string inputPath, string outputPath, Double startSec, Double endSec) { - return FFMpeg.SubVideo(inputPath, outputPath, TimeSpan.FromSeconds(startSec), TimeSpan.FromSeconds(endSec)); + return FFMpegArguments + .FromFileInput(inputPath, true, options => options.Seek(TimeSpan.FromSeconds(startSec)).EndSeek(TimeSpan.FromSeconds(endSec))) + .OutputToFile(outputPath, true, options => options.CopyChannel().WithCustomArgument("-an")) + .ProcessSynchronously(); + } + + + public static bool SubAudio(string inputPath, string outputPath, Double startSec, Double endSec) + { + return FFMpegArguments + .FromFileInput(inputPath, true, options => options.Seek(TimeSpan.FromSeconds(startSec)).EndSeek(TimeSpan.FromSeconds(endSec))) + .OutputToFile(outputPath, true, options => options.CopyChannel()) + .ProcessSynchronously(); } public static bool JoinVideo(string outPutPath, string[] videoParts) { return FFMpeg.Join(outPutPath, videoParts); } - } } diff --git a/ViewModels/ExtractWindowViewModel.cs b/ViewModels/ExtractWindowViewModel.cs index 01402b2..344e90c 100644 --- a/ViewModels/ExtractWindowViewModel.cs +++ b/ViewModels/ExtractWindowViewModel.cs @@ -1,15 +1,16 @@ -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; using FFMpegCore.Enums; -using static VideoConcat.Models.VideoModel; -using System.Windows.Threading; +using Microsoft.Expression.Drawing.Core; +using System.IO; using System.Windows; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Threading; +using VideoConcat.Common.Tools; +using VideoConcat.Models; using VideoConcat.Services.Video; +using static VideoConcat.Models.VideoModel; +using MessageBox = System.Windows.MessageBox; namespace VideoConcat.ViewModels { @@ -58,20 +59,38 @@ namespace VideoConcat.ViewModels DoExcue = obj => { ExtractWindowModel.HelpInfo = ""; - Task.Run(action: () => + + + SemaphoreSlim semaphore = new(10); // Limit to 3 threads + + List _tasks = []; + + ExtractWindowModel.videos.ForEach(async (video) => { - ExtractWindowModel.videos.ForEach(async (video) => + await semaphore.WaitAsync(); // Wait when more than 3 threads are running + var _task = Task.Run(async () => { - // 实例化并调用 - var remover = new VideoProcess(); - // 删除4秒处的帧(需根据实际帧位置调整) - string _tmpPath = Path.GetDirectoryName(video) ?? ""; - string _tmpFileName = $"抽帧视频-{Path.GetFileName(video)}"; - await VideoProcess.RemoveFrameRandomeAsync(video, $"{_tmpPath}\\{_tmpFileName}"); + try + { + // 实例化并调用 + var remover = new VideoProcess(); + // 删除4秒处的帧(需根据实际帧位置调整) + string _tmpPath = Path.GetDirectoryName(video) ?? ""; + string _tmpFileName = $"抽帧视频-{Path.GetFileName(video)}"; + await VideoProcess.RemoveFrameRandomeAsync(video, $"{_tmpPath}\\{_tmpFileName}"); + } + finally + { + semaphore.Release(); // Work is done, signal to semaphore that more work is possible + } }); + _tasks.Add(_task); }); - ExtractWindowModel.HelpInfo = "全部完成!"; + Task.WhenAll(_tasks).ContinueWith((task) => + { + ExtractWindowModel.HelpInfo = "全部完成!"; + }); } } };