Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a28d3cac8e | |||
| da5f023390 | |||
| e2a75a64bd | |||
| dd2827dc71 | |||
| c6a1692f90 | |||
| bda7e13042 | |||
| e178f93663 |
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
|
||||||
|
|||||||
8
App.config
Normal file
8
App.config
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<appSettings>
|
||||||
|
<add key="userName" value=""/>
|
||||||
|
<add key="password" value=""/>
|
||||||
|
<add key="isRemember" value=""/>
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
||||||
6
App.xaml
6
App.xaml
@ -2,14 +2,16 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:VideoConcat"
|
xmlns:local="clr-namespace:VideoConcat"
|
||||||
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
|
xmlns:conv="clr-namespace:VideoConcat.Conversions"
|
||||||
StartupUri="Views/MainWindow.xaml">
|
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers" xmlns:local1="clr-namespace:VideoConcat.Views"
|
||||||
|
StartupUri="Views/LoginWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Light.Blue.xaml"/>
|
<ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Light.Blue.xaml"/>
|
||||||
<!--需要注意 wd:Resources 必须在配色主题后,Theme="Dark" 为黑色皮肤-->
|
<!--需要注意 wd:Resources 必须在配色主题后,Theme="Dark" 为黑色皮肤-->
|
||||||
<wd:Resources Theme="Light"/>
|
<wd:Resources Theme="Light"/>
|
||||||
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Theme.xaml"/>
|
<ResourceDictionary Source="pack://application:,,,/WPFDevelopers;component/Themes/Theme.xaml"/>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
using System.Net;
|
||||||
using VideoConcat.Common.Tools;
|
using VideoConcat.Common.Tools;
|
||||||
|
|
||||||
namespace VideoConcat.Common.Api.Base
|
namespace VideoConcat.Common.Api.Base
|
||||||
@ -7,9 +8,26 @@ namespace VideoConcat.Common.Api.Base
|
|||||||
{
|
{
|
||||||
public static async Task<ApiResponse<UserLoginResponse>> LoginAsync<UserLoginResponse>(string username, string password)
|
public static async Task<ApiResponse<UserLoginResponse>> LoginAsync<UserLoginResponse>(string username, string password)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
string pcMachineName = Environment.MachineName;
|
||||||
|
string pcUserName = Environment.UserName;
|
||||||
|
|
||||||
|
string name = Dns.GetHostName();
|
||||||
|
string ipadrlist = Dns.GetHostAddresses(name)?.ToString()??"";
|
||||||
|
|
||||||
HttpHelper Http = new();
|
HttpHelper Http = new();
|
||||||
ApiResponse<UserLoginResponse> res = await Http.PostAsync<UserLoginResponse>("/api/base/login", new { Username = username, Password = password, Platform = "pc" });
|
ApiResponse<UserLoginResponse> res = await Http.PostAsync<UserLoginResponse>("/api/base/login",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password,
|
||||||
|
Platform = "pc",
|
||||||
|
PcName = pcMachineName,
|
||||||
|
PcUserName = pcUserName,
|
||||||
|
Ips = ipadrlist
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
Common/Tools/Config.cs
Normal file
48
Common/Tools/Config.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VideoConcat.Common.Tools
|
||||||
|
{
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 读取客户设置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingName"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetSettingString(string settingName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string settingString = ConfigurationManager.AppSettings[settingName].ToString();
|
||||||
|
return settingString;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新设置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingName"></param>
|
||||||
|
/// <param name="valueName"></param>
|
||||||
|
public static void UpdateSettingString(string settingName, string valueName)
|
||||||
|
{
|
||||||
|
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
||||||
|
|
||||||
|
if (ConfigurationManager.AppSettings[settingName] != null)
|
||||||
|
{
|
||||||
|
config.AppSettings.Settings.Remove(settingName);
|
||||||
|
}
|
||||||
|
config.AppSettings.Settings.Add(settingName, valueName);
|
||||||
|
config.Save(ConfigurationSaveMode.Modified);
|
||||||
|
ConfigurationManager.RefreshSection("appSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
@ -14,7 +18,7 @@ namespace VideoConcat.Common.Tools
|
|||||||
{
|
{
|
||||||
if (index == videoLists.Count)
|
if (index == videoLists.Count)
|
||||||
{
|
{
|
||||||
result.Add(new List<string>(currentCombination));
|
result.Add([.. currentCombination]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
Conversions/EnumToBooleanConverter.cs
Normal file
23
Conversions/EnumToBooleanConverter.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace VideoConcat.Conversions
|
||||||
|
{
|
||||||
|
public class EnumToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value.Equals(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value?.Equals(true) == true ? parameter : System.Windows.Data.Binding.DoNothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
158
Models/ExtractWindowModel.cs
Normal file
158
Models/ExtractWindowModel.cs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
using Standard;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header;
|
||||||
|
using static VideoConcat.Models.VideoModel;
|
||||||
|
|
||||||
|
namespace VideoConcat.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public class ExtractWindowModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
|
||||||
|
private string _folderPath = "";
|
||||||
|
private string _helpInfo = "";
|
||||||
|
private bool _canExtractFrame = false;
|
||||||
|
private bool _canModify = false;
|
||||||
|
private bool _isCanOperate = false;
|
||||||
|
private bool _isStart = false;
|
||||||
|
private string[] _videos = [];
|
||||||
|
private Dispatcher _dispatcher;
|
||||||
|
|
||||||
|
public string[] videos
|
||||||
|
{
|
||||||
|
get => _videos;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_videos = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCanOperate
|
||||||
|
{
|
||||||
|
get => _isCanOperate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isCanOperate = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsStart
|
||||||
|
{
|
||||||
|
get => _isStart;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isStart = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dispatcher Dispatcher
|
||||||
|
{
|
||||||
|
get => _dispatcher;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_dispatcher = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class VideoStruct
|
||||||
|
{
|
||||||
|
public int Index { set; get; }
|
||||||
|
public string FileName { set; get; } = "";
|
||||||
|
public string Size { set; get; } = "";
|
||||||
|
public int Seconds { set; get; }
|
||||||
|
public string Status { set; get; } = "";
|
||||||
|
public string Progress { set; get; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FolderPath
|
||||||
|
{
|
||||||
|
get { return _folderPath; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_folderPath = value;
|
||||||
|
OnPropertyChanged(nameof(FolderPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HelpInfo
|
||||||
|
{
|
||||||
|
get { return _helpInfo; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_helpInfo = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanExtractFrame
|
||||||
|
{
|
||||||
|
get { return _canExtractFrame; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_canExtractFrame = value;
|
||||||
|
OnPropertyChanged(nameof(CanExtractFrame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public bool CanModify
|
||||||
|
{
|
||||||
|
get { return _canModify; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_canModify = value;
|
||||||
|
OnPropertyChanged(nameof(CanModify));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand? BtnOpenFolderCommand { get; set; }
|
||||||
|
public ICommand? BtnStartVideoConcatCommand { get; set; }
|
||||||
|
public ICommand? BtnStartVideoModifyCommand { get; set; }
|
||||||
|
public ICommand? BtnChooseAuditImageCommand { get; set; }
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ExtractWindowModel()
|
||||||
|
{
|
||||||
|
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCanStart()
|
||||||
|
{
|
||||||
|
CanExtractFrame = false;
|
||||||
|
if (videos.Length > 0)
|
||||||
|
{
|
||||||
|
CanModify = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CanModify = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
Models/MainWindowModel.cs
Normal file
56
Models/MainWindowModel.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace VideoConcat.Models
|
||||||
|
{
|
||||||
|
class MainWindowModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
public enum OptionType { none, video, extract }
|
||||||
|
|
||||||
|
private OptionType _selectedOption;
|
||||||
|
public OptionType SelectedOption
|
||||||
|
{
|
||||||
|
get => _selectedOption;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedOption = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
// 状态变化时执行逻辑
|
||||||
|
HandleSelection(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSelection(OptionType option)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"选中了: {option}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EnumToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value?.Equals(parameter) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return (bool)value ? parameter : System.Windows.Data.Binding.DoNothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,18 +4,27 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Data;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header;
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Header;
|
||||||
|
using static VideoConcat.Models.VideoModel;
|
||||||
|
|
||||||
namespace VideoConcat.Models
|
namespace VideoConcat.Models
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public enum Gender
|
||||||
|
{
|
||||||
|
Male,
|
||||||
|
Female
|
||||||
|
}
|
||||||
public class VideoModel : INotifyPropertyChanged
|
public class VideoModel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private int _num;
|
private int _num;
|
||||||
@ -23,7 +32,7 @@ namespace VideoConcat.Models
|
|||||||
private string _folderPath = "";
|
private string _folderPath = "";
|
||||||
private string _auditImagePath = "";
|
private string _auditImagePath = "";
|
||||||
private bool _canStart = false;
|
private bool _canStart = false;
|
||||||
private bool _isCanOperate=false;
|
private bool _isCanOperate = false;
|
||||||
private bool _isStart = false;
|
private bool _isStart = false;
|
||||||
private ObservableCollection<FolderInfo> _FolderInfos = [];
|
private ObservableCollection<FolderInfo> _FolderInfos = [];
|
||||||
private ObservableCollection<ConcatVideo> _concatVideos = [];
|
private ObservableCollection<ConcatVideo> _concatVideos = [];
|
||||||
@ -192,14 +201,21 @@ namespace VideoConcat.Models
|
|||||||
int _temp = 1;
|
int _temp = 1;
|
||||||
if (FolderInfos.Count > 0)
|
if (FolderInfos.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (FolderInfo item in FolderInfos)
|
if (IsJoinType1Selected)
|
||||||
{
|
{
|
||||||
if (item.Num > 0)
|
foreach (FolderInfo item in FolderInfos)
|
||||||
{
|
{
|
||||||
_temp *= item.Num;
|
if (item.Num > 0)
|
||||||
|
{
|
||||||
|
_temp *= item.Num;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MaxNum = _temp;
|
||||||
|
}
|
||||||
|
if (IsJoinType2Selected)
|
||||||
|
{
|
||||||
|
MaxNum = FolderInfos[0].Num;
|
||||||
}
|
}
|
||||||
MaxNum = _temp;
|
|
||||||
SetCanStart();
|
SetCanStart();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -227,5 +243,29 @@ namespace VideoConcat.Models
|
|||||||
ConcatVideos = [];
|
ConcatVideos = [];
|
||||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _isJoinType1Selected;
|
||||||
|
private bool _isJoinType2Selected;
|
||||||
|
|
||||||
|
public bool IsJoinType1Selected
|
||||||
|
{
|
||||||
|
get { return _isJoinType1Selected; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isJoinType1Selected = value;
|
||||||
|
OnPropertyChanged(nameof(IsJoinType1Selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsJoinType2Selected
|
||||||
|
{
|
||||||
|
get { return _isJoinType2Selected; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isJoinType2Selected = value;
|
||||||
|
OnPropertyChanged(nameof(IsJoinType2Selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
Services/BaseService.cs
Normal file
12
Services/BaseService.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VideoConcat.Services
|
||||||
|
{
|
||||||
|
abstract class BaseService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
263
Services/Video/VideoProcess.cs
Normal file
263
Services/Video/VideoProcess.cs
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
using FFMpegCore;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
using Standard;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VideoConcat.Common.Tools;
|
||||||
|
using static System.Windows.Forms.DataFormats;
|
||||||
|
|
||||||
|
namespace VideoConcat.Services.Video
|
||||||
|
{
|
||||||
|
public class VideoProcess
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 同步删除视频指定帧和对应音频片段
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputPath">输入文件路径</param>
|
||||||
|
/// <param name="outputPath">输出文件路径</param>
|
||||||
|
/// <param name="frameNumber">要删除的帧编号(从1开始)</param>
|
||||||
|
/// <returns>操作是否成功</returns>
|
||||||
|
public static async Task<bool> RemoveFrameRandomeAsync(string inputPath, string outputPath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(inputPath))
|
||||||
|
return false;
|
||||||
|
// 创建临时目录
|
||||||
|
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 获取视频信息
|
||||||
|
IMediaAnalysis mediaInfo = await FFProbe.AnalyseAsync(inputPath);
|
||||||
|
var videoStream = mediaInfo.PrimaryVideoStream;
|
||||||
|
if (videoStream == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("没有找到视频流");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHevc = videoStream.CodecName == "hevc";
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
if (isHevc)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
// 临时文件路径
|
||||||
|
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);
|
||||||
|
|
||||||
|
inputPath = videoConvert;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
throw new Exception("转换失败!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 1. 获取视频信息
|
||||||
|
mediaInfo = await FFProbe.AnalyseAsync(inputPath);
|
||||||
|
videoStream = mediaInfo.PrimaryVideoStream;
|
||||||
|
if (videoStream == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("没有找到视频流");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 视频总时长(秒)
|
||||||
|
double totalDuration = mediaInfo.Duration.TotalSeconds;
|
||||||
|
double frameRate = videoStream.FrameRate;
|
||||||
|
double frameDuration = Math.Round(1.0 / frameRate, 6); // 一帧时长(秒)
|
||||||
|
int totalFram = (int)(totalDuration * frameRate);
|
||||||
|
|
||||||
|
var random = new Random();
|
||||||
|
var randomFrame = random.Next(20, (int)totalDuration);
|
||||||
|
|
||||||
|
//return RemoveVideoFrame(inputPath, outputPath, randomFrame);
|
||||||
|
|
||||||
|
|
||||||
|
string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
|
||||||
|
string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
|
||||||
|
bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, randomFrame - 0.016);
|
||||||
|
bool hasSubVideo2 = SubVideo(inputPath, videoPart2, randomFrame, totalDuration);
|
||||||
|
if (!hasSubVideo1 || !hasSubVideo2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool isJoinSuccess = JoinVideo(outputPath, [videoPart1, videoPart2]);
|
||||||
|
if (!isJoinSuccess)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogUtils.Error("抽帧失败", ex);
|
||||||
|
Console.WriteLine($"操作失败: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
string[] files = Directory.GetFiles(tempDir);
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
File.Delete(tempDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过修改视频元数据(添加注释)改变MD5
|
||||||
|
/// </summary>
|
||||||
|
public static bool ModifyByMetadata(string inputPath, string outputPath, string comment = "JSY")
|
||||||
|
{
|
||||||
|
// 添加或修改视频元数据中的注释信息
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromFileInput(inputPath)
|
||||||
|
.OutputToFile(outputPath, true, options => options
|
||||||
|
//.WithVideoCodec("copy") // 直接复制视频流,不重新编码
|
||||||
|
//.WithAudioCodec("copy") // 直接复制音频流
|
||||||
|
.CopyChannel()
|
||||||
|
.WithCustomArgument($"-metadata comment=\"{comment}_{Guid.NewGuid()}\"") // 添加唯一注释
|
||||||
|
)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool SubVideo(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()) //.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ProcessVideo(string inputPath, string outputPath, string tempDir)
|
||||||
|
{
|
||||||
|
// 1. 获取视频信息
|
||||||
|
IMediaAnalysis mediaInfo = await FFProbe.AnalyseAsync(inputPath);
|
||||||
|
var videoStream = mediaInfo.PrimaryVideoStream;
|
||||||
|
if (videoStream == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("没有找到视频流");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 视频总时长(秒)
|
||||||
|
var totalDuration = mediaInfo.Duration.TotalSeconds;
|
||||||
|
|
||||||
|
// 计算帧时间参数
|
||||||
|
double frameRate = videoStream.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 = Math.Round((randomFrame - 1) * frameDuration, 6); // 目标帧开始时间
|
||||||
|
double nextFrameTime = Math.Round((randomFrame + 1) * 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;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, frameTime);
|
||||||
|
bool hasSubVideo2 = SubVideo(inputPath, videoPart2, nextFrameTime, totalDuration);
|
||||||
|
if (!hasSubVideo1 || !hasSubVideo2)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool RemoveVideoFrame(string inputPath, string outputPath, int frameToRemove)
|
||||||
|
{
|
||||||
|
// 使用FFMpegArguments构建转换流程
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromFileInput(inputPath)
|
||||||
|
.OutputToFile(outputPath, true, options => options
|
||||||
|
// 使用select过滤器排除指定帧(帧序号从0开始)
|
||||||
|
.WithCustomArgument($"select=not(eq(n\\,{frameToRemove}))")
|
||||||
|
|
||||||
|
// $"not(eq(n\\,{frameToRemove}))"); // 注意:在C#中需要转义反斜杠
|
||||||
|
//// 保持其他编码参数
|
||||||
|
.WithVideoCodec("libx264")
|
||||||
|
.WithAudioCodec("copy") // 如果不需要处理音频,直接复制
|
||||||
|
)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
Services/Video/VideoService.cs
Normal file
244
Services/Video/VideoService.cs
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
using FFMpegCore.Enums;
|
||||||
|
using FFMpegCore.Helpers;
|
||||||
|
using FFMpegCore;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VideoConcat.Common.Tools;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.IO;
|
||||||
|
using VideoConcat.Models;
|
||||||
|
using static VideoConcat.Models.VideoModel;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace VideoConcat.Services.Video
|
||||||
|
{
|
||||||
|
internal class VideoService : BaseService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将视频文件转换为ts格式
|
||||||
|
/// </summary>
|
||||||
|
public static string ConvertVideos(string 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);
|
||||||
|
|
||||||
|
string _tempMd5Name = GetLargeFileMD5(videoPath);
|
||||||
|
|
||||||
|
//GlobalFFOptions.Current.TemporaryFilesFolder
|
||||||
|
var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
|
||||||
|
|
||||||
|
if (File.Exists(destinationPath))
|
||||||
|
{
|
||||||
|
return destinationPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Directory.CreateDirectory(GlobalFFOptions.Current.TemporaryFilesFolder);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FFMpeg.Convert(videoPath, destinationPath, VideoType.Ts);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
|
||||||
|
LogUtils.Info("视频转换失败!尝试另外一种转换");
|
||||||
|
// 创建FFmpeg参数
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清理临时文件
|
||||||
|
/// </summary>
|
||||||
|
public static void Cleanup(List<string> pathList)
|
||||||
|
{
|
||||||
|
foreach (var path in pathList)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void JoinVideos(List<string> combination)
|
||||||
|
{
|
||||||
|
VideoModel videoModel = new();
|
||||||
|
|
||||||
|
if (Directory.Exists($"{videoModel.FolderPath}\\output") == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory($"{videoModel.FolderPath}\\output");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Random random = new();
|
||||||
|
string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
|
||||||
|
|
||||||
|
string _outPutName = Path.Combine($"{videoModel.FolderPath}", "output", _tempFileName); ;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var temporaryVideoParts = combination.Select((_videoPath) =>
|
||||||
|
{
|
||||||
|
string _tempMd5Name = VideoService.GetLargeFileMD5(_videoPath);
|
||||||
|
//GlobalFFOptions.Current.TemporaryFilesFolder
|
||||||
|
return Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
bool _isSuccess = false;
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isSuccess = FFMpegArguments
|
||||||
|
.FromConcatInput(temporaryVideoParts)
|
||||||
|
.OutputToFile(_outPutName, true, options => options
|
||||||
|
.CopyChannel()
|
||||||
|
.WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc)
|
||||||
|
.WithFastStart()
|
||||||
|
.WithVideoCodec("copy") // 复制视频流
|
||||||
|
.WithAudioCodec("aac") // 重新编码音频
|
||||||
|
.WithAudioSamplingRate(44100) // 强制采样率
|
||||||
|
.WithCustomArgument("-movflags +faststart -analyzeduration 100M -probesize 100M")
|
||||||
|
)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
catch (Exception ex1)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
LogUtils.Error("拼接视频失败", ex1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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)// 或显式指定编码器参数
|
||||||
|
.WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
|
||||||
|
)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
|
||||||
|
LogUtils.Info($"图片已成功添加到视频中,输出文件:{_outPutName}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogUtils.Error($"图片添加到视频中失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LogUtils.Info($"当前视频-[${_outPutName}]: {string.Join(";", combination)} 合并成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,38 +1,52 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ApplicationIcon>视频.ico</ApplicationIcon>
|
<ApplicationIcon>视频.ico</ApplicationIcon>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FFMpegCore" Version="5.1.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="log4net" Version="3.0.2" />
|
<PackageReference Include="LiveCharts" Version="0.9.7" />
|
||||||
<PackageReference Include="MaterialDesignXaml.DialogsHelper" Version="1.0.4" />
|
<PackageReference Include="LiveCharts.Wpf" Version="0.9.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
<PackageReference Include="log4net" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="MahApps.Metro.IconPacks.Core" Version="6.0.0" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="9.0.0" />
|
<PackageReference Include="MahApps.Metro.IconPacks.Material" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" />
|
<PackageReference Include="MaterialDesignXaml.DialogsHelper" Version="1.0.4" />
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||||
<PackageReference Include="WPFDevelopers" Version="0.0.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="System.IO.Pipelines" Version="9.0.8" />
|
||||||
|
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.8" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="9.0.8" />
|
||||||
|
<PackageReference Include="WPFDevelopers" Version="0.0.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ApplicationDefinition Update="App.xaml">
|
<AdditionalFiles Update="app.manifest">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</ApplicationDefinition>
|
</AdditionalFiles>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="log4net.config">
|
<ApplicationDefinition Update="App.xaml">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</None>
|
</ApplicationDefinition>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="App.config">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="log4net.config">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -5,8 +5,6 @@ VisualStudioVersion = 17.11.35327.3
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoConcat", "VideoConcat.csproj", "{2FF5691C-3184-4B68-944B-C704E64C4E4E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoConcat", "VideoConcat.csproj", "{2FF5691C-3184-4B68-944B-C704E64C4E4E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "视频", "..\视频\视频.vdproj", "{6253EBA0-190A-4A7D-AC55-954172807A46}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -17,8 +15,6 @@ Global
|
|||||||
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2FF5691C-3184-4B68-944B-C704E64C4E4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{6253EBA0-190A-4A7D-AC55-954172807A46}.Debug|Any CPU.ActiveCfg = Debug
|
|
||||||
{6253EBA0-190A-4A7D-AC55-954172807A46}.Release|Any CPU.ActiveCfg = Release
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
173
ViewModels/ExtractWindowViewModel.cs
Normal file
173
ViewModels/ExtractWindowViewModel.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
using FFMpegCore;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public class ExtractWindowViewModel
|
||||||
|
{
|
||||||
|
private ExtractWindowModel _extractWindowModel;
|
||||||
|
|
||||||
|
public List<ConcatVideo> ConcatVideos { get; set; } = [];
|
||||||
|
|
||||||
|
public ExtractWindowModel ExtractWindowModel
|
||||||
|
{
|
||||||
|
get { return _extractWindowModel; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_extractWindowModel = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ExtractWindowViewModel()
|
||||||
|
{
|
||||||
|
ExtractWindowModel = new ExtractWindowModel
|
||||||
|
{
|
||||||
|
CanExtractFrame = false,
|
||||||
|
CanModify = false,
|
||||||
|
IsStart = false,
|
||||||
|
IsCanOperate = true,
|
||||||
|
|
||||||
|
BtnOpenFolderCommand = new Command()
|
||||||
|
{
|
||||||
|
DoExcue = obj =>
|
||||||
|
{
|
||||||
|
FolderBrowserDialog folderBrowserDialog = new();
|
||||||
|
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
ExtractWindowModel.FolderPath = folderBrowserDialog.SelectedPath;
|
||||||
|
LogUtils.Info($"获取视频文件夹,视频路径:{ExtractWindowModel.FolderPath}");
|
||||||
|
ListFolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
BtnStartVideoConcatCommand = new Command()
|
||||||
|
{
|
||||||
|
DoExcue = obj =>
|
||||||
|
{
|
||||||
|
ExtractWindowModel.HelpInfo = "";
|
||||||
|
|
||||||
|
|
||||||
|
SemaphoreSlim semaphore = new(10); // Limit to 3 threads
|
||||||
|
|
||||||
|
List<Task> _tasks = [];
|
||||||
|
|
||||||
|
ExtractWindowModel.videos.ForEach(async (video) =>
|
||||||
|
{
|
||||||
|
await semaphore.WaitAsync(); // Wait when more than 3 threads are running
|
||||||
|
var _task = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 实例化并调用
|
||||||
|
var remover = new VideoProcess();
|
||||||
|
// 删除4秒处的帧(需根据实际帧位置调整)
|
||||||
|
string _tmpPath = Path.GetDirectoryName(video) ?? "";
|
||||||
|
string _tmpFileName = $"{(new Random()).Next(10000, 99999)}{Path.GetFileName(video)}";
|
||||||
|
|
||||||
|
string outPath = Path.Combine(_tmpPath, "out");
|
||||||
|
if (!Path.Exists(outPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await VideoProcess.RemoveFrameRandomeAsync(video, $"{_tmpPath}\\out\\{_tmpFileName}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release(); // Work is done, signal to semaphore that more work is possible
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_tasks.Add(_task);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task.WhenAll(_tasks).ContinueWith((task) =>
|
||||||
|
{
|
||||||
|
ExtractWindowModel.HelpInfo = "全部完成!";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
BtnStartVideoModifyCommand = new Command()
|
||||||
|
{
|
||||||
|
DoExcue = obj =>
|
||||||
|
{
|
||||||
|
ExtractWindowModel.HelpInfo = "";
|
||||||
|
|
||||||
|
|
||||||
|
SemaphoreSlim semaphore = new(10); // Limit to 3 threads
|
||||||
|
|
||||||
|
List<Task> _tasks = [];
|
||||||
|
|
||||||
|
ExtractWindowModel.videos.ForEach(async (video) =>
|
||||||
|
{
|
||||||
|
await semaphore.WaitAsync(); // Wait when more than 3 threads are running
|
||||||
|
var _task = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 实例化并调用
|
||||||
|
var remover = new VideoProcess();
|
||||||
|
// 删除4秒处的帧(需根据实际帧位置调整)
|
||||||
|
string _tmpPath = Path.GetDirectoryName(video) ?? "";
|
||||||
|
string _tmpFileName = $"{(new Random()).Next(10000, 99999)}{Path.GetFileName(video)}";
|
||||||
|
|
||||||
|
string outPath = Path.Combine(_tmpPath, "out");
|
||||||
|
if (!Path.Exists(outPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoProcess.ModifyByMetadata(video, $"{_tmpPath}\\out\\modify{_tmpFileName}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
semaphore.Release(); // Work is done, signal to semaphore that more work is possible
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_tasks.Add(_task);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task.WhenAll(_tasks).ContinueWith((task) =>
|
||||||
|
{
|
||||||
|
ExtractWindowModel.HelpInfo = "全部完成!";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListFolder()
|
||||||
|
{
|
||||||
|
DirectoryInfo dir = new(ExtractWindowModel.FolderPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DirectoryInfo dirD = dir as DirectoryInfo;
|
||||||
|
|
||||||
|
//获取文件夹下所有视频文件
|
||||||
|
|
||||||
|
ExtractWindowModel.videos = Directory.GetFiles(dirD.FullName, "*.mp4");
|
||||||
|
ExtractWindowModel.SetCanStart();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
ViewModels/MainWindowViewModel.cs
Normal file
19
ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VideoConcat.Models;
|
||||||
|
|
||||||
|
namespace VideoConcat.ViewModels
|
||||||
|
{
|
||||||
|
class MainWindowViewModel
|
||||||
|
{
|
||||||
|
private MainWindowModel _mainWindowModel;
|
||||||
|
public MainWindowModel MainWindowModel
|
||||||
|
{
|
||||||
|
get { return _mainWindowModel; }
|
||||||
|
set { _mainWindowModel = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,6 +35,9 @@ namespace VideoConcat.ViewModels
|
|||||||
{
|
{
|
||||||
FolderInfos = [],
|
FolderInfos = [],
|
||||||
ConcatVideos = [],
|
ConcatVideos = [],
|
||||||
|
IsJoinType1Selected = true,
|
||||||
|
IsJoinType2Selected = false,
|
||||||
|
|
||||||
MaxNum = 0,
|
MaxNum = 0,
|
||||||
CanStart = false,
|
CanStart = false,
|
||||||
IsStart = false,
|
IsStart = false,
|
||||||
@ -91,7 +94,7 @@ namespace VideoConcat.ViewModels
|
|||||||
VideoModel.ConcatVideos.Clear();
|
VideoModel.ConcatVideos.Clear();
|
||||||
VideoModel.IsStart = true;
|
VideoModel.IsStart = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Directory.Exists($"{VideoModel.FolderPath}\\output") == false)
|
if (Directory.Exists($"{VideoModel.FolderPath}\\output") == false)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory($"{VideoModel.FolderPath}\\output");
|
Directory.CreateDirectory($"{VideoModel.FolderPath}\\output");
|
||||||
@ -106,33 +109,52 @@ namespace VideoConcat.ViewModels
|
|||||||
List<List<string>> videoLists = [];
|
List<List<string>> videoLists = [];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
VideoModel.FolderInfos.ForEach(folderInfo =>
|
VideoModel.FolderInfos.ForEach(folderInfo =>
|
||||||
{
|
{
|
||||||
videoLists.Add(folderInfo.VideoPaths);
|
videoLists.Add(folderInfo.VideoPaths);
|
||||||
});
|
});
|
||||||
|
string[] _converVideoPath = [];
|
||||||
VideoCombine.GenerateCombinations(videoLists, 0, currentCombination, combinations);
|
|
||||||
|
|
||||||
|
|
||||||
List<List<string>> result = [];
|
List<List<string>> result = [];
|
||||||
Random random = new();
|
Random random = new();
|
||||||
|
if (VideoModel.IsJoinType1Selected)
|
||||||
|
|
||||||
// 复制原列表,避免修改原列表
|
|
||||||
List<List<string>> tempList = new(combinations);
|
|
||||||
|
|
||||||
string[] _converVideoPath = [];
|
|
||||||
List<string> _clearPath = [];
|
|
||||||
|
|
||||||
for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++)
|
|
||||||
{
|
{
|
||||||
int index = random.Next(tempList.Count);
|
VideoCombine.GenerateCombinations(videoLists, 0, currentCombination, combinations);
|
||||||
result.Add(tempList[index]);
|
|
||||||
|
|
||||||
_converVideoPath = [.. _converVideoPath, .. tempList[index]];
|
// 复制原列表,避免修改原列表
|
||||||
|
List<List<string>> tempList = [.. combinations];
|
||||||
|
for (int i = 0; i < VideoModel.Num && tempList.Count > 0; i++)
|
||||||
|
{
|
||||||
|
int index = random.Next(tempList.Count);
|
||||||
|
result.Add(tempList[index]);
|
||||||
|
|
||||||
tempList.RemoveAt(index);
|
_converVideoPath = [.. _converVideoPath, .. tempList[index]];
|
||||||
|
|
||||||
|
tempList.RemoveAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VideoModel.IsJoinType2Selected)
|
||||||
|
{
|
||||||
|
int count = videoLists[0].Count;
|
||||||
|
for (int index = 1; index < count; index++)
|
||||||
|
{
|
||||||
|
if (videoLists[0].Count != count)
|
||||||
|
{
|
||||||
|
WPFDevelopers.Controls.MessageBox.Show("请输入用户名或者密码!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
List<string> list2 = [];
|
||||||
|
foreach (List<string> list in videoLists)
|
||||||
|
{
|
||||||
|
_converVideoPath=[.. _converVideoPath, list[index]];
|
||||||
|
list2.Add(list[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(list2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SemaphoreSlim semaphore = new(10); // Limit to 3 threads
|
SemaphoreSlim semaphore = new(10); // Limit to 3 threads
|
||||||
@ -146,7 +168,7 @@ namespace VideoConcat.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_clearPath.Add(VideoCombine.ConvertVideos(_path));
|
VideoCombine.mustClearPath.Add(VideoCombine.ConvertVideos(_path));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -172,17 +194,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 +216,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 +268,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 +284,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 +307,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 +323,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)
|
||||||
|
|||||||
44
Views/ExtractWindow.xaml
Normal file
44
Views/ExtractWindow.xaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<UserControl x:Class="VideoConcat.Views.ExtractWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:VideoConcat.Views"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Height="800" Width="1000">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Red.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />
|
||||||
|
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Border CornerRadius="5" BorderBrush="White" BorderThickness="2,2,2,2">
|
||||||
|
<Grid>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="40"></RowDefinition>
|
||||||
|
<RowDefinition Height="*"></RowDefinition>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Border>
|
||||||
|
<WrapPanel>
|
||||||
|
<Label VerticalAlignment="Center" Width="100" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">视频文件夹:</Label>
|
||||||
|
<TextBox Grid.Column="1" Width="600" Name="FoldPath" Text="{Binding ExtractWindowModel.FolderPath,Mode=TwoWay}" IsReadOnly="True" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" materialDesign:HintAssist.Hint="选择包含需要处理的视频文件夹"/>
|
||||||
|
<Button Grid.Column="2" Width="80" Content="选择" FontSize="16" Command="{Binding ExtractWindowModel.BtnOpenFolderCommand}" Background="#40568D" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="选择含有视频的主文件夹" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding ExtractWindowModel.IsCanOperate}"/>
|
||||||
|
<Button Grid.Column="3" Width="80" Content="抽帧" FontSize="16" Command="{Binding ExtractWindowModel.BtnStartVideoConcatCommand}" Background="#3B94FE" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="开始处理视频" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2,0,2" IsEnabled="{Binding ExtractWindowModel.CanExtractFrame}"/>
|
||||||
|
<Button Grid.Column="4" Width="80" Content="修改" FontSize="16" Command="{Binding ExtractWindowModel.BtnStartVideoConcatCommand}" Background="#3B94FE" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="开始处理视频" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2,0,2" IsEnabled="{Binding ExtractWindowModel.CanModify}"/>
|
||||||
|
</WrapPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Row="1">
|
||||||
|
<TextBlock Text="{Binding ExtractWindowModel.HelpInfo,Mode=TwoWay}"></TextBlock>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
29
Views/ExtractWindow.xaml.cs
Normal file
29
Views/ExtractWindow.xaml.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using VideoConcat.ViewModels;
|
||||||
|
|
||||||
|
namespace VideoConcat.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ExtractWindow.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ExtractWindow : System.Windows.Controls.UserControl
|
||||||
|
{
|
||||||
|
public ExtractWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.DataContext = new ExtractWindowViewModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
94
Views/LoginWindow.xaml
Normal file
94
Views/LoginWindow.xaml
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<Window x:Class="VideoConcat.Views.LoginWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:helpers="clr-namespace:VideoConcat.Helpers"
|
||||||
|
xmlns:local="clr-namespace:VideoConcat"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="登录"
|
||||||
|
Height="330"
|
||||||
|
Width="500"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<Window.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Red.xaml"/>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml"/>
|
||||||
|
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Window.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="39*"/>
|
||||||
|
<ColumnDefinition Width="461*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border Grid.ColumnSpan="2">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<StackPanel Width="500">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Margin="20"
|
||||||
|
FontFamily="Great Vibes"
|
||||||
|
FontSize="38"
|
||||||
|
TextAlignment="Center">
|
||||||
|
用户登录
|
||||||
|
</TextBlock>
|
||||||
|
<StackPanel Margin="10"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<materialDesign:PackIcon
|
||||||
|
Width="30" Height="30"
|
||||||
|
Kind="User" Margin="10,0,10,0"/>
|
||||||
|
<TextBox x:Name="Username" Width="400"
|
||||||
|
Margin="10,0" BorderBrush="White"
|
||||||
|
CaretBrush="#FFD94448"
|
||||||
|
SelectionBrush="#FFD94448"
|
||||||
|
materialDesign:HintAssist.Hint="输入 用户名"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="10"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<materialDesign:PackIcon
|
||||||
|
Width="30" Height="30"
|
||||||
|
Kind="Lock" Margin="10,0,10,0"/>
|
||||||
|
<PasswordBox
|
||||||
|
Width="400"
|
||||||
|
Margin="10,0" BorderBrush="White"
|
||||||
|
CaretBrush="#FFD94448"
|
||||||
|
SelectionBrush="#FFD94448"
|
||||||
|
materialDesign:HintAssist.Hint="输入 密码"
|
||||||
|
x:Name="Password"/>
|
||||||
|
<PasswordBox
|
||||||
|
Width="400"
|
||||||
|
Margin="10,0"
|
||||||
|
BorderBrush="White"
|
||||||
|
CaretBrush="#FFD94448"
|
||||||
|
SelectionBrush="#FFD94448"
|
||||||
|
materialDesign:HintAssist.Hint="输入 密码"
|
||||||
|
helpers:PasswordBoxHelper.BindPassword="True"
|
||||||
|
helpers:PasswordBoxHelper.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
||||||
|
</PasswordBox>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="10" Orientation="Horizontal">
|
||||||
|
<CheckBox HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="30" x:Name="ckbRemember" Width="30" Height="30" Margin="15,0,10,0" IsChecked="True"></CheckBox>
|
||||||
|
<TextBlock VerticalAlignment="Center" Margin="10,0">记住我</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Button x:Name="btnLogin" Width="100" Height="40"
|
||||||
|
materialDesign:ButtonAssist.CornerRadius="2"
|
||||||
|
Background="#40568D" BorderBrush="#7F7F7F" BorderThickness="2"
|
||||||
|
Content="登录" ToolTip="登录"
|
||||||
|
Style="{StaticResource MaterialDesignRaisedButton}" Click="BtnLogin_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
67
Views/LoginWindow.xaml.cs
Normal file
67
Views/LoginWindow.xaml.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Windows;
|
||||||
|
using VideoConcat.Common.Api.Base;
|
||||||
|
using VideoConcat.Common.Tools;
|
||||||
|
|
||||||
|
namespace VideoConcat.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class LoginWindow : Window
|
||||||
|
{
|
||||||
|
public LoginWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Username.Text = Config.GetSettingString("userName");
|
||||||
|
Password.Password = Config.GetSettingString("password");
|
||||||
|
ckbRemember.IsChecked = Config.GetSettingString("isRemember") == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnExit_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnLogin_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
string _userName = Username.Text;
|
||||||
|
bool _isChecked = Convert.ToBoolean(ckbRemember.IsChecked);
|
||||||
|
string _password = Password.Password;
|
||||||
|
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_userName) || string.IsNullOrEmpty(_password))
|
||||||
|
{
|
||||||
|
Username.Clear();
|
||||||
|
Password.Clear();
|
||||||
|
WPFDevelopers.Controls.MessageBox.Show("请输入用户名或者密码!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ApiResponse<UserLoginResponse> res = await SystemApi.LoginAsync<UserLoginResponse>(_userName, _password);
|
||||||
|
if (res.Code != 0)
|
||||||
|
{
|
||||||
|
WPFDevelopers.Controls.MessageBox.Show(res.Msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_isChecked)
|
||||||
|
{
|
||||||
|
Config.UpdateSettingString("userName", _userName);
|
||||||
|
Config.UpdateSettingString("password", _password);
|
||||||
|
Config.UpdateSettingString("isRemember", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Config.UpdateSettingString("userName", "");
|
||||||
|
Config.UpdateSettingString("password", "");
|
||||||
|
Config.UpdateSettingString("isRemember", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
new MainWindow().Show();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,90 +1,141 @@
|
|||||||
<Window x:Class="VideoConcat.Views.MainWindow"
|
<Window x:Class="VideoConcat.Views.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:Icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
||||||
xmlns:helpers="clr-namespace:VideoConcat.Helpers"
|
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
|
||||||
xmlns:local="clr-namespace:VideoConcat"
|
xmlns:local="clr-namespace:VideoConcat.Views"
|
||||||
mc:Ignorable="d"
|
xmlns:vm="clr-namespace:VideoConcat.ViewModels"
|
||||||
Title="登录"
|
xmlns:conv="clr-namespace:VideoConcat.Conversions"
|
||||||
Height="300"
|
|
||||||
Width="500"
|
mc:Ignorable="d"
|
||||||
ResizeMode="NoResize"
|
Width="1100" Height="800" WindowStartupLocation="CenterScreen"
|
||||||
WindowStartupLocation="CenterScreen">
|
ResizeMode="CanMinimize"
|
||||||
|
Title="工具">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<ResourceDictionary>
|
<Style x:Key="BottomButton" TargetType="Button">
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
|
<Setter Property="Foreground" Value="#FFFFFF"/>
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"/>
|
<Setter Property="Width" Value="50"/>
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Red.xaml"/>
|
<Setter Property="Height" Value="50"/>
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml"/>
|
<Setter Property="BorderBrush" Value="White"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2,2,2,2"/>
|
||||||
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
<Setter Property="Margin" Value="1"/>
|
||||||
</ResourceDictionary>
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}" CornerRadius="5">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#5a5080"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" Value="#FFFFFF"/>
|
||||||
|
<Setter Property="BorderBrush" Value="GreenYellow"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2,2,2,2"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="MenuButton" TargetType="RadioButton">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Foreground" Value="#FFFFFF"/>
|
||||||
|
<Setter Property="Width" Value="50"/>
|
||||||
|
<Setter Property="Height" Value="50"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Black"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2,2,2,2"/>
|
||||||
|
|
||||||
|
<Setter Property="Margin" Value="1"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="RadioButton">
|
||||||
|
|
||||||
|
<Border Background="{TemplateBinding Background}" CornerRadius="5">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#5a5080"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter Property="Background" Value="Brown"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Black"/>
|
||||||
|
<Setter Property="BorderThickness" Value="2,2,2,2"/>
|
||||||
|
</Trigger>
|
||||||
|
<!-- 按下状态 -->
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" Value="#D0D0D0"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="MenuButtonIcon" TargetType="Icon:PackIconMaterial">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="White"/>
|
||||||
|
<Setter Property="Width" Value="24"/>
|
||||||
|
<Setter Property="Height" Value="18"/>
|
||||||
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="39*"/>
|
|
||||||
<ColumnDefinition Width="461*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border Grid.ColumnSpan="2">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<StackPanel Width="500">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Margin="20"
|
|
||||||
FontFamily="Great Vibes"
|
|
||||||
FontSize="38"
|
|
||||||
TextAlignment="Center">
|
|
||||||
用户登录
|
|
||||||
</TextBlock>
|
|
||||||
<StackPanel Margin="10"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<materialDesign:PackIcon
|
|
||||||
Width="30" Height="30"
|
|
||||||
Kind="User" Margin="10,0,10,0"/>
|
|
||||||
<TextBox x:Name="Username" Width="400"
|
|
||||||
Margin="10,0" BorderBrush="White"
|
|
||||||
CaretBrush="#FFD94448"
|
|
||||||
SelectionBrush="#FFD94448"
|
|
||||||
materialDesign:HintAssist.Hint="输入 用户名"/>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Margin="10"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<materialDesign:PackIcon
|
|
||||||
Width="30" Height="30"
|
|
||||||
Kind="Lock" Margin="10,0,10,0"/>
|
|
||||||
<PasswordBox
|
|
||||||
Width="400"
|
|
||||||
Margin="10,0" BorderBrush="White"
|
|
||||||
CaretBrush="#FFD94448"
|
|
||||||
SelectionBrush="#FFD94448"
|
|
||||||
materialDesign:HintAssist.Hint="输入 密码"
|
|
||||||
x:Name="Password"/>
|
|
||||||
<PasswordBox
|
|
||||||
Width="400"
|
|
||||||
Margin="10,0"
|
|
||||||
BorderBrush="White"
|
|
||||||
CaretBrush="#FFD94448"
|
|
||||||
SelectionBrush="#FFD94448"
|
|
||||||
materialDesign:HintAssist.Hint="输入 密码"
|
|
||||||
helpers:PasswordBoxHelper.BindPassword="True"
|
|
||||||
helpers:PasswordBoxHelper.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
|
|
||||||
</PasswordBox>
|
|
||||||
|
|
||||||
</StackPanel>
|
<Border Background="#cfd5e5" CornerRadius="5" BorderThickness="2" BorderBrush="#ebedf3" Padding="2" MouseDown="Border_MouseDown">
|
||||||
<StackPanel Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center">
|
<Border CornerRadius="5">
|
||||||
<Button x:Name="btnLogin" Width="100" Height="40"
|
<Border.Background>
|
||||||
materialDesign:ButtonAssist.CornerRadius="2"
|
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||||
Background="#40568D" BorderBrush="#7F7F7F" BorderThickness="2"
|
<GradientStop Color="#fefefe" Offset="0" />
|
||||||
Content="登录" ToolTip="登录"
|
<GradientStop Color="#ededef" Offset="1" />
|
||||||
Style="{StaticResource MaterialDesignRaisedButton}" Click="BtnLogin_Click"/>
|
</LinearGradientBrush>
|
||||||
</StackPanel>
|
</Border.Background>
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
</StackPanel>
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="70" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<!--Left Menu-->
|
||||||
|
<Border Background="#7163ba" CornerRadius="5" BorderThickness="2" BorderBrush="#ebedf3" Margin="2">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel VerticalAlignment="Top">
|
||||||
|
<RadioButton GroupName="OptionsGroup" Name="video" Style="{StaticResource MenuButton}" Checked="RadioButton_Checked" >
|
||||||
|
<StackPanel HorizontalAlignment="Center">
|
||||||
|
<Icon:PackIconMaterial Kind="VideoBox" Style="{StaticResource MenuButtonIcon}"/>
|
||||||
|
<TextBlock>视频</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</RadioButton>
|
||||||
|
<Separator></Separator>
|
||||||
|
<RadioButton GroupName="OptionsGroup" Name="extract" Style="{StaticResource MenuButton}" Checked="RadioButton_Checked">
|
||||||
|
<StackPanel HorizontalAlignment="Center">
|
||||||
|
<Icon:PackIconMaterial Kind="VideoCheck" Style="{StaticResource MenuButtonIcon}"/>
|
||||||
|
<TextBlock>抽帧</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</RadioButton>
|
||||||
|
<Separator></Separator>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel VerticalAlignment="Bottom">
|
||||||
|
<Button Style="{StaticResource BottomButton}">
|
||||||
|
<Icon:PackIconMaterial Kind="Account" Style="{StaticResource MenuButtonIcon}"/>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!--Main Section-->
|
||||||
|
<Border Grid.Column="1" CornerRadius="5" BorderThickness="2" BorderBrush="#ebedf3" Margin="2">
|
||||||
|
<Grid x:Name="mainGrid">
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Border>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@ -1,48 +1,101 @@
|
|||||||
using System.Windows;
|
using System.Globalization;
|
||||||
using VideoConcat.Common.Api.Base;
|
using System.Windows;
|
||||||
using VideoConcat.Common.Tools;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using VideoConcat.Models;
|
||||||
|
using RadioButton = System.Windows.Controls.RadioButton;
|
||||||
|
|
||||||
namespace VideoConcat.Views
|
namespace VideoConcat.Views
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interaction logic for MainWindow.xaml
|
/// MainWindow.xaml 的交互逻辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.DataContext = new MainWindowModel();
|
||||||
|
// 窗口加载完成后执行
|
||||||
|
Loaded += MainView_Loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnExit_Click(object sender, RoutedEventArgs e)
|
public void MainView_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Close();
|
// 1. 为目标Grid创建列定义
|
||||||
|
SetupGridColumns(mainGrid);
|
||||||
|
|
||||||
|
// 2. 实例化已有视图(或获取已存在的视图实例)
|
||||||
|
var existingView = new VideoWindow(); // 这里是已有视图的实例
|
||||||
|
|
||||||
|
// 3. 将视图添加到指定列中(例如第1列,索引为1)
|
||||||
|
AddViewToColumn(mainGrid, existingView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void BtnLogin_Click(object sender, RoutedEventArgs e)
|
/// <summary>
|
||||||
|
/// 为Grid设置列定义
|
||||||
|
/// </summary>
|
||||||
|
private void SetupGridColumns(Grid grid)
|
||||||
|
{
|
||||||
|
// 清空现有列(可选)
|
||||||
|
grid.Children.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将视图添加到Grid的指定列
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="grid">目标Grid</param>
|
||||||
|
/// <param name="view">要添加的视图(UserControl/FrameworkElement)</param>
|
||||||
|
/// <param name="columnIndex">列索引(从0开始)</param>
|
||||||
|
private void AddViewToColumn(Grid grid, FrameworkElement view)
|
||||||
{
|
{
|
||||||
|
|
||||||
string _userName = Username.Text;
|
// 将视图添加到Grid的子元素中
|
||||||
string _password = Password.Password;
|
grid.Children.Add(view);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_userName) || string.IsNullOrEmpty(_password))
|
private void Border_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (e.ChangedButton == MouseButton.Left)
|
||||||
{
|
{
|
||||||
Username.Clear();
|
this.DragMove();
|
||||||
Password.Clear();
|
|
||||||
WPFDevelopers.Controls.MessageBox.Show("请输入用户名或者密码!");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
ApiResponse<UserLoginResponse> res = await SystemApi.LoginAsync<UserLoginResponse>(_userName, _password);
|
}
|
||||||
if (res.Code !=0)
|
|
||||||
|
|
||||||
|
private void RadioButton_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is RadioButton radioButton)
|
||||||
{
|
{
|
||||||
WPFDevelopers.Controls.MessageBox.Show(res.Msg);
|
if (radioButton.Name == "extract")
|
||||||
}
|
{
|
||||||
else
|
SetupGridColumns(mainGrid);
|
||||||
{
|
// 2. 实例化已有视图(或获取已存在的视图实例)
|
||||||
new Video().Show();
|
var existingView = new ExtractWindow(); // 这里是已有视图的实例
|
||||||
Close();
|
|
||||||
|
// 3. 将视图添加到指定列中(例如第1列,索引为1)
|
||||||
|
AddViewToColumn(mainGrid, existingView);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radioButton.Name == "video")
|
||||||
|
{
|
||||||
|
// 1. 为目标Grid创建列定义
|
||||||
|
SetupGridColumns(mainGrid);
|
||||||
|
|
||||||
|
// 2. 实例化已有视图(或获取已存在的视图实例)
|
||||||
|
var existingView = new VideoWindow(); // 这里是已有视图的实例
|
||||||
|
|
||||||
|
// 3. 将视图添加到指定列中(例如第1列,索引为1)
|
||||||
|
AddViewToColumn(mainGrid, existingView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<Window x:Name="视频拼接" x:Class="VideoConcat.Views.Video"
|
<UserControl x:Name="视频拼接" x:Class="VideoConcat.Views.VideoWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@ -7,8 +7,8 @@
|
|||||||
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
|
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
|
||||||
xmlns:local="clr-namespace:VideoConcat.Views"
|
xmlns:local="clr-namespace:VideoConcat.Views"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="视频拼接" Height="800" Width="780" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
|
Height="800" Width="1000">
|
||||||
<Window.Resources>
|
<UserControl.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
|
||||||
@ -18,20 +18,25 @@
|
|||||||
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Window.Resources>
|
</UserControl.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<StackPanel >
|
<StackPanel >
|
||||||
<WrapPanel Height="120">
|
<WrapPanel Height="120">
|
||||||
<Label VerticalAlignment="Center" Width="100" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">视频文件夹:</Label>
|
<Label VerticalAlignment="Center" Width="100" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">视频文件夹:</Label>
|
||||||
<TextBox Grid.Column="1" Width="500" Name="FoldPath" Text="{Binding VideoModel.FolderPath,Mode=TwoWay}" IsReadOnly="True" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" materialDesign:HintAssist.Hint="选择视频主文件夹"/>
|
<TextBox Grid.Column="1" Width="740" Name="FoldPath" Text="{Binding VideoModel.FolderPath,Mode=TwoWay}" IsReadOnly="True" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" materialDesign:HintAssist.Hint="选择视频主文件夹"/>
|
||||||
<Button Grid.Column="2" Width="100" Content="选 择" FontSize="16" Command="{Binding VideoModel.BtnOpenFolderCommand}" Background="#40568D" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="选择含有视频的主文件夹" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.IsCanOperate}"/>
|
<Button Grid.Column="2" Width="100" Content="选 择" FontSize="16" Command="{Binding VideoModel.BtnOpenFolderCommand}" Background="#40568D" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="选择含有视频的主文件夹" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.IsCanOperate}"/>
|
||||||
<Label Grid.Row="1" Width="100" VerticalAlignment="Center" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">选择广审:</Label>
|
<Label Grid.Row="1" Width="100" VerticalAlignment="Center" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">选择广审:</Label>
|
||||||
<TextBox Grid.Row="1" Width="500" Grid.Column="1" Text="{Binding VideoModel.AuditImagePath,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" ToolTip="请选择" materialDesign:HintAssist.Hint="请选择"/>
|
<TextBox Grid.Row="1" Width="740" Grid.Column="1" Text="{Binding VideoModel.AuditImagePath,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" ToolTip="请选择" materialDesign:HintAssist.Hint="请选择"/>
|
||||||
<Button Grid.Row="1" Width="100" Grid.Column="2" Content="选择文件" FontSize="16" Command="{Binding VideoModel.BtnChooseAuditImageCommand}" Background="#E54858" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="选择广审" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.IsCanOperate}"/>
|
<Button Grid.Row="1" Width="100" Grid.Column="2" Content="选择文件" FontSize="16" Command="{Binding VideoModel.BtnChooseAuditImageCommand}" Background="#E54858" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="选择广审" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.IsCanOperate}"/>
|
||||||
<Label Grid.Row="1" Width="100" VerticalAlignment="Center" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">视频个数:</Label>
|
<Label Grid.Row="1" Width="100" VerticalAlignment="Center" FontSize="16" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" Style="{StaticResource MaterialDesignLabel}" VerticalContentAlignment="Stretch">视频个数:</Label>
|
||||||
<TextBox Grid.Row="1" Width="500" Grid.Column="1" Text="{Binding VideoModel.Num,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" ToolTip="请输入合成视频个数" materialDesign:HintAssist.Hint="请输入拼接视频数目" IsEnabled="{Binding VideoModel.IsCanOperate}" />
|
<TextBox Grid.Row="1" Width="740" Grid.Column="1" Text="{Binding VideoModel.Num,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Margin="5,2" BorderBrush="#7F7F7F" BorderThickness="2" FontSize="16" VerticalContentAlignment="Center" ToolTip="请输入合成视频个数" materialDesign:HintAssist.Hint="请输入拼接视频数目" IsEnabled="{Binding VideoModel.IsCanOperate}" />
|
||||||
<Button Grid.Row="1" Width="100" Grid.Column="2" Content="开始拼接" FontSize="16" Command="{Binding VideoModel.BtnStartVideoConcatCommand}" Background="#E54858" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="开始拼接视频" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.CanStart}" Cursor="Hand"/>
|
<Button Grid.Row="1" Width="100" Grid.Column="2" Content="开始拼接" FontSize="16" Command="{Binding VideoModel.BtnStartVideoConcatCommand}" Background="#E54858" BorderBrush="#7F7F7F" BorderThickness="2" ToolTip="开始拼接视频" Style="{StaticResource MaterialDesignRaisedButton}" Margin="5,2" IsEnabled="{Binding VideoModel.CanStart}" Cursor="Hand"/>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Height="40">
|
||||||
|
<Label FontWeight="ExtraBold" FontSize="16" Grid.Row="1">拼接方式:</Label>
|
||||||
|
<RadioButton Name="JoinType1" Content="随机" GroupName="JoinType" IsChecked="{Binding VideoModel.IsJoinType1Selected, Mode=TwoWay}" Margin="10,2" />
|
||||||
|
<RadioButton Name="JoinType2" Content="按顺序组合" GroupName="JoinType" IsChecked="{Binding VideoModel.IsJoinType2Selected, Mode=TwoWay}" Margin="10,2" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel Height="30">
|
<StackPanel Height="30">
|
||||||
<Label FontWeight="ExtraBold" FontSize="16" Grid.Row="1">文件信息:</Label>
|
<Label FontWeight="ExtraBold" FontSize="16" Grid.Row="1">文件信息:</Label>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@ -45,7 +50,7 @@
|
|||||||
</DataGrid>
|
</DataGrid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<StackPanel Height="30">
|
<StackPanel Height="30">
|
||||||
<Label FontWeight="ExtraBold" FontSize="16">视频合成进度:</Label>
|
<Label FontWeight="ExtraBold" FontSize="16">视频合成进度:</Label>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@ -64,4 +69,4 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</UserControl>
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -19,9 +20,9 @@ namespace VideoConcat.Views
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Video.xaml 的交互逻辑
|
/// Video.xaml 的交互逻辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Video : Window
|
public partial class VideoWindow : System.Windows.Controls.UserControl
|
||||||
{
|
{
|
||||||
public Video()
|
public VideoWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.DataContext=new VideoViewModel();
|
this.DataContext=new VideoViewModel();
|
||||||
79
app.manifest
Normal file
79
app.manifest
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<!-- UAC 清单选项
|
||||||
|
如果想要更改 Windows 用户帐户控制级别,请使用
|
||||||
|
以下节点之一替换 requestedExecutionLevel 节点。
|
||||||
|
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||||
|
|
||||||
|
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
|
||||||
|
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
|
||||||
|
元素。
|
||||||
|
-->
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
|
||||||
|
Windows 版本的列表。取消评论适当的元素,
|
||||||
|
Windows 将自动选择最兼容的环境。 -->
|
||||||
|
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
|
||||||
|
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
|
||||||
|
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
|
||||||
|
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
|
||||||
|
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
|
||||||
|
|
||||||
|
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
|
||||||
|
<!--
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||||
|
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
|
||||||
|
<!--
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</assembly>
|
||||||
Loading…
Reference in New Issue
Block a user