1. 视频裁剪软件套路多
最近再做一些测试,经常需要录屏什么的,有时候录制的时长视频,需要裁剪,比如去掉开头一些帧或者结尾的一些帧,就想保留关键点。但是网上下的一些软件,打开一用都是要付费的。所以想着手撸一个吧。。。
2. AI选择
比如chatgpt,豆包,kimi,deepseek等。个人比较推荐chatgpt,,理由??没有
3. 关键代码
AI生成后,直接修改下细节就可以用
using System.Text;
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.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.IO;
using System.Diagnostics;
using Microsoft.Win32;
namespace VideoTools
{
public partial class MainWindow : Window
{
private DispatcherTimer timer;
private bool isPlaying = false;
private bool isUserSeeking = false;
private TimeSpan frameStep = TimeSpan.FromSeconds(0.04); // 约25帧/秒
private TimeSpan startTime = TimeSpan.Zero;
private TimeSpan endTime = TimeSpan.Zero;
private string ffmpegPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FFmpeg", "ffmpeg.exe");
public MainWindow()
{
InitializeComponent();
// 初始化计时器用于更新进度条和时间显示
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(30); // 更频繁更新以显示毫秒变化
timer.Tick += Timer_Tick;
// 媒体状态改变事件
mediaElement.MediaOpened += MediaElement_MediaOpened;
mediaElement.MediaEnded += MediaElement_MediaEnded;
mediaElement.MediaFailed += MediaElement_MediaFailed;
// 检查FFmpeg是否可用
CheckFFmpegAvailability();
}
// 检查FFmpeg是否可用
private void CheckFFmpegAvailability()
{
try
{
if (!File.Exists(ffmpegPath))
{
exportStatusText.Text = "错误: 未找到FFmpeg。请确保ffmpeg.exe已包含在应用程序目录中。";
exportButton.IsEnabled = false;
return;
}
// 测试运行
Process.Start(new ProcessStartInfo
{
FileName = ffmpegPath,
Arguments = "-version",
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
});
exportStatusText.Text = "";//"FFmpeg已就绪";
}
catch (Exception ex)
{
exportStatusText.Text = $"错误: 无法运行FFmpeg - {ex.Message}";
exportButton.IsEnabled = false;
}
}
// 计时器事件,更新进度条和时间显示
private void Timer_Tick(object sender, EventArgs e)
{
if (mediaElement.NaturalDuration.HasTimeSpan && !isUserSeeking)
{
progressSlider.Value = mediaElement.Position.TotalSeconds;
currentTimeText.Text = FormatTime(mediaElement.Position);
}
}
// 媒体打开事件
private void MediaElement_MediaOpened(object sender, RoutedEventArgs e)
{
if (mediaElement.NaturalDuration.HasTimeSpan)
{
var duration = mediaElement.NaturalDuration.TimeSpan;
progressSlider.Maximum = duration.TotalSeconds;
totalTimeText.Text = FormatTime(duration);
statusText.Visibility = Visibility.Collapsed;
// 启用控制按钮
playPauseButton.IsEnabled = true;
backwardButton.IsEnabled = true;
forwardButton.IsEnabled = true;
progressSlider.IsEnabled = true;
setStartTimeButton.IsEnabled = true;
setEndTimeButton.IsEnabled = true;
// 重置剪辑时间
startTime = TimeSpan.Zero;
endTime = duration;
startTimeText.Text = "开始: " + FormatTime(startTime);
endTimeText.Text = "结束: " + FormatTime(endTime);
// 开始更新进度(仅在播放时更新)
if (isPlaying)
timer.Start();
}
}
// 设置开始时间按钮点击事件
private void SetStartTimeButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null && mediaElement.NaturalDuration.HasTimeSpan)
{
startTime = mediaElement.Position;
if (startTime >= endTime)
{
endTime = startTime.Add(TimeSpan.FromSeconds(1));
if (endTime > mediaElement.NaturalDuration.TimeSpan)
endTime = mediaElement.NaturalDuration.TimeSpan;
endTimeText.Text = "结束: " + FormatTime(endTime);
}
startTimeText.Text = "开始: " + FormatTime(startTime);
exportButton.IsEnabled = true;
exportStatusText.Text = "";
}
}
// 设置结束时间按钮点击事件
private void SetEndTimeButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null && mediaElement.NaturalDuration.HasTimeSpan)
{
endTime = mediaElement.Position;
if (endTime <= startTime)
{
startTime = endTime.Subtract(TimeSpan.FromSeconds(1));
if (startTime < TimeSpan.Zero)
startTime = TimeSpan.Zero;
startTimeText.Text = "开始: " + FormatTime(startTime);
}
endTimeText.Text = "结束: " + FormatTime(endTime);
exportButton.IsEnabled = true;
exportStatusText.Text = "";
}
}
// 导出按钮点击事件
private void ExportButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null && mediaElement.NaturalDuration.HasTimeSpan)
{
// 检查开始和结束时间是否有效
if (startTime >= endTime)
{
MessageBox.Show("开始时间必须小于结束时间", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
// 显示保存文件对话框
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "视频文件|*.mp4;*.avi;*.mkv;*.wmv|所有文件|*.*",
Title = "保存剪辑后的视频",
DefaultExt = "mp4"
};
if (saveFileDialog.ShowDialog() == true)
{
string outputFilePath = saveFileDialog.FileName;
ExportVideoClip(outputFilePath);
}
}
}
// ✅ 集成导出进度到 ProgressBar(需配合前台 XAML 修改)
// ✅ 修改点:将进度值通过 Dispatcher.Invoke 更新至 UI 控件
private void ExportVideoClip(string outputFilePath)
{
try
{
PausePlayback();
string inputFilePath = string.Empty;
TimeSpan duration = TimeSpan.Zero;
Dispatcher.Invoke(() =>
{
exportStatusText.Text = "正在导出视频片段 (0%)...";
exportButton.IsEnabled = false;
inputFilePath = mediaElement.Source?.LocalPath ?? string.Empty;
duration = endTime - startTime;
// 初始化进度条(XAML 中需要定义 exportProgressBar)
if (exportProgressBar != null)
{
exportProgressBar.Minimum = 0;
exportProgressBar.Maximum = 100;
exportProgressBar.Value = 0;
exportProgressBar.Visibility = Visibility.Visible;
}
});
if (string.IsNullOrWhiteSpace(inputFilePath) || !File.Exists(inputFilePath))
{
Dispatcher.Invoke(() =>
{
exportStatusText.Text = "导出失败: 无效的输入文件路径";
exportButton.IsEnabled = true;
});
return;
}
if (duration.TotalMilliseconds <= 0)
{
Dispatcher.Invoke(() =>
{
exportStatusText.Text = "导出失败: 选择的时间区间无效";
exportButton.IsEnabled = true;
});
return;
}
System.Threading.Thread exportThread = new System.Threading.Thread(() =>
{
try
{
string arguments = $"-ss {FormatTimeForFFmpeg(startTime)} -i \"{inputFilePath}\" -t {FormatTimeForFFmpeg(duration)} -y -movflags +faststart -preset ultrafast \"{outputFilePath}\"";
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = ffmpegPath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using (Process process = new Process { StartInfo = startInfo })
{
process.Start();
while (!process.StandardError.EndOfStream)
{
string line = process.StandardError.ReadLine();
if (line != null && line.Contains("time="))
{
var match = System.Text.RegularExpressions.Regex.Match(line, @"time=(\d{2}):(\d{2}):(\d{2})\.(\d+)");
if (match.Success)
{
var t = new TimeSpan(
int.Parse(match.Groups[1].Value),
int.Parse(match.Groups[2].Value),
int.Parse(match.Groups[3].Value)) +
TimeSpan.FromMilliseconds(int.Parse(match.Groups[4].Value.PadRight(3, '0')));
double progress = Math.Min(100.0, t.TotalMilliseconds / duration.TotalMilliseconds * 100);
Dispatcher.Invoke(() =>
{
exportStatusText.Text = $"正在导出视频片段 ({progress:F1}%)...";
if (exportProgressBar != null)
exportProgressBar.Value = progress;
});
}
}
}
process.WaitForExit();
Dispatcher.Invoke(() =>
{
exportProgressBar.Visibility = Visibility.Collapsed;
if (process.ExitCode == 0 && File.Exists(outputFilePath) && new FileInfo(outputFilePath).Length > 0)
{
exportStatusText.Text = "导出成功!";
MessageBox.Show($"视频片段已成功导出到:\n{outputFilePath}",
"导出完成", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
exportStatusText.Text = "导出失败";
MessageBox.Show("导出失败,请检查FFmpeg输出", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
exportButton.IsEnabled = true;
});
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
exportProgressBar.Visibility = Visibility.Collapsed;
exportStatusText.Text = "导出失败";
exportButton.IsEnabled = true;
MessageBox.Show($"导出视频时出错:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
});
}
});
exportThread.IsBackground = true;
exportThread.Start();
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
exportStatusText.Text = "导出失败";
exportButton.IsEnabled = true;
MessageBox.Show($"导出视频时出错:\n{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
});
}
}
// 格式化时间为FFmpeg可用的格式 (HH:MM:SS.mmm)
private string FormatTimeForFFmpeg(TimeSpan time)
{
return time.ToString(@"hh\:mm\:ss\.fff");
}
// 媒体播放结束事件
private void MediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
PausePlayback();
mediaElement.Position = TimeSpan.Zero;
UpdateTimeDisplay();
}
// 媒体播放失败事件
private void MediaElement_MediaFailed(object sender, ExceptionRoutedEventArgs e)
{
MessageBox.Show($"视频播放失败: {e.ErrorException.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
statusText.Text = "视频加载失败";
statusText.Visibility = Visibility.Visible;
PausePlayback();
}
// 窗口拖放事件
private void Window_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length > 0)
{
OpenVideoFile(files[0]);
}
}
}
// 窗口拖入事件
private void Window_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length == 1 && IsVideoFile(files[0]))
{
e.Effects = DragDropEffects.Copy;
}
else
{
e.Effects = DragDropEffects.None;
}
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
// 打开视频文件
private void OpenVideoFile(string filePath)
{
try
{
if (File.Exists(filePath) && IsVideoFile(filePath))
{
// 停止当前播放
PausePlayback();
// 加载新视频
mediaElement.Source = new Uri(filePath);
mediaElement.LoadedBehavior = MediaState.Manual;
StartPlayback();
statusText.Text = "加载中...";
statusText.Visibility = Visibility.Visible;
}
else
{
MessageBox.Show("请选择有效的视频文件", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"打开视频时出错: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
// 检查文件是否为视频文件
private bool IsVideoFile(string filePath)
{
string extension = System.IO.Path.GetExtension(filePath).ToLower();
string[] videoExtensions = { ".mp4", ".avi", ".mkv", ".wmv", ".mov", ".flv", ".webm" };
return Array.IndexOf(videoExtensions, extension) >= 0;
}
// 播放/暂停按钮点击事件
private void PlayPauseButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null)
{
if (isPlaying)
PausePlayback();
else
StartPlayback();
}
}
// 开始播放
private void StartPlayback()
{
mediaElement.Play();
playPauseButton.Content = "❚❚";
isPlaying = true;
timer.Start();
}
// 暂停播放
private void PausePlayback()
{
mediaElement.Pause();
playPauseButton.Content = "▶";
isPlaying = false;
timer.Stop();
}
// 后退一帧按钮点击事件
private void BackwardButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null)
{
bool wasPlaying = isPlaying;
if (wasPlaying) PausePlayback();
var newPosition = mediaElement.Position - frameStep;
if (newPosition < TimeSpan.Zero)
newPosition = TimeSpan.Zero;
mediaElement.Position = newPosition;
UpdateTimeDisplay();
if (wasPlaying) StartPlayback();
}
}
// 前进一帧按钮点击事件
private void ForwardButton_Click(object sender, RoutedEventArgs e)
{
if (mediaElement.Source != null && mediaElement.NaturalDuration.HasTimeSpan)
{
bool wasPlaying = isPlaying;
if (wasPlaying) PausePlayback();
var newPosition = mediaElement.Position + frameStep;
if (newPosition > mediaElement.NaturalDuration.TimeSpan)
newPosition = mediaElement.NaturalDuration.TimeSpan;
mediaElement.Position = newPosition;
UpdateTimeDisplay();
if (wasPlaying) StartPlayback();
}
}
// 进度条值改变事件
private void ProgressSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (mediaElement.Source != null && progressSlider.IsEnabled)
{
if (Mouse.LeftButton == MouseButtonState.Pressed || isUserSeeking)
{
isUserSeeking = true;
mediaElement.Position = TimeSpan.FromSeconds(progressSlider.Value);
UpdateTimeDisplay();
}
}
}
// 鼠标释放事件,结束用户拖动
private void ProgressSlider_MouseUp(object sender, MouseButtonEventArgs e)
{
isUserSeeking = false;
}
// 更新时间显示
private void UpdateTimeDisplay()
{
currentTimeText.Text = FormatTime(mediaElement.Position);
}
// 格式化时间显示(精确到毫秒)
private string FormatTime(TimeSpan time)
{
if (time.TotalHours >= 1)
return time.ToString(@"hh\:mm\:ss\.fff");
else if (time.TotalMinutes >= 1)
return time.ToString(@"mm\:ss\.fff");
else
return time.ToString(@"ss\.fff");
}
}
}
视频裁剪工具,视频浏览,支持各类视频文件-*.mp4;*.avi;*.mkv;*.wmv播放,支持视频裁剪,完全免费使用资源-CSDN文库