安装AutoUpdater.NET：打开你的WPF项目，然后使用NuGet包管理器来安装AutoUpdater.NET。配置更新过程：a. 在你的WPF应用程序中找到主窗体的代码文件。b. 引入AutoUpdater.NET命名空间：using AutoUpdaterDotNET;c. 在主窗体的Loaded事件中配置AutoUpdater：以下是在主窗体的Loaded事件中启动自动更新的示例：/** * 配置 AutoUpdater.NET 检查更新参数 */ private async void MainWindow_Loaded(object sender, RoutedEventArgs e) { try { await Task.Delay(300); if (GlobalConfig.SoftwareVersionId <= 0) { return; } /** 设置图标，将 System.Drawing.Icon 对象转换为 System.Drawing.Bitmap 对象 */ AutoUpdater.UpdateFormSize = new System.Drawing.Size(800, 500); try { AutoUpdater.Icon = Properties.Resources.Icon.ToBitmap(); } catch { /** 资源缺失不致命，保持默认图标即可，不阻断更新检查 */ } /** 从配置文件读取更新URL */ string updateUrl = $"{GlobalConfig.ServerUrl}/api/client/update/xml/{GlobalConfig.SoftwareVersionId}"; AutoUpdater.Start(updateUrl); } catch { /** async void 顶层兜底：避免 AutoUpdater / 资源加载异常被 Dispatcher.UnhandledException 上抛终结进程 */ } }确保将URL替换为指向你的更新XML文件的真实URL。服务器配置 XML 文件：<?xml version="1.0" encoding="UTF-8"?> 1.1.0.0 https://update.codespool.com/VideoWatermark/AutoUpdaterTest.zip https://update.codespool.com/VideoWatermark/release.html false 服务器配置 changelog文件： 软件更新日志 软件更新日志 版本 1.0.0 新增功能：用户登录 新增功能：创建和编辑个人资料 新增功能：查看其他用户资料 修复了一些已知的 bug
1.保持原有视频的参数（如编码器、比特率等）不变在这个命令中：-c:v libx264 指定使用 H.264 视频编码器。-crf 18 设置常量速率因子（CRF），其中较低的值表示较高的质量。CRF 18-20 通常被认为是视觉上无损的高质量编码。-preset slow 提高编码效率和质量，但会花费更多的时间。ffmpeg -i \"{videoFile}\" -i \"{watermarkImage}\" -filter_complex \"overlay=x='mod(main_h/0.8*ceil(t),main_w)':y='mod(main_w/3*ceil(t/2),main_h)'\" -c:v libx264 -crf 18 -preset slow \"{outputVideo}\"2.string ffmpegArgs = $"-i \"{videoFile}\" -i \"{watermarkImage}\" -filter_complex \"overlay=x='mod(main_h/0.8*ceil(t),main_w)':y='mod(main_w/3*ceil(t/2),main_h)'\" -b:v 36729k -c:a copy \"{outputVideo}\"";3.string ffmpegArgs = $"-i \"{videoFile}\" -i \"{watermarkImage}\" -filter_complex \"overlay=x='mod(main_h/0.8*ceil(t),main_w)':y='mod(main_w/3*ceil(t/2),main_h)'\" -c:v libx264 -crf 18 -preset slow -c:a libfdk_aac -vbr 4 \"{outputVideo}\"";
问题：在使用cmd窗口执行以上命令时(cmd中参数前面要加 ffmpeg 注意文件位置)，可以成功处理，但在运行WPF测试的时候，发现只有一个大小为0kb的新文件生成，但迟迟不见处理。给人一种假死的现象。而当关掉调试的WPF程序时，过几秒钟，貌似ffmpeg.exe 又起作用了，文件处理成功了。这个不得其解.原因：process.WaitForExit(); 这句执行会造成程序一直处于等待状态。于是，抱着试试看的态度，注释了这一句。当然，程序不再等待外部进程完成执行，proces.Close(); 这一句也要注释一下,测试结果成功！ <Application.Resources> <ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries> </Application.Resources> <Window x:Class="VideoWatermark.MainWindow" xmlns:hc="https://handyorg.github.io/handycontrol" 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:VideoWatermark" Icon="ico.ico" mc:Ignorable="d" ResizeMode="CanMinimize" Title="VideoWatermark" Height="500" Width="815"> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition Height="50"/> <RowDefinition Height="50"/> <RowDefinition Height="300"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="800"/> </Grid.ColumnDefinitions> <TextBox x:Name="VideosPathTextBox" Style="{StaticResource TextBoxExtend}" hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="视频文件：" Width="600" VerticalAlignment="Center" IsEnabled="False"/> <TextBox x:Name="TxtWatermarkPath" Style="{StaticResource TextBoxExtend}" hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="图片水印：" Width="600" VerticalAlignment="Center" IsEnabled="False"/> <ListView.View> <GridViewColumn Width="80" Header="序号" DisplayMemberBinding="{Binding Index}"/> <GridViewColumn Width="500" Header="名称" DisplayMemberBinding="{Binding Name}"/> <GridViewColumn Width="80" Header="状态" DisplayMemberBinding="{Binding Remark}"/> </ListView.View> <TextBlock Text="没有数据" Height="200" HorizontalAlignment="Center" Foreground="#000000"/> </Window>using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.IO; using Microsoft.Win32; using VideoWatermark.Models; using System.Diagnostics; namespace VideoWatermark { /// /// MainWindow.xaml 的交互逻辑 /// public partial class MainWindow : Window { public ObservableCollection<VideoItem> DataList { get; set; } = new ObservableCollection<VideoItem>(); public MainWindow() { InitializeComponent(); DataList = new ObservableCollection<VideoItem>(); // 初始化DataList DataContext = this; } private void SelectVideoButton_Click(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog { Multiselect = true, Filter = "Video files (*.mp4;*.avi;*.wmv)|*.mp4;*.avi|All files (*.*)|*.*" }; if (openFileDialog.ShowDialog() == true) { // 获取已在DataList中的文件名 var existingFiles = DataList.Select(v => v.Name).ToList(); // 将选中的文件路径添加到文本框，同时避免重复 List<string> newFiles = new List<string>(); int index = DataList.Count + 1; // 计算新的起始序号 foreach (string fileName in openFileDialog.FileNames) { string justFileName = Path.GetFileName(fileName); if (!existingFiles.Contains(justFileName)) { newFiles.Add(fileName); // 只有当文件不在列表中时，才添加到DataList DataList.Add(new VideoItem { Index = index++, Name = justFileName, Remark = "待处理" }); } } // 更新文本框 if (newFiles.Any()) { string existingText = VideosPathTextBox.Text; if (!string.IsNullOrEmpty(existingText)) { VideosPathTextBox.Text += ";"; } VideosPathTextBox.Text += string.Join(";", newFiles); } UpdateNoDataVisibility(); // 更新ListView VideosListView.ItemsSource = null; VideosListView.ItemsSource = DataList; } } private void Button_WatermarkClick(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "图片文件|*.png;*.jpg;*.jpeg|所有文件|*.*"; if (openFileDialog.ShowDialog() == true) { TxtWatermarkPath.Text = openFileDialog.FileName; } } private void Button_ClearList_Click(object sender, RoutedEventArgs e) { DataList.Clear(); VideosPathTextBox.Text = string.Empty; VideosListView.ItemsSource = null; VideosListView.ItemsSource = DataList; UpdateNoDataVisibility(); HandyControl.Controls.MessageBox.Show(this, "列表已清空", "提示"); } private async void Button_AddWatermarkButton_Click(object sender, RoutedEventArgs e) { string watermarkImage = TxtWatermarkPath.Text; string[] videoFiles = VideosPathTextBox.Text.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // 检查是否导入了视频文件 if (videoFiles.Length == 0) { HandyControl.Controls.MessageBox.Show(this, "请先导入视频文件！", "缺少视频"); return; } // 检查是否导入了水印图片 if (string.IsNullOrWhiteSpace(watermarkImage) || !File.Exists(watermarkImage)) { HandyControl.Controls.MessageBox.Show(this, "请先导入水印图片！", "缺少水印"); return; } foreach (string videoFile in videoFiles) { // 检查视频文件是否存在 if (!File.Exists(videoFile)) { UpdateVideoItemStatus(Path.GetFileName(videoFile), "文件不存在"); continue; // 跳过不存在的文件 } UpdateVideoItemStatus(Path.GetFileName(videoFile), "正在处理"); await Task.Run(() => RunFFmpegProcess(videoFile, watermarkImage)); // 更新视频状态为 "已完成" UpdateVideoItemStatus(Path.GetFileName(videoFile), "已完成"); } HandyControl.Controls.MessageBox.Show(this, "所有添加水印操作完成！", "提示"); } private void RunFFmpegProcess(string videoFile, string watermarkImage) { string outputVideo = Path.Combine(Path.GetDirectoryName(videoFile), "watermarked_" + Path.GetFileName(videoFile)); // 在这里编写使用 FFmpeg 添加水印的代码 string ffmpegArgs = $"-i \"{videoFile}\" -i \"{watermarkImage}\" -filter_complex \"overlay=x='mod(main_h/0.8*ceil(t),main_w)':y='mod(main_w/3*ceil(t/2),main_h)'\" \"{outputVideo}\""; // 请确保你已经正确配置 FFmpeg.AutoGen，并且有 FFmpeg 的可执行文件 //string ffmpegPath = @"C:\Users\az102\ Visual Studio Source\repos\VideoWatermark\VideoWatermark\FFmpeg\ffmpeg.exe"; // 请根据实际路径配置 FFmpeg 可执行文件路径 string ffmpegPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FFmpeg", "ffmpeg.exe"); Process process = new Process(); process.StartInfo.FileName = ffmpegPath; process.StartInfo.Arguments = $"-i \"{videoFile}\" -i \"{watermarkImage}\" -filter_complex \"overlay=x='mod(main_h/0.8*ceil(t),main_w)':y='mod(main_w/3*ceil(t/2),main_h)'\" \"{outputVideo}\""; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; process.Start(); process.WaitForExit(); process.Close(); } private void UpdateVideoItemStatus(string fileName, string status) { var videoItem = DataList.FirstOrDefault(v => v.Name == fileName); if (videoItem != null) { Application.Current.Dispatcher.Invoke(() => { videoItem.Remark = status; }); } } private void UpdateNoDataVisibility() { if (DataList.Count > 0) { NoDataBorder.Visibility = Visibility.Collapsed; } else { NoDataBorder.Visibility = Visibility.Visible; } } } }using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel; namespace VideoWatermark.Models { public class VideoItem : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public int Index { get; set; } private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } } private string remark; public string Remark { get { return remark; } set { remark = value; OnPropertyChanged("Remark"); } } protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
1.Grid：是 WPF 中用于创建灵活布局的强大控件。它允许您通过行和列来安排子元素。基本语法: <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/> 主要属性:▪RowDefinitions 和 ColumnDefinitions: 定义网格的行和列。每个 RowDefinition 和 ColumnDefinition 可以设置其宽度和高度。▪Height 和 Width: 可以设置为固定值、Auto（自动调整大小以适应内容）或星号表示法（如 *），星号表示法用于分配剩余空间。▪Grid.Row 和 Grid.Column: 指定子元素位于哪一行和哪一列。行列编号从 0 开始。▪Grid.RowSpan 和 Grid.ColumnSpan: 指定子元素横跨的行数或列数。创建具有两行两列的网格，其中第一行高度自适应，第二行高度是第一行的两倍；两列宽度均分配： <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Text="Row 0, Column 0" Grid.Row="0" Grid.Column="0"/> <TextBlock Text="Row 0, Column 1" Grid.Row="0" Grid.Column="1"/> <TextBlock Text="Row 1, Column 0" Grid.Row="1" Grid.Column="0"/> <TextBlock Text="Row 1, Column 1" Grid.Row="1" Grid.Column="1"/> 2.StackPanel：是 WPF 中的一种布局控件，用于按顺序（水平或垂直）排列子元素。基本语法： 主要属性：▪Orientation: 决定子元素是水平(Horizontal)排列还是垂直(Vertical)排列。默认值是 Vertical。▪VerticalAlignment 和 HorizontalAlignment: 决定 StackPanel 如何在其父容器中对齐。可以设置为 Top、Bottom、Left、Right 或 Center。▪Margin: 设置 StackPanel 边缘与其周围元素之间的距离。▪Background: 设置 StackPanel 的背景色。垂直排列的 StackPanel： <TextBox Text="Enter Text Here"/> 水平排列的 StackPanel： <TextBox Text="Enter Text Here"/> 3.WrapPanel：与 StackPanel 类似，但当空间不足以容纳更多元素时，它会将元素包裹到下一行或列。4.DockPanel：允许子元素停靠到父容器的顶部、底部、左侧或右侧。5.Canvas：提供了一个绝对定位的空间，你可以在其中明确地设置子元素的精确位置和大小。6.UniformGrid：类似于 Grid，但所有单元格的大小都是一致的。7.TabControl：允许通过选项卡来组织内容，每个选项卡对应一个页面。8.Expander：提供了一个可以展开和折叠的区域，用于显示隐藏的内容。9.ScrollViewer：当内容超出可视区域时，提供滚动功能。10.Border：虽然主要用于装饰，但也可以用作布局控件，为其子元素提供边框。11、Viewbox：自动缩放其内容以适应分配给它的空间。12.StackPanel 和 DockPanel 的组合：这些控件经常结合使用，以实现更复杂的布局。
使用WPF和FFmpeg.AutoGen 6.1开发一个为视频添加动态图片水印的工具时，你可以创建一个简单的界面，并使用C#代码实现相应的逻辑。下面是一个简单的示例，涵盖了基本的界面和代码： 首先，确保你已经将FFmpeg.AutoGen 6.1添加到你的项目中。你可以使用NuGet包管理器安装它。 请注意，下述代码仅为基本示例，你可能需要根据实际需求和FFmpeg的使用方式进行调整。确保你的项目中包含了FFmpeg.AutoGen 6.1，并根据你的系统配置正确的FFmpeg可执行文件路径。 <TextBlock Text="视频文件路径:"/> <TextBox x:Name="txtVideoPath" Margin="0,0,0,10"/> <TextBlock Text="水印图片路径:"/> <TextBox x:Name="txtWatermarkPath" Margin="0,0,0,10"/> namespace WpfApp3 { /// /// MainWindow.xaml 的交互逻辑 /// public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void SelectVideoFile_Click(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "视频文件|*.mp4;*.avi;*.mkv|所有文件|*.*"; if (openFileDialog.ShowDialog() == true) { txtVideoPath.Text = openFileDialog.FileName; } } private void SelectWatermark_Click(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Filter = "图片文件|*.png;*.jpg;*.jpeg|所有文件|*.*"; if (openFileDialog.ShowDialog() == true) { txtWatermarkPath.Text = openFileDialog.FileName; } } private async void AddWatermark_Click(object sender, RoutedEventArgs e) { string videoPath = txtVideoPath.Text; string watermarkPath = txtWatermarkPath.Text; if (string.IsNullOrEmpty(videoPath) || string.IsNullOrEmpty(watermarkPath)) { MessageBox.Show("请选择视频文件和水印图片"); return; } await Task.Run(() => AddWatermark(videoPath, watermarkPath)); } private void AddWatermark(string videoPath, string watermarkPath) { // 在这里编写使用 FFmpeg 添加水印的代码 // 请确保你已经正确配置 FFmpeg.AutoGen，并且有 FFmpeg 的可执行文件 // 以下是一个简单的示例，你可能需要根据实际情况调整参数 string outputVideoPath = @"D:\SoftWare\FFmpeg\output.mp4"; string ffmpegPath = @"D:\SoftWare\FFmpeg\ffmpeg.exe"; // 请根据实际路径配置 FFmpeg 可执行文件路径 Process process = new Process(); process.StartInfo.FileName = ffmpegPath; process.StartInfo.Arguments = $"-i {videoPath} -i {watermarkPath} -filter_complex overlay=W-w-10:H-h-10 {outputVideoPath}"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; process.Start(); process.WaitForExit(); MessageBox.Show("水印添加完成"); } } }
/www/wwwroot/demo11.codesdemo.top/template/conch/html/vod/player.html 替换下面的代码 马上充值 确认购买