diff --git a/SpineViewer/Dialogs/ExportDialog.Designer.cs b/SpineViewer/Dialogs/ExportDialog.Designer.cs
index 23f505c..7d233ce 100644
--- a/SpineViewer/Dialogs/ExportDialog.Designer.cs
+++ b/SpineViewer/Dialogs/ExportDialog.Designer.cs
@@ -47,7 +47,7 @@
panel1.Location = new Point(0, 0);
panel1.Name = "panel1";
panel1.Padding = new Padding(50, 15, 50, 10);
- panel1.Size = new Size(710, 698);
+ panel1.Size = new Size(793, 754);
panel1.TabIndex = 2;
//
// tableLayoutPanel1
@@ -65,7 +65,7 @@
tableLayoutPanel1.RowStyles.Add(new RowStyle());
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
- tableLayoutPanel1.Size = new Size(610, 673);
+ tableLayoutPanel1.Size = new Size(693, 729);
tableLayoutPanel1.TabIndex = 0;
//
// propertyGrid_ExportArgs
@@ -74,7 +74,7 @@
propertyGrid_ExportArgs.Location = new Point(3, 3);
propertyGrid_ExportArgs.Name = "propertyGrid_ExportArgs";
propertyGrid_ExportArgs.PropertySort = PropertySort.Categorized;
- propertyGrid_ExportArgs.Size = new Size(604, 594);
+ propertyGrid_ExportArgs.Size = new Size(687, 650);
propertyGrid_ExportArgs.TabIndex = 1;
propertyGrid_ExportArgs.ToolbarVisible = false;
//
@@ -88,18 +88,18 @@
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
tableLayoutPanel2.Dock = DockStyle.Bottom;
- tableLayoutPanel2.Location = new Point(3, 630);
+ tableLayoutPanel2.Location = new Point(3, 686);
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
tableLayoutPanel2.Name = "tableLayoutPanel2";
tableLayoutPanel2.RowCount = 1;
tableLayoutPanel2.RowStyles.Add(new RowStyle());
- tableLayoutPanel2.Size = new Size(604, 40);
+ tableLayoutPanel2.Size = new Size(687, 40);
tableLayoutPanel2.TabIndex = 10;
//
// button_Ok
//
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
- button_Ok.Location = new Point(160, 3);
+ button_Ok.Location = new Point(201, 3);
button_Ok.Margin = new Padding(3, 3, 30, 3);
button_Ok.Name = "button_Ok";
button_Ok.Size = new Size(112, 34);
@@ -111,7 +111,7 @@
// button_Cancel
//
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
- button_Cancel.Location = new Point(332, 3);
+ button_Cancel.Location = new Point(373, 3);
button_Cancel.Margin = new Padding(30, 3, 3, 3);
button_Cancel.Name = "button_Cancel";
button_Cancel.Size = new Size(112, 34);
@@ -126,9 +126,8 @@
AutoScaleDimensions = new SizeF(11F, 24F);
AutoScaleMode = AutoScaleMode.Font;
CancelButton = button_Cancel;
- ClientSize = new Size(710, 698);
+ ClientSize = new Size(793, 754);
Controls.Add(panel1);
- FormBorderStyle = FormBorderStyle.FixedDialog;
Icon = (Icon)resources.GetObject("$this.Icon");
MaximizeBox = false;
MinimizeBox = false;
diff --git a/SpineViewer/Dialogs/ExportDialog.cs b/SpineViewer/Dialogs/ExportDialog.cs
index 31b2d9b..25aae4a 100644
--- a/SpineViewer/Dialogs/ExportDialog.cs
+++ b/SpineViewer/Dialogs/ExportDialog.cs
@@ -11,7 +11,7 @@ using System.Reflection;
namespace SpineViewer.Dialogs
{
- public partial class ExportDialog: Form
+ public partial class ExportDialog : Form
{
private readonly ExporterWrapper wrapper;
@@ -64,7 +64,7 @@ namespace SpineViewer.Dialogs
private void button_Ok_Click(object sender, EventArgs e)
{
if (wrapper.Exporter.Validate() is string error)
- {
+ {
MessagePopup.Info(error, "参数错误");
return;
}
diff --git a/SpineViewer/Exporter/AvifExporter.cs b/SpineViewer/Exporter/AvifExporter.cs
new file mode 100644
index 0000000..d0795ec
--- /dev/null
+++ b/SpineViewer/Exporter/AvifExporter.cs
@@ -0,0 +1,55 @@
+using FFMpegCore;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpineViewer.Exporter
+{
+ ///
+ /// MP4 导出参数
+ ///
+ public class AvifExporter : FFmpegVideoExporter
+ {
+ public AvifExporter()
+ {
+ FPS = 12;
+ }
+
+ public override string Format => "avif";
+
+ public override string Suffix => ".avif";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "av1_nvenc";
+
+ ///
+ /// CRF
+ ///
+ public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
+ private int crf = 23;
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuv420p";
+
+ ///
+ /// 循环次数, 0 无限循环, 取值范围 [0, 65535]
+ ///
+ public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); }
+ private int loop = 0;
+
+ public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).WithCustomArgument($"-loop {Loop}");
+ }
+ }
+}
diff --git a/SpineViewer/Exporter/CustomExporter.cs b/SpineViewer/Exporter/CustomExporter.cs
index d270b84..408e0cf 100644
--- a/SpineViewer/Exporter/CustomExporter.cs
+++ b/SpineViewer/Exporter/CustomExporter.cs
@@ -12,6 +12,11 @@ namespace SpineViewer.Exporter
///
public class CustomExporter : FFmpegVideoExporter
{
+ public CustomExporter()
+ {
+ CustomArgument = "-c:v libx264 -crf 23 -pix_fmt yuv420p"; // 提供一个示例参数
+ }
+
public override string Format => CustomFormat;
public override string Suffix => CustomSuffix;
diff --git a/SpineViewer/Exporter/GifExporter.cs b/SpineViewer/Exporter/GifExporter.cs
index b33a291..22da00c 100644
--- a/SpineViewer/Exporter/GifExporter.cs
+++ b/SpineViewer/Exporter/GifExporter.cs
@@ -35,6 +35,12 @@ namespace SpineViewer.Exporter
public byte AlphaThreshold { get => alphaThreshold; set => alphaThreshold = value; }
private byte alphaThreshold = 128;
+ ///
+ /// 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]
+ ///
+ public int Loop { get => loop; set => loop = Math.Clamp(value, -1, 65535); }
+ private int loop = 0;
+
public override string FileNameNoteSuffix => $"{MaxColors}_{AlphaThreshold}";
public override void SetOutputOptions(FFMpegArgumentOptions options)
@@ -43,7 +49,7 @@ namespace SpineViewer.Exporter
var v = $"[0:v] split [s0][s1]";
var s0 = $"[s0] palettegen=reserve_transparent=1:max_colors={MaxColors} [p]";
var s1 = $"[s1][p] paletteuse=dither=bayer:alpha_threshold={AlphaThreshold}";
- var customArgs = $"-filter_complex \"{v};{s0};{s1}\"";
+ var customArgs = $"-filter_complex \"{v};{s0};{s1}\" -loop {Loop}";
options.WithCustomArgument(customArgs);
}
}
diff --git a/SpineViewer/Exporter/WebpExporter.cs b/SpineViewer/Exporter/WebpExporter.cs
new file mode 100644
index 0000000..21c0f32
--- /dev/null
+++ b/SpineViewer/Exporter/WebpExporter.cs
@@ -0,0 +1,60 @@
+using FFMpegCore;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpineViewer.Exporter
+{
+ ///
+ /// MP4 导出参数
+ ///
+ public class WebpExporter : FFmpegVideoExporter
+ {
+ public WebpExporter()
+ {
+ FPS = 12;
+ }
+
+ public override string Format => "webp";
+
+ public override string Suffix => ".webp";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "libwebp";
+
+ ///
+ /// 是否无损
+ ///
+ public bool Lossless { get; set; } = false;
+
+ ///
+ /// 质量
+ ///
+ public int Quality { get => quality; set => quality = Math.Clamp(value, 0, 100); }
+ private int quality = 75;
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuva420p";
+
+ ///
+ /// 循环次数, 0 无限循环, 取值范围 [0, 65535]
+ ///
+ public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); }
+ private int loop = 0;
+
+ public override string FileNameNoteSuffix => $"{Codec}_{Quality}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithVideoCodec(Codec).WithCustomArgument($"-lossless {(Lossless ? 1 : 0)} -quality {Quality} -loop {Loop}");
+ }
+ }
+}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs
new file mode 100644
index 0000000..4857e50
--- /dev/null
+++ b/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs
@@ -0,0 +1,44 @@
+using SpineViewer.Exporter;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpineViewer.PropertyGridWrappers.Exporter
+{
+ public class AvifExporterWrapper(AvifExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ {
+ [Browsable(false)]
+ public override AvifExporter Exporter => (AvifExporter)base.Exporter;
+
+ ///
+ /// 编码器
+ ///
+ [StringEnumConverter.StandardValues("av1_nvenc", "av1_amf", "libaom-av1", Customizable = true)]
+ [TypeConverter(typeof(StringEnumConverter))]
+ [Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器\n建议使用硬件加速, libaom-av1 速度非常非常非常慢")]
+ public string Codec { get => Exporter.Codec; set => Exporter.Codec = value; }
+
+ ///
+ /// CRF
+ ///
+ [Category("[3] 格式参数"), DisplayName("CRF"), Description("-crf, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
+ public int CRF { get => Exporter.CRF; set => Exporter.CRF = value; }
+
+ ///
+ /// 像素格式
+ ///
+ [StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", Customizable = true)]
+ [TypeConverter(typeof(StringEnumConverter))]
+ [Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
+ public string PixelFormat { get => Exporter.PixelFormat; set => Exporter.PixelFormat = value; }
+
+ ///
+ /// 循环次数
+ ///
+ [Category("[3] 格式参数"), DisplayName("循环次数"), Description("循环次数, 0 无限循环, 取值范围 [0, 65535]")]
+ public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
+ }
+}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs
index 261bcf5..fc77831 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs
@@ -13,16 +13,29 @@ namespace SpineViewer.PropertyGridWrappers.Exporter
[Browsable(false)]
public override CustomExporter Exporter => (CustomExporter)base.Exporter;
+ [Browsable(false)]
+ public override string Format => Exporter.Format;
+
+ [Browsable(false)]
+ public override string Suffix => Exporter.Suffix;
+
///
/// 文件格式
///
- [Category("[3] 自定义参数"), DisplayName("文件格式"), Description("文件格式")]
- public string CustomFormat { get; set; } = "mp4";
+ [Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
+ public string CustomFormat { get => Exporter.CustomFormat; set => Exporter.CustomFormat = value; }
///
/// 文件名后缀
///
- [Category("[3] 自定义参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
- public string CustomSuffix { get; set; } = ".mp4";
+ [Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
+ public string CustomSuffix { get => Exporter.CustomSuffix; set => Exporter.CustomSuffix = value; }
+
+ ///
+ /// 文件名后缀
+ ///
+ [Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("提供给 FFmpeg 的自定义参数")]
+ public override string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
+
}
}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs
index c9f5677..6e95ba7 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs
@@ -17,18 +17,18 @@ namespace SpineViewer.PropertyGridWrappers.Exporter
/// 文件格式
///
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
- public string Format => Exporter.Format;
+ public virtual string Format => Exporter.Format;
///
/// 文件名后缀
///
[Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
- public string Suffix => Exporter.Format;
+ public virtual string Suffix => Exporter.Suffix;
///
/// 文件名后缀
///
[Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("提供给 FFmpeg 的自定义参数, 除非很清楚自己在做什么, 否则请勿填写此参数")]
- public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
+ public virtual string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
}
}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs
index fc1ba69..d8d94b9 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs
@@ -24,5 +24,11 @@ namespace SpineViewer.PropertyGridWrappers.Exporter
///
[Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; }
+
+ ///
+ /// 透明度阈值
+ ///
+ [Category("[3] 格式参数"), DisplayName("循环次数"), Description("循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")]
+ public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
}
}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs
index 5470098..2c00492 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs
@@ -16,7 +16,7 @@ namespace SpineViewer.PropertyGridWrappers.Exporter
///
/// 编码器
///
- [StringEnumConverter.StandardValues("libx264", "libx265", "libvpx-vp9", Customizable = true)]
+ [StringEnumConverter.StandardValues("libx264", "libx265", "libvpx-vp9", "av1_nvenc", Customizable = true)]
[TypeConverter(typeof(StringEnumConverter))]
[Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
public string Codec { get => Exporter.Codec; set => Exporter.Codec = value; }
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs
new file mode 100644
index 0000000..0f97e56
--- /dev/null
+++ b/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs
@@ -0,0 +1,50 @@
+using SpineViewer.Exporter;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SpineViewer.PropertyGridWrappers.Exporter
+{
+ public class WebpExporterWrapper(WebpExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ {
+ [Browsable(false)]
+ public override WebpExporter Exporter => (WebpExporter)base.Exporter;
+
+ ///
+ /// 编码器
+ ///
+ [StringEnumConverter.StandardValues("libwebp", Customizable = true)]
+ [TypeConverter(typeof(StringEnumConverter))]
+ [Category("[3] 格式参数"), DisplayName("编码器"), Description("-c:v, 要使用的编码器")]
+ public string Codec { get => Exporter.Codec; set => Exporter.Codec = value; }
+
+ ///
+ /// 是否无损
+ ///
+ [Category("[3] 格式参数"), DisplayName("无损"), Description("-lossless, 0 表示有损, 1 表示无损")]
+ public bool Lossless { get => Exporter.Lossless; set => Exporter.Lossless = value; }
+
+ ///
+ /// CRF
+ ///
+ [Category("[3] 格式参数"), DisplayName("质量"), Description("-quality, 取值范围 0-100, 默认值 75")]
+ public int Quality { get => Exporter.Quality; set => Exporter.Quality = value; }
+
+ ///
+ /// 像素格式
+ ///
+ [StringEnumConverter.StandardValues("yuv420p", "yuva420p", Customizable = true)]
+ [TypeConverter(typeof(StringEnumConverter))]
+ [Category("[3] 格式参数"), DisplayName("像素格式"), Description("-pix_fmt, 要使用的像素格式")]
+ public string PixelFormat { get => Exporter.PixelFormat; set => Exporter.PixelFormat = value; }
+
+ ///
+ /// 透明度阈值
+ ///
+ [Category("[3] 格式参数"), DisplayName("循环次数"), Description("循环次数, 0 无限循环, 取值范围 [0, 65535]")]
+ public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
+ }
+}