2024年1月

  1. 安装AutoUpdater.NET:打开你的WPF项目,然后使用NuGet包管理器来安装AutoUpdater.NET。
  2. 配置更新过程
    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。
  3. 服务器配置 XML 文件

    <?xml version="1.0" encoding="UTF-8"?>
    <item>
      <version>1.1.0.0</version>
      <url>https://update.codespool.com/VideoWatermark/AutoUpdaterTest.zip</url>
      <changelog>https://update.codespool.com/VideoWatermark/release.html</changelog>
      <mandatory>false</mandatory>
    </item>
  4. 服务器配置 changelog文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>软件更新日志</title>
     <style>
         body {
             font-family: Arial, sans-serif;
             line-height: 1.6;
             padding: 20px;
         }
         h1 {
             text-align: center;
         }
         h2 {
             margin-top: 30px;
         }
         ul {
             margin-left: 20px;
         }
     </style>
    </head>
    <body>
     <h1>软件更新日志</h1>
     
     <h2>版本 1.0.0</h2>
     <ul>
         <li>新增功能:用户登录</li>
         <li>新增功能:创建和编辑个人资料</li>
         <li>新增功能:查看其他用户资料</li>
         <li>修复了一些已知的 bug</li>
     </ul>
    </body>
    </html>

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 x:Class="VideoWatermark.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:VideoWatermark"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
<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>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="300"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="800"/>
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBox x:Name="VideosPathTextBox" Style="{StaticResource TextBoxExtend}" hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="视频文件:" Width="600" VerticalAlignment="Center" IsEnabled="False"/>
            <Button Style="{StaticResource ButtonPrimary}" Width="85" Content="选择文件" Click="SelectVideoButton_Click"/>
        </StackPanel>
        <StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBox x:Name="TxtWatermarkPath" Style="{StaticResource TextBoxExtend}" hc:InfoElement.TitlePlacement="Left" hc:InfoElement.Title="图片水印:" Width="600" VerticalAlignment="Center" IsEnabled="False"/>
            <Button  Style="{StaticResource ButtonPrimary}" Width="85"  Content="选择水印" Click="Button_WatermarkClick"/>
        </StackPanel>
        <StackPanel Grid.Row="2" Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <Button Style="{StaticResource ButtonPrimary}" Width="335"  Content="开始" Click="Button_AddWatermarkButton_Click" Margin="0,0,10,0"/>
            <Button Style="{StaticResource ButtonPrimary}" Width="335"  Content="清空" Click="Button_ClearList_Click"/>
        </StackPanel>

        <StackPanel Grid.Row="3" Grid.Column="0" Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Center">
            <ListView x:Name="VideosListView" ItemsSource="{Binding DataList}" >
                <ListView.View>
                    <GridView>
                        <GridViewColumn Width="80" Header="序号" DisplayMemberBinding="{Binding Index}"/>
                        <GridViewColumn Width="500" Header="名称" DisplayMemberBinding="{Binding Name}"/>
                        <GridViewColumn Width="80" Header="状态" DisplayMemberBinding="{Binding Remark}"/>
                    </GridView>
                </ListView.View>
            </ListView>
            <Border x:Name="NoDataBorder" Style="{StaticResource BorderRegion}" Visibility="Visible">
                <TextBlock Text="没有数据" Height="200" HorizontalAlignment="Center" Foreground="#000000"/>
            </Border>
        </StackPanel>
    </Grid>
</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
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    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>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="2*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <!-- 子元素 -->
    <Button Content="Button 1" Grid.Row="0" Grid.Column="0"/>
    <Button Content="Button 2" Grid.Row="0" Grid.Column="1"/>
    <TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
</Grid>

主要属性:
▪RowDefinitions 和 ColumnDefinitions: 定义网格的行和列。每个 RowDefinition 和 ColumnDefinition 可以设置其宽度和高度。
▪Height 和 Width: 可以设置为固定值、Auto(自动调整大小以适应内容)或星号表示法(如 *),星号表示法用于分配剩余空间。
▪Grid.Row 和 Grid.Column: 指定子元素位于哪一行和哪一列。行列编号从 0 开始。
▪Grid.RowSpan 和 Grid.ColumnSpan: 指定子元素横跨的行数或列数。
创建具有两行两列的网格,其中第一行高度自适应,第二行高度是第一行的两倍;两列宽度均分配:

<Grid>
    <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"/>
</Grid>

2.StackPanel:是 WPF 中的一种布局控件,用于按顺序(水平或垂直)排列子元素。
基本语法:

<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left">
    <!-- 子元素 -->
    <Button Content="Button 1"/>
    <Button Content="Button 2"/>
    <Button Content="Button 3"/>
</StackPanel>

主要属性:
▪Orientation: 决定子元素是水平(Horizontal)排列还是垂直(Vertical)排列。默认值是 Vertical。
▪VerticalAlignment 和 HorizontalAlignment: 决定 StackPanel 如何在其父容器中对齐。可以设置为 Top、Bottom、Left、Right 或 Center。
▪Margin: 设置 StackPanel 边缘与其周围元素之间的距离。
▪Background: 设置 StackPanel 的背景色。
垂直排列的 StackPanel:

<StackPanel Orientation="Vertical">
    <Button Content="Button 1"/>
    <Button Content="Button 2"/>
    <TextBox Text="Enter Text Here"/>
</StackPanel>

水平排列的 StackPanel:

<StackPanel Orientation="Horizontal">
    <Button Content="Button 1"/>
    <Button Content="Button 2"/>
    <TextBox Text="Enter Text Here"/>
</StackPanel>

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可执行文件路径。

<Window x:Class="WpfApp3.MainWindow"
        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:WpfApp3"
        mc:Ignorable="d"
        Title="视频水印工具" Height="350" Width="500">
    <Grid>
        <StackPanel Margin="10">
            <TextBlock Text="视频文件路径:"/>
            <TextBox x:Name="txtVideoPath" Margin="0,0,0,10"/>

            <TextBlock Text="水印图片路径:"/>
            <TextBox x:Name="txtWatermarkPath" Margin="0,0,0,10"/>

            <Button Content="选择视频文件" Click="SelectVideoFile_Click"/>
            <Button Content="选择水印图片" Click="SelectWatermark_Click"/>

            <Button Content="开始添加水印" Click="AddWatermark_Click" Margin="0,10,0,0"/>
        </StackPanel>
    </Grid>
</Window>
namespace WpfApp3
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    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 替换下面的代码

    <div class="btn">
        <a class="cz hl-text-conch" href="{:url('user/buy')}" target="_blank">马上充值</a>
        <a class="gm hl-bg-conch" href="javascript:;" onclick="MAC.User.BuyPopedom(this)" data-id="{$obj.vod_id}" data-sid="{$param.sid}" data-mid="{$maccms.mid}" data-nid="{$param.nid}" data-type="4">确认购买</a>
    </div>