diff --git a/.gitignore b/.gitignore
index 9491a2f..499a1f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
*.user
*.userosscache
*.sln.docstates
+/.idea
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/Common/Tools/LogUtils.cs b/Common/Tools/LogUtils.cs
index 54dbbe1..ecfb74b 100644
--- a/Common/Tools/LogUtils.cs
+++ b/Common/Tools/LogUtils.cs
@@ -96,6 +96,18 @@ namespace VideoConcat.Common.Tools
}
}
+ ///
+ /// Debug级 常规日志
+ ///
+ /// 日志信息
+ public static void DebugFormat(string info, params object?[]? args)
+ {
+ if (loginfo.IsDebugEnabled)
+ {
+ loginfo.DebugFormat(info, args);
+ }
+ }
+
///
/// Debug级 异常日志
///
diff --git a/Common/Tools/VideoCombine.cs b/Common/Tools/VideoCombine.cs
index d857e20..03d97fd 100644
--- a/Common/Tools/VideoCombine.cs
+++ b/Common/Tools/VideoCombine.cs
@@ -2,11 +2,15 @@
using FFMpegCore.Helpers;
using FFMpegCore;
using System.IO;
+using System.Security.Cryptography;
namespace VideoConcat.Common.Tools
{
internal class VideoCombine
{
+
+ public static List mustClearPath = [];
+
///
/// 生成所有组合
///
@@ -48,12 +52,38 @@ namespace VideoConcat.Common.Tools
{
var video = FFProbe.Analyse(videoPath);
+
+ string mediaInfo = "";
+
+ mediaInfo += $"视频: {videoPath}";
+ mediaInfo += $"时长: {video.Duration}";
+ mediaInfo += $"格式: {video.Format}";
+ // 视频流信息
+ foreach (var videoStream in video.VideoStreams)
+ {
+ mediaInfo += $"视频编码: {videoStream.CodecName}, 分辨率: {videoStream.Width}x{videoStream.Height}";
+ }
+ // 音频流信息
+ foreach (var audioStream in video.AudioStreams)
+ {
+ mediaInfo += $"音频编码: {audioStream.CodecName}, 采样率: {audioStream.SampleRateHz} Hz";
+ }
+
+ LogUtils.Info(mediaInfo);
+
+
FFMpegHelper.ConversionSizeExceptionCheck(video);
- string _tempPath = Path.GetDirectoryName(videoPath) ?? "";
+ string _tempMd5Name = GetLargeFileMD5(videoPath);
//GlobalFFOptions.Current.TemporaryFilesFolder
- var destinationPath = Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(videoPath)}{FileExtension.Ts}");
+ var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
+
+ if (File.Exists(destinationPath))
+ {
+ return destinationPath;
+ }
+
//Directory.CreateDirectory(GlobalFFOptions.Current.TemporaryFilesFolder);
try
{
@@ -61,9 +91,76 @@ namespace VideoConcat.Common.Tools
}
catch (Exception ex)
{
- LogUtils.Error($"{videoPath} 转换失败", ex);
+
+ LogUtils.Info("视频转换失败!尝试另外一种转换");
+ // 创建FFmpeg参数
+ try
+ {
+ // string _tempPathError = Path.GetDirectoryName(videoPath) ?? "";
+
+ //GlobalFFOptions.Current.TemporaryFilesFolder
+ //var destinationPathExecp = Path.Combine(Path.GetTempPath(), $"{Path.GetFileNameWithoutExtension(videoPath)}{FileExtension.Mp4}");
+
+
+ //VideoCombine.mustClearPath.Add(destinationPathExecp);
+
+ // 配置 FFmpeg 参数
+ //var options = new FFMpegArgumentOptions()
+ // .WithVideoCodec("libx264") // 指定支持 CRF 的编码器
+ // .WithConstantRateFactor(23) // 内置方法设置 CRF
+ // .WithAudioCodec("aac") // 音频编码器
+ // .WithFastStart(); // 流媒体优化
+
+
+ FFMpegArguments
+ .FromFileInput(videoPath)
+ .OutputToFile(destinationPath, true, o => o
+ .WithVideoCodec("libx264") // 设置视频编码器
+ .WithAudioCodec("aac") // 设置音频编码器
+ .WithAudioSamplingRate(44100)
+ .WithAudioBitrate(128000)
+ .WithConstantRateFactor(23) // 设置质量调整参数
+ .WithCustomArgument("-vf fps=30") // 强制指定帧率(例如 30fps)
+ .WithFastStart()
+ //.WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
+ //.CopyChannel()
+ //.WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB)
+ .ForceFormat(VideoType.Ts)
+ )
+ //.NotifyOnOutput(Console.WriteLine) // 打印 FFmpeg 详细日志
+ .ProcessSynchronously(true);
+ //FFMpeg.Convert(destinationPathExecp, destinationPath, VideoType.Ts);
+
+ }
+ catch (Exception e)
+ {
+ LogUtils.Error($"{videoPath} 转换失败", ex);
+ LogUtils.Error($"{videoPath} 转换再次失败", e);
+ }
+
}
+
return destinationPath;
}
+
+ public static string GetLargeFileMD5(string filePath)
+ {
+ using var md5 = MD5.Create();
+ using var stream = File.OpenRead(filePath);
+
+ byte[] buffer = new byte[8192]; // 8KB 缓冲区
+ int bytesRead;
+ long totalBytesRead = 0;
+
+ while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ md5.TransformBlock(buffer, 0, bytesRead, null, 0);
+ totalBytesRead += bytesRead;
+ // 可在此添加进度显示:Console.WriteLine($"已读取 {totalBytesRead / 1024 / 1024}MB");
+ }
+
+ md5.TransformFinalBlock(buffer, 0, 0);
+ return BitConverter.ToString(value: md5.Hash).Replace("-", "").ToLowerInvariant();
+ }
}
}
diff --git a/ViewModels/VideoViewModel.cs b/ViewModels/VideoViewModel.cs
index 07734f1..2860a04 100644
--- a/ViewModels/VideoViewModel.cs
+++ b/ViewModels/VideoViewModel.cs
@@ -91,7 +91,7 @@ namespace VideoConcat.ViewModels
VideoModel.ConcatVideos.Clear();
VideoModel.IsStart = true;
});
-
+
if (Directory.Exists($"{VideoModel.FolderPath}\\output") == false)
{
Directory.CreateDirectory($"{VideoModel.FolderPath}\\output");
@@ -120,10 +120,10 @@ namespace VideoConcat.ViewModels
// 复制原列表,避免修改原列表
- List> tempList = new(combinations);
+ List> tempList = [.. combinations];
string[] _converVideoPath = [];
- List _clearPath = [];
+
for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++)
{
@@ -146,7 +146,7 @@ namespace VideoConcat.ViewModels
{
try
{
- _clearPath.Add(VideoCombine.ConvertVideos(_path));
+ VideoCombine.mustClearPath.Add(VideoCombine.ConvertVideos(_path));
}
finally
{
@@ -172,17 +172,17 @@ namespace VideoConcat.ViewModels
await semaphore.WaitAsync();
var _task = Task.Run(() =>
{
+ string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
+ string _outPutName = Path.Combine($"{VideoModel.FolderPath}", "output", _tempFileName); ;
+
+
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) ?? "";
+ string _tempMd5Name = VideoCombine.GetLargeFileMD5(_videoPath);
//GlobalFFOptions.Current.TemporaryFilesFolder
- return Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(_videoPath)}{FileExtension.Ts}");
+ return Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
}).ToArray();
bool _isSuccess = false;
@@ -194,7 +194,13 @@ namespace VideoConcat.ViewModels
.FromConcatInput(temporaryVideoParts)
.OutputToFile(_outPutName, true, options => options
.CopyChannel()
- .WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc))
+ .WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc)
+ .WithFastStart()
+ .WithVideoCodec("copy") // 复制视频流
+ .WithAudioCodec("aac") // 重新编码音频
+ .WithAudioSamplingRate(44100) // 强制采样率
+ .WithCustomArgument("-movflags +faststart -analyzeduration 100M -probesize 100M")
+ )
.ProcessSynchronously();
}
catch (Exception ex)
@@ -240,7 +246,9 @@ namespace VideoConcat.ViewModels
.OutputToFile(
_outPutNameImg,
true,
- options => options.WithCustomArgument(_customArg)
+ options => options
+ .WithCustomArgument(_customArg)// 或显式指定编码器参数
+ .WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
)
.ProcessSynchronously();
@@ -254,11 +262,11 @@ namespace VideoConcat.ViewModels
}
- LogUtils.Info($"当前视频: {string.Join(";", combination)} 合并成功");
+ LogUtils.Info($"当前视频-[${_outPutName}]: {string.Join(";", combination)} 合并成功");
}
catch (Exception ex)
{
- LogUtils.Error($"视频:{string.Join(";", combination)} 合并失败", ex);
+ LogUtils.Error($"视频[${_outPutName}]:{string.Join(";", combination)} 合并失败", ex);
}
finally
{
@@ -277,8 +285,9 @@ namespace VideoConcat.ViewModels
LogUtils.Info($"所有视频拼接完成,用时{(endTime - startTime).TotalSeconds}秒");
VideoModel.IsStart = false;
- VideoCombine.Cleanup(_clearPath);
+ VideoCombine.Cleanup(VideoCombine.mustClearPath);
MessageBox.Show("所有视频拼接完成");
+ VideoCombine.mustClearPath = [];
});
});
}
@@ -292,7 +301,8 @@ namespace VideoConcat.ViewModels
try
{
DirectoryInfo dirD = dir as DirectoryInfo;
- DirectoryInfo[] folders = dirD.GetDirectories();
+ DirectoryInfo[] folders = [.. dirD.GetDirectories().OrderBy(d=>d.Name)];
+
VideoModel.FolderInfos.Clear();
//获取文件夹下所有视频文件
foreach (DirectoryInfo Folder in folders)
diff --git a/Views/MainWindow.xaml.cs b/Views/MainWindow.xaml.cs
index 8285f03..2321cfc 100644
--- a/Views/MainWindow.xaml.cs
+++ b/Views/MainWindow.xaml.cs
@@ -14,14 +14,7 @@ namespace VideoConcat.Views
InitializeComponent();
Username.Text = Config.GetSettingString("userName");
Password.Password = Config.GetSettingString("password");
- if (Config.GetSettingString("isRemember") == "true")
- {
- ckbRemember.IsChecked = true;
- }
- else
- {
- ckbRemember.IsChecked = false;
- }
+ ckbRemember.IsChecked = Config.GetSettingString("isRemember") == "true";
}
private void BtnExit_Click(object sender, RoutedEventArgs e)