update
This commit is contained in:
parent
e178f93663
commit
bda7e13042
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
/.idea
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|||||||
@ -96,6 +96,18 @@ namespace VideoConcat.Common.Tools
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Debug级 常规日志
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">日志信息</param>
|
||||||
|
public static void DebugFormat(string info, params object?[]? args)
|
||||||
|
{
|
||||||
|
if (loginfo.IsDebugEnabled)
|
||||||
|
{
|
||||||
|
loginfo.DebugFormat(info, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Debug级 异常日志
|
/// Debug级 异常日志
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -2,11 +2,15 @@
|
|||||||
using FFMpegCore.Helpers;
|
using FFMpegCore.Helpers;
|
||||||
using FFMpegCore;
|
using FFMpegCore;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace VideoConcat.Common.Tools
|
namespace VideoConcat.Common.Tools
|
||||||
{
|
{
|
||||||
internal class VideoCombine
|
internal class VideoCombine
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public static List<string> mustClearPath = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成所有组合
|
/// 生成所有组合
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,12 +52,38 @@ namespace VideoConcat.Common.Tools
|
|||||||
{
|
{
|
||||||
|
|
||||||
var video = FFProbe.Analyse(videoPath);
|
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);
|
FFMpegHelper.ConversionSizeExceptionCheck(video);
|
||||||
|
|
||||||
string _tempPath = Path.GetDirectoryName(videoPath) ?? "";
|
string _tempMd5Name = GetLargeFileMD5(videoPath);
|
||||||
|
|
||||||
//GlobalFFOptions.Current.TemporaryFilesFolder
|
//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);
|
//Directory.CreateDirectory(GlobalFFOptions.Current.TemporaryFilesFolder);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -61,9 +91,76 @@ namespace VideoConcat.Common.Tools
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,10 +120,10 @@ namespace VideoConcat.ViewModels
|
|||||||
|
|
||||||
|
|
||||||
// 复制原列表,避免修改原列表
|
// 复制原列表,避免修改原列表
|
||||||
List<List<string>> tempList = new(combinations);
|
List<List<string>> tempList = [.. combinations];
|
||||||
|
|
||||||
string[] _converVideoPath = [];
|
string[] _converVideoPath = [];
|
||||||
List<string> _clearPath = [];
|
|
||||||
|
|
||||||
for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++)
|
for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++)
|
||||||
{
|
{
|
||||||
@ -146,7 +146,7 @@ namespace VideoConcat.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clearPath.Add(VideoCombine.ConvertVideos(_path));
|
VideoCombine.mustClearPath.Add(VideoCombine.ConvertVideos(_path));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -172,17 +172,17 @@ namespace VideoConcat.ViewModels
|
|||||||
await semaphore.WaitAsync();
|
await semaphore.WaitAsync();
|
||||||
var _task = Task.Run(() =>
|
var _task = Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
|
||||||
|
string _outPutName = Path.Combine($"{VideoModel.FolderPath}", "output", _tempFileName); ;
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
|
|
||||||
string _outPutName = Path.Combine($"{VideoModel.FolderPath}", "output", _tempFileName); ;
|
|
||||||
|
|
||||||
|
|
||||||
var temporaryVideoParts = combination.Select((_videoPath) =>
|
var temporaryVideoParts = combination.Select((_videoPath) =>
|
||||||
{
|
{
|
||||||
string _tempPath = Path.GetDirectoryName(_videoPath) ?? "";
|
string _tempMd5Name = VideoCombine.GetLargeFileMD5(_videoPath);
|
||||||
//GlobalFFOptions.Current.TemporaryFilesFolder
|
//GlobalFFOptions.Current.TemporaryFilesFolder
|
||||||
return Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(_videoPath)}{FileExtension.Ts}");
|
return Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
bool _isSuccess = false;
|
bool _isSuccess = false;
|
||||||
@ -194,7 +194,13 @@ namespace VideoConcat.ViewModels
|
|||||||
.FromConcatInput(temporaryVideoParts)
|
.FromConcatInput(temporaryVideoParts)
|
||||||
.OutputToFile(_outPutName, true, options => options
|
.OutputToFile(_outPutName, true, options => options
|
||||||
.CopyChannel()
|
.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();
|
.ProcessSynchronously();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -240,7 +246,9 @@ namespace VideoConcat.ViewModels
|
|||||||
.OutputToFile(
|
.OutputToFile(
|
||||||
_outPutNameImg,
|
_outPutNameImg,
|
||||||
true,
|
true,
|
||||||
options => options.WithCustomArgument(_customArg)
|
options => options
|
||||||
|
.WithCustomArgument(_customArg)// 或显式指定编码器参数
|
||||||
|
.WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
|
||||||
)
|
)
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
@ -254,11 +262,11 @@ namespace VideoConcat.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LogUtils.Info($"当前视频: {string.Join(";", combination)} 合并成功");
|
LogUtils.Info($"当前视频-[${_outPutName}]: {string.Join(";", combination)} 合并成功");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogUtils.Error($"视频:{string.Join(";", combination)} 合并失败", ex);
|
LogUtils.Error($"视频[${_outPutName}]:{string.Join(";", combination)} 合并失败", ex);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -277,8 +285,9 @@ namespace VideoConcat.ViewModels
|
|||||||
LogUtils.Info($"所有视频拼接完成,用时{(endTime - startTime).TotalSeconds}秒");
|
LogUtils.Info($"所有视频拼接完成,用时{(endTime - startTime).TotalSeconds}秒");
|
||||||
|
|
||||||
VideoModel.IsStart = false;
|
VideoModel.IsStart = false;
|
||||||
VideoCombine.Cleanup(_clearPath);
|
VideoCombine.Cleanup(VideoCombine.mustClearPath);
|
||||||
MessageBox.Show("所有视频拼接完成");
|
MessageBox.Show("所有视频拼接完成");
|
||||||
|
VideoCombine.mustClearPath = [];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -292,7 +301,8 @@ namespace VideoConcat.ViewModels
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
DirectoryInfo dirD = dir as DirectoryInfo;
|
DirectoryInfo dirD = dir as DirectoryInfo;
|
||||||
DirectoryInfo[] folders = dirD.GetDirectories();
|
DirectoryInfo[] folders = [.. dirD.GetDirectories().OrderBy(d=>d.Name)];
|
||||||
|
|
||||||
VideoModel.FolderInfos.Clear();
|
VideoModel.FolderInfos.Clear();
|
||||||
//获取文件夹下所有视频文件
|
//获取文件夹下所有视频文件
|
||||||
foreach (DirectoryInfo Folder in folders)
|
foreach (DirectoryInfo Folder in folders)
|
||||||
|
|||||||
@ -14,14 +14,7 @@ namespace VideoConcat.Views
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Username.Text = Config.GetSettingString("userName");
|
Username.Text = Config.GetSettingString("userName");
|
||||||
Password.Password = Config.GetSettingString("password");
|
Password.Password = Config.GetSettingString("password");
|
||||||
if (Config.GetSettingString("isRemember") == "true")
|
ckbRemember.IsChecked = Config.GetSettingString("isRemember") == "true";
|
||||||
{
|
|
||||||
ckbRemember.IsChecked = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ckbRemember.IsChecked = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnExit_Click(object sender, RoutedEventArgs e)
|
private void BtnExit_Click(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user