diff --git a/SpineViewer/Controls/SpinePreviewer.cs b/SpineViewer/Controls/SpinePreviewer.cs
index cce1102..1567924 100644
--- a/SpineViewer/Controls/SpinePreviewer.cs
+++ b/SpineViewer/Controls/SpinePreviewer.cs
@@ -51,7 +51,7 @@ namespace SpineViewer.Controls
{
propertyGrid = value;
if (propertyGrid is not null)
- propertyGrid.SelectedObject = new PropertyGridWrappers.SpinePreviewerWrapper(this);
+ propertyGrid.SelectedObject = new SpinePreviewerProperty(this);
}
}
private PropertyGrid? propertyGrid;
@@ -635,4 +635,42 @@ namespace SpineViewer.Controls
//public void ClickForwardStepButton() => button_ForwardStep_Click(button_ForwardStep, EventArgs.Empty);
//public void ClickForwardFastButton() => button_ForwardFast_Click(button_ForwardFast, EventArgs.Empty);
}
+
+ ///
+ /// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类, 提供用户操作接口
+ ///
+ public class SpinePreviewerProperty(SpinePreviewer previewer)
+ {
+ [Browsable(false)]
+ public SpinePreviewer Previewer { get; } = previewer;
+
+ [TypeConverter(typeof(SizeConverter))]
+ [Category("[0] 导出"), DisplayName("分辨率")]
+ public Size Resolution { get => Previewer.Resolution; set => Previewer.Resolution = value; }
+
+ [TypeConverter(typeof(PointFConverter))]
+ [Category("[0] 导出"), DisplayName("画面中心点")]
+ public PointF Center { get => Previewer.Center; set => Previewer.Center = value; }
+
+ [Category("[0] 导出"), DisplayName("缩放")]
+ public float Zoom { get => Previewer.Zoom; set => Previewer.Zoom = value; }
+
+ [Category("[0] 导出"), DisplayName("旋转")]
+ public float Rotation { get => Previewer.Rotation; set => Previewer.Rotation = value; }
+
+ [Category("[0] 导出"), DisplayName("水平翻转")]
+ public bool FlipX { get => Previewer.FlipX; set => Previewer.FlipX = value; }
+
+ [Category("[0] 导出"), DisplayName("垂直翻转")]
+ public bool FlipY { get => Previewer.FlipY; set => Previewer.FlipY = value; }
+
+ [Category("[0] 导出"), DisplayName("仅渲染选中")]
+ public bool RenderSelectedOnly { get => Previewer.RenderSelectedOnly; set => Previewer.RenderSelectedOnly = value; }
+
+ [Category("[1] 预览"), DisplayName("显示坐标轴")]
+ public bool ShowAxis { get => Previewer.ShowAxis; set => Previewer.ShowAxis = value; }
+
+ [Category("[1] 预览"), DisplayName("最大帧率")]
+ public uint MaxFps { get => Previewer.MaxFps; set => Previewer.MaxFps = value; }
+ }
}
diff --git a/SpineViewer/Dialogs/ExportDialog.cs b/SpineViewer/Dialogs/ExportDialog.cs
index a5af9a4..17248b2 100644
--- a/SpineViewer/Dialogs/ExportDialog.cs
+++ b/SpineViewer/Dialogs/ExportDialog.cs
@@ -1,5 +1,4 @@
-using SpineViewer.PropertyGridWrappers.Exporter;
-using SpineViewer.Utils;
+using SpineViewer.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -8,14 +7,15 @@ using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+using SpineViewer.Spine.SpineExporter;
namespace SpineViewer.Dialogs
{
public partial class ExportDialog : Form
{
- private readonly ExporterWrapper wrapper;
+ private readonly ExporterProperty wrapper;
- public ExportDialog(ExporterWrapper wrapper)
+ public ExportDialog(ExporterProperty wrapper)
{
InitializeComponent();
this.wrapper = wrapper;
diff --git a/SpineViewer/Exporter/AvifExporter.cs b/SpineViewer/Exporter/AvifExporter.cs
deleted file mode 100644
index ea071e5..0000000
--- a/SpineViewer/Exporter/AvifExporter.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-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 = 24;
- }
-
- 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).ForcePixelFormat(PixelFormat).WithConstantRateFactor(CRF).WithCustomArgument($"-loop {Loop}");
- }
- }
-}
diff --git a/SpineViewer/Exporter/CustomExporter.cs b/SpineViewer/Exporter/CustomExporter.cs
deleted file mode 100644
index 408e0cf..0000000
--- a/SpineViewer/Exporter/CustomExporter.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.Exporter
-{
- ///
- /// FFmpeg 自定义视频导出参数
- ///
- 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;
-
- public override string FileNameNoteSuffix => string.Empty;
-
- ///
- /// 文件格式
- ///
- public string CustomFormat { get; set; } = "mp4";
-
- ///
- /// 文件名后缀
- ///
- public string CustomSuffix { get; set; } = ".mp4";
- }
-}
diff --git a/SpineViewer/Exporter/MkvExporter.cs b/SpineViewer/Exporter/MkvExporter.cs
deleted file mode 100644
index 476d4e2..0000000
--- a/SpineViewer/Exporter/MkvExporter.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using FFMpegCore;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.Exporter
-{
- ///
- /// MKV 导出参数
- ///
- public class MkvExporter : FFmpegVideoExporter
- {
- public MkvExporter()
- {
- BackgroundColor = new(0, 255, 0);
- }
-
- public override string Format => "matroska";
-
- public override string Suffix => ".mkv";
-
- ///
- /// 编码器
- ///
- public string Codec { get; set; } = "libx265";
-
- ///
- /// CRF
- ///
- public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
- private int crf = 23;
-
- ///
- /// 像素格式
- ///
- public string PixelFormat { get; set; } = "yuv444p";
-
- public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
-
- public override void SetOutputOptions(FFMpegArgumentOptions options)
- {
- base.SetOutputOptions(options);
- options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
- }
- }
-}
diff --git a/SpineViewer/Exporter/MovExporter.cs b/SpineViewer/Exporter/MovExporter.cs
deleted file mode 100644
index 958f889..0000000
--- a/SpineViewer/Exporter/MovExporter.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using FFMpegCore;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.Exporter
-{
- ///
- /// MOV 导出参数
- ///
- public class MovExporter : FFmpegVideoExporter
- {
- public MovExporter()
- {
- BackgroundColor = new(0, 255, 0);
- }
-
- public override string Format => "mov";
-
- public override string Suffix => ".mov";
-
- ///
- /// 编码器
- ///
- public string Codec { get; set; } = "prores_ks";
-
- ///
- /// 预设
- ///
- public string Profile { get; set; } = "auto";
-
- ///
- /// 像素格式
- ///
- public string PixelFormat { get; set; } = "yuva444p10le";
-
- public override string FileNameNoteSuffix => $"{Codec}_{Profile}_{PixelFormat}";
-
- public override void SetOutputOptions(FFMpegArgumentOptions options)
- {
- base.SetOutputOptions(options);
- options.WithFastStart().WithVideoCodec(Codec).WithCustomArgument($"-profile {Profile}").ForcePixelFormat(PixelFormat);
- }
- }
-}
diff --git a/SpineViewer/Exporter/Mp4Exporter.cs b/SpineViewer/Exporter/Mp4Exporter.cs
deleted file mode 100644
index e554ddc..0000000
--- a/SpineViewer/Exporter/Mp4Exporter.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-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 Mp4Exporter : FFmpegVideoExporter
- {
- public Mp4Exporter()
- {
- BackgroundColor = new(0, 255, 0);
- }
-
- public override string Format => "mp4";
-
- public override string Suffix => ".mp4";
-
- ///
- /// 编码器
- ///
- public string Codec { get; set; } = "libx264";
-
- ///
- /// CRF
- ///
- public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
- private int crf = 23;
-
- ///
- /// 像素格式
- ///
- public string PixelFormat { get; set; } = "yuv444p";
-
- public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
-
- public override void SetOutputOptions(FFMpegArgumentOptions options)
- {
- base.SetOutputOptions(options);
- options.WithFastStart().WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
- }
- }
-}
diff --git a/SpineViewer/Exporter/WebmExporter.cs b/SpineViewer/Exporter/WebmExporter.cs
deleted file mode 100644
index b8097fd..0000000
--- a/SpineViewer/Exporter/WebmExporter.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using FFMpegCore;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.Exporter
-{
- ///
- /// WebM 导出参数
- ///
- public class WebmExporter : FFmpegVideoExporter
- {
- public WebmExporter()
- {
- // 默认用透明黑背景
- BackgroundColor = new(0, 0, 0, 0);
- }
-
- public override string Format => "webm";
-
- public override string Suffix => ".webm";
-
- ///
- /// 编码器
- ///
- public string Codec { get; set; } = "libvpx-vp9";
-
- ///
- /// CRF
- ///
- public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
- private int crf = 23;
-
- ///
- /// 像素格式
- ///
- public string PixelFormat { get; set; } = "yuva420p";
-
- public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
-
- public override void SetOutputOptions(FFMpegArgumentOptions options)
- {
- base.SetOutputOptions(options);
- options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
- }
- }
-}
diff --git a/SpineViewer/Exporter/WebpExporter.cs b/SpineViewer/Exporter/WebpExporter.cs
deleted file mode 100644
index 4fb93ac..0000000
--- a/SpineViewer/Exporter/WebpExporter.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-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 = 24;
- }
-
- public override string Format => "webp";
-
- public override string Suffix => ".webp";
-
- ///
- /// 编码器
- ///
- public string Codec { get; set; } = "libwebp_anim";
-
- ///
- /// 是否无损
- ///
- 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).ForcePixelFormat(PixelFormat).WithCustomArgument($"-lossless {(Lossless ? 1 : 0)} -quality {Quality} -loop {Loop}");
- }
- }
-}
diff --git a/SpineViewer/Forms/SpineViewerForm.cs b/SpineViewer/Forms/SpineViewerForm.cs
index 1ecefe9..9cec6e7 100644
--- a/SpineViewer/Forms/SpineViewerForm.cs
+++ b/SpineViewer/Forms/SpineViewerForm.cs
@@ -2,11 +2,9 @@
using SpineViewer.Spine;
using System.ComponentModel;
using System.Diagnostics;
-using SpineViewer.Exporter;
-using System.Reflection.Metadata;
-using SpineViewer.PropertyGridWrappers.Exporter;
using SpineViewer.Natives;
using SpineViewer.Utils;
+using SpineViewer.Spine.SpineExporter;
namespace SpineViewer
{
@@ -14,7 +12,7 @@ namespace SpineViewer
{
private readonly Logger logger = LogManager.GetCurrentClassLogger();
- private readonly Dictionary exporterCache = [];
+ private readonly Dictionary exporterCache = [];
public SpineViewerForm()
{
@@ -98,7 +96,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new FrameExporterWrapper((FrameExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new FrameExporterProperty((FrameExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -118,7 +116,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new FrameSequenceExporterWrapper((FrameSequenceExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new FrameSequenceExporterProperty((FrameSequenceExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -138,7 +136,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new GifExporterWrapper((GifExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new GifExporterProperty((GifExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -158,7 +156,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new WebpExporterWrapper((WebpExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new WebpExporterProperty((WebpExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -178,7 +176,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new AvifExporterWrapper((AvifExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new AvifExporterProperty((AvifExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -198,7 +196,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new Mp4ExporterWrapper((Mp4Exporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new Mp4ExporterProperty((Mp4Exporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -218,7 +216,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new WebmExporterWrapper((WebmExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new WebmExporterProperty((WebmExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -238,7 +236,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new MkvExporterWrapper((MkvExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new MkvExporterProperty((MkvExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -258,7 +256,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new MovExporterWrapper((MovExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new MovExporterProperty((MovExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -278,7 +276,7 @@ namespace SpineViewer
exporter.View = spinePreviewer.GetView();
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
- var exportDialog = new Dialogs.ExportDialog(new CustomExporterWrapper((CustomExporter)exporter));
+ var exportDialog = new Dialogs.ExportDialog(new CustomExporterProperty((CustomExporter)exporter));
if (exportDialog.ShowDialog() != DialogResult.OK)
return;
@@ -337,7 +335,7 @@ namespace SpineViewer
private void Export_Work(object? sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
- var exporter = (Exporter.Exporter)e.Argument;
+ var exporter = (Exporter)e.Argument;
Invoke(() => TaskbarManager.SetProgressState(Handle, TBPFLAG.TBPF_INDETERMINATE));
spinePreviewer.StopRender();
lock (spineListView.Spines) { exporter.Export(spineListView.Spines.Where(sp => !sp.IsHidden).ToArray(), (BackgroundWorker)sender); }
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs
deleted file mode 100644
index a8c1aa8..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Drawing.Design;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.PropertyGridWrappers.Exporter
-{
- public class ExporterWrapper(SpineViewer.Exporter.Exporter exporter)
- {
- [Browsable(false)]
- public virtual SpineViewer.Exporter.Exporter Exporter { get; } = exporter;
-
- ///
- /// 输出文件夹
- ///
- [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
- [Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")]
- public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; }
-
- ///
- /// 导出单个
- ///
- [Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")]
- public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; }
-
- ///
- /// 画面分辨率
- ///
- [TypeConverter(typeof(SizeConverter))]
- [Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")]
- public Size Resolution { get => Exporter.Resolution; }
-
- ///
- /// 渲染视窗
- ///
- [Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
- public SFML.Graphics.View View { get => Exporter.View; }
-
- ///
- /// 是否仅渲染选中
- ///
- [Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")]
- public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; }
-
- ///
- /// 背景颜色
- ///
- [Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
- [TypeConverter(typeof(SFMLColorConverter))]
- [Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
- public SFML.Graphics.Color BackgroundColor { get => Exporter.BackgroundColor; set => Exporter.BackgroundColor = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs
deleted file mode 100644
index b8e268a..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-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 FFmpegVideoExporterWrapper(FFmpegVideoExporter exporter) : VideoExporterWrapper(exporter)
- {
- [Browsable(false)]
- public override FFmpegVideoExporter Exporter => (FFmpegVideoExporter)base.Exporter;
-
- ///
- /// 文件格式
- ///
- [Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
- public virtual string Format => Exporter.Format;
-
- ///
- /// 文件名后缀
- ///
- [Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
- public virtual string Suffix => Exporter.Suffix;
-
- ///
- /// 文件名后缀
- ///
- [Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")]
- public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs
deleted file mode 100644
index ddf522d..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using SpineViewer.Exporter;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Drawing.Imaging;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.PropertyGridWrappers.Exporter
-{
- public class FrameExporterWrapper(FrameExporter exporter) : ExporterWrapper(exporter)
- {
- [Browsable(false)]
- public override FrameExporter Exporter => (FrameExporter)base.Exporter;
-
- ///
- /// 单帧画面格式
- ///
- [TypeConverter(typeof(ImageFormatConverter))]
- [Category("[1] 单帧画面"), DisplayName("图像格式")]
- public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; }
-
- ///
- /// 文件名后缀
- ///
- [Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
- public string Suffix { get => Exporter.ImageFormat.GetSuffix(); }
-
- ///
- /// DPI
- ///
- [TypeConverter(typeof(SizeFConverter))]
- [Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
- public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs
deleted file mode 100644
index 5e05570..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-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 FrameSequenceExporterWrapper(VideoExporter exporter) : VideoExporterWrapper(exporter)
- {
- [Browsable(false)]
- public override FrameSequenceExporter Exporter => (FrameSequenceExporter)base.Exporter;
-
- ///
- /// 文件名后缀
- ///
- [TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")]
- [Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
- public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs
deleted file mode 100644
index fa91755..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-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
-{
- class GifExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
- {
- [Browsable(false)]
- public override GifExporter Exporter => (GifExporter)base.Exporter;
-
- ///
- /// 调色板最大颜色数量
- ///
- [Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
- public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; }
-
- ///
- /// 透明度阈值
- ///
- [Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
- public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; }
-
- ///
- /// 透明度阈值
- ///
- [Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")]
- public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs
deleted file mode 100644
index 3becc3e..0000000
--- a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-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 VideoExporterWrapper(VideoExporter exporter) : ExporterWrapper(exporter)
- {
- [Browsable(false)]
- public override VideoExporter Exporter => (VideoExporter)base.Exporter;
-
- ///
- /// 导出时长
- ///
- [Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")]
- public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; }
-
- ///
- /// 帧率
- ///
- [Category("[1] 视频参数"), DisplayName("帧率"), Description("每秒画面数")]
- public float FPS { get => Exporter.FPS; set => Exporter.FPS = value; }
-
- ///
- /// 保留最后一帧
- ///
- [Category("[1] 视频参数"), DisplayName("保留最后一帧"), Description("当设置保留最后一帧时, 动图会更为连贯, 但是帧数可能比预期帧数多 1")]
- public bool KeepLast { get => Exporter.KeepLast; set => Exporter.KeepLast = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs
index 2264c59..19bc04d 100644
--- a/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs
@@ -1,4 +1,5 @@
using SpineViewer.Spine;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs
index 8741051..d10ef20 100644
--- a/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs
@@ -1,4 +1,5 @@
using SpineViewer.Spine;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs
index 2932db5..c0da519 100644
--- a/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs
@@ -1,4 +1,5 @@
using SpineViewer.Spine;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs
index febf1f6..8930984 100644
--- a/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs
+++ b/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpineViewer.Spine;
+using SpineViewer.Utils;
namespace SpineViewer.PropertyGridWrappers.Spine
{
diff --git a/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs b/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs
deleted file mode 100644
index a833123..0000000
--- a/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using SpineViewer.Controls;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SpineViewer.PropertyGridWrappers
-{
- ///
- /// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类
- ///
- public class SpinePreviewerWrapper(SpinePreviewer previewer)
- {
- [Browsable(false)]
- public SpinePreviewer Previewer { get; } = previewer;
-
- [TypeConverter(typeof(SizeConverter))]
- [Category("[0] 导出"), DisplayName("分辨率")]
- public Size Resolution { get => Previewer.Resolution; set => Previewer.Resolution = value; }
-
- [TypeConverter(typeof(PointFConverter))]
- [Category("[0] 导出"), DisplayName("画面中心点")]
- public PointF Center { get => Previewer.Center; set => Previewer.Center = value; }
-
- [Category("[0] 导出"), DisplayName("缩放")]
- public float Zoom { get => Previewer.Zoom; set => Previewer.Zoom = value; }
-
- [Category("[0] 导出"), DisplayName("旋转")]
- public float Rotation { get => Previewer.Rotation; set => Previewer.Rotation = value; }
-
- [Category("[0] 导出"), DisplayName("水平翻转")]
- public bool FlipX { get => Previewer.FlipX; set => Previewer.FlipX = value; }
-
- [Category("[0] 导出"), DisplayName("垂直翻转")]
- public bool FlipY { get => Previewer.FlipY; set => Previewer.FlipY = value; }
-
- [Category("[0] 导出"), DisplayName("仅渲染选中")]
- public bool RenderSelectedOnly { get => Previewer.RenderSelectedOnly; set => Previewer.RenderSelectedOnly = value; }
-
- [Category("[1] 预览"), DisplayName("显示坐标轴")]
- public bool ShowAxis { get => Previewer.ShowAxis; set => Previewer.ShowAxis = value; }
-
- [Category("[1] 预览"), DisplayName("最大帧率")]
- public uint MaxFps { get => Previewer.MaxFps; set => Previewer.MaxFps = value; }
- }
-}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/AvifExporter.cs
similarity index 54%
rename from SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/AvifExporter.cs
index 4f5722f..a06ed17 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/AvifExporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,54 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class AvifExporterWrapper(AvifExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// MP4 导出参数
+ ///
+ public class AvifExporter : FFmpegVideoExporter
+ {
+ public AvifExporter()
+ {
+ FPS = 24;
+ }
+
+ 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).ForcePixelFormat(PixelFormat).WithConstantRateFactor(CRF).WithCustomArgument($"-loop {Loop}");
+ }
+ }
+
+ public class AvifExporterProperty(AvifExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override AvifExporter Exporter => (AvifExporter)base.Exporter;
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/CustomExporter.cs
similarity index 52%
rename from SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/CustomExporter.cs
index b820186..f7c8e5c 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/CustomExporter.cs
@@ -1,14 +1,40 @@
-using SpineViewer.Exporter;
-using System;
+using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class CustomExporterWrapper(CustomExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// FFmpeg 自定义视频导出参数
+ ///
+ 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;
+
+ public override string FileNameNoteSuffix => string.Empty;
+
+ ///
+ /// 文件格式
+ ///
+ public string CustomFormat { get; set; } = "mp4";
+
+ ///
+ /// 文件名后缀
+ ///
+ public string CustomSuffix { get; set; } = ".mp4";
+ }
+
+ public class CustomExporterProperty(CustomExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override CustomExporter Exporter => (CustomExporter)base.Exporter;
diff --git a/SpineViewer/Exporter/ExportHelper.cs b/SpineViewer/Spine/SpineExporter/ExportHelper.cs
similarity index 98%
rename from SpineViewer/Exporter/ExportHelper.cs
rename to SpineViewer/Spine/SpineExporter/ExportHelper.cs
index c864479..f1310f6 100644
--- a/SpineViewer/Exporter/ExportHelper.cs
+++ b/SpineViewer/Spine/SpineExporter/ExportHelper.cs
@@ -6,7 +6,7 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Text;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// SFML.Graphics.Image 帧对象包装类, 将接管给定的 image 对象生命周期
diff --git a/SpineViewer/Exporter/Exporter.cs b/SpineViewer/Spine/SpineExporter/Exporter.cs
similarity index 70%
rename from SpineViewer/Exporter/Exporter.cs
rename to SpineViewer/Spine/SpineExporter/Exporter.cs
index 35c68ab..b17b178 100644
--- a/SpineViewer/Exporter/Exporter.cs
+++ b/SpineViewer/Spine/SpineExporter/Exporter.cs
@@ -1,6 +1,5 @@
using NLog;
using SpineViewer.Extensions;
-using SpineViewer.PropertyGridWrappers;
using SpineViewer.Utils;
using System;
using System.Collections.Generic;
@@ -11,7 +10,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// 导出器基类
@@ -96,12 +95,12 @@ namespace SpineViewer.Exporter
///
/// 获取单个模型的单帧画面
///
- protected SFMLImageVideoFrame GetFrame(Spine.SpineObject spine) => GetFrame([spine]);
+ protected SFMLImageVideoFrame GetFrame(SpineObject spine) => GetFrame([spine]);
///
/// 获取模型列表的单帧画面
///
- protected SFMLImageVideoFrame GetFrame(Spine.SpineObject[] spinesToRender)
+ protected SFMLImageVideoFrame GetFrame(SpineObject[] spinesToRender)
{
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
using var texPma = GetRenderTexture();
@@ -149,12 +148,12 @@ namespace SpineViewer.Exporter
///
/// 每个模型在同一个画面进行导出
///
- protected abstract void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null);
+ protected abstract void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null);
///
/// 每个模型独立导出
///
- protected abstract void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null);
+ protected abstract void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null);
///
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
@@ -178,7 +177,7 @@ namespace SpineViewer.Exporter
/// 要进行导出的 Spine 列表
/// 用来执行该函数的 worker
///
- public virtual void Export(Spine.SpineObject[] spines, BackgroundWorker? worker = null)
+ public virtual void Export(SpineObject[] spines, BackgroundWorker? worker = null)
{
if (Validate() is string err)
throw new ArgumentException(err);
@@ -191,4 +190,53 @@ namespace SpineViewer.Exporter
logger.LogCurrentProcessMemoryUsage();
}
}
+
+ ///
+ /// 用于在 PropertyGrid 上提供用户操作接口的包装类
+ ///
+ public class ExporterProperty(Exporter exporter)
+ {
+ [Browsable(false)]
+ public virtual Exporter Exporter { get; } = exporter;
+
+ ///
+ /// 输出文件夹
+ ///
+ [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
+ [Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")]
+ public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; }
+
+ ///
+ /// 导出单个
+ ///
+ [Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")]
+ public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; }
+
+ ///
+ /// 画面分辨率
+ ///
+ [TypeConverter(typeof(SizeConverter))]
+ [Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")]
+ public Size Resolution { get => Exporter.Resolution; }
+
+ ///
+ /// 渲染视窗
+ ///
+ [Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
+ public SFML.Graphics.View View { get => Exporter.View; }
+
+ ///
+ /// 是否仅渲染选中
+ ///
+ [Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")]
+ public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; }
+
+ ///
+ /// 背景颜色
+ ///
+ [Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
+ [TypeConverter(typeof(SFMLColorConverter))]
+ [Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
+ public SFML.Graphics.Color BackgroundColor { get => Exporter.BackgroundColor; set => Exporter.BackgroundColor = value; }
+ }
}
diff --git a/SpineViewer/Exporter/FFmpegVideoExporter.cs b/SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs
similarity index 73%
rename from SpineViewer/Exporter/FFmpegVideoExporter.cs
rename to SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs
index 9490d19..e7147ce 100644
--- a/SpineViewer/Exporter/FFmpegVideoExporter.cs
+++ b/SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs
@@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// 使用 FFmpeg 的视频导出器
@@ -51,7 +51,7 @@ namespace SpineViewer.Exporter
return null;
}
- protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
var noteSuffix = FileNameNoteSuffix;
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
@@ -76,7 +76,7 @@ namespace SpineViewer.Exporter
}
}
- protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
var noteSuffix = FileNameNoteSuffix;
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
@@ -108,4 +108,28 @@ namespace SpineViewer.Exporter
}
}
}
+
+ public class FFmpegVideoExporterProperty(FFmpegVideoExporter exporter) : VideoExporterProperty(exporter)
+ {
+ [Browsable(false)]
+ public override FFmpegVideoExporter Exporter => (FFmpegVideoExporter)base.Exporter;
+
+ ///
+ /// 文件格式
+ ///
+ [Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
+ public virtual string Format => Exporter.Format;
+
+ ///
+ /// 文件名后缀
+ ///
+ [Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
+ public virtual string Suffix => Exporter.Suffix;
+
+ ///
+ /// 文件名后缀
+ ///
+ [Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")]
+ public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
+ }
}
diff --git a/SpineViewer/Exporter/FrameExporter.cs b/SpineViewer/Spine/SpineExporter/FrameExporter.cs
similarity index 70%
rename from SpineViewer/Exporter/FrameExporter.cs
rename to SpineViewer/Spine/SpineExporter/FrameExporter.cs
index f23df3a..0d7152b 100644
--- a/SpineViewer/Exporter/FrameExporter.cs
+++ b/SpineViewer/Spine/SpineExporter/FrameExporter.cs
@@ -7,7 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// 单帧画面导出器
@@ -43,7 +43,7 @@ namespace SpineViewer.Exporter
}
private SizeF dpi = new(144, 144);
- protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
// 导出单个时必定提供输出文件夹
var filename = $"frame_{timestamp}{ImageFormat.GetSuffix()}";
@@ -65,7 +65,7 @@ namespace SpineViewer.Exporter
worker?.ReportProgress(100, $"已处理 1/1");
}
- protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
int total = spinesToRender.Length;
int success = 0;
@@ -104,4 +104,30 @@ namespace SpineViewer.Exporter
logger.Info("{} frames saved successfully", success);
}
}
+
+ public class FrameExporterProperty(FrameExporter exporter) : ExporterProperty(exporter)
+ {
+ [Browsable(false)]
+ public override FrameExporter Exporter => (FrameExporter)base.Exporter;
+
+ ///
+ /// 单帧画面格式
+ ///
+ [TypeConverter(typeof(ImageFormatConverter))]
+ [Category("[1] 单帧画面"), DisplayName("图像格式")]
+ public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; }
+
+ ///
+ /// 文件名后缀
+ ///
+ [Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
+ public string Suffix { get => Exporter.ImageFormat.GetSuffix(); }
+
+ ///
+ /// DPI
+ ///
+ [TypeConverter(typeof(SizeFConverter))]
+ [Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
+ public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; }
+ }
}
diff --git a/SpineViewer/Exporter/FrameSequenceExporter.cs b/SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs
similarity index 74%
rename from SpineViewer/Exporter/FrameSequenceExporter.cs
rename to SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs
index 4bbc55f..ac39fcb 100644
--- a/SpineViewer/Exporter/FrameSequenceExporter.cs
+++ b/SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs
@@ -1,4 +1,5 @@
using SpineViewer.Spine;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,7 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// 帧序列导出器
@@ -18,7 +19,7 @@ namespace SpineViewer.Exporter
///
public string Suffix { get; set; } = ".png";
- protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
// 导出单个时必定提供输出文件夹,
var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{FPS:f0}");
@@ -47,7 +48,7 @@ namespace SpineViewer.Exporter
}
}
- protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
foreach (var spine in spinesToRender)
{
@@ -82,4 +83,17 @@ namespace SpineViewer.Exporter
}
}
}
+
+ public class FrameSequenceExporterProperty(VideoExporter exporter) : VideoExporterProperty(exporter)
+ {
+ [Browsable(false)]
+ public override FrameSequenceExporter Exporter => (FrameSequenceExporter)base.Exporter;
+
+ ///
+ /// 文件名后缀
+ ///
+ [TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")]
+ [Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
+ public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; }
+ }
}
diff --git a/SpineViewer/Exporter/GifExporter.cs b/SpineViewer/Spine/SpineExporter/GifExporter.cs
similarity index 57%
rename from SpineViewer/Exporter/GifExporter.cs
rename to SpineViewer/Spine/SpineExporter/GifExporter.cs
index 28b2a9f..8395f73 100644
--- a/SpineViewer/Exporter/GifExporter.cs
+++ b/SpineViewer/Spine/SpineExporter/GifExporter.cs
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// GIF 导出参数
@@ -52,4 +52,28 @@ namespace SpineViewer.Exporter
options.WithCustomArgument(customArgs);
}
}
+
+ class GifExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter)
+ {
+ [Browsable(false)]
+ public override GifExporter Exporter => (GifExporter)base.Exporter;
+
+ ///
+ /// 调色板最大颜色数量
+ ///
+ [Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
+ public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; }
+
+ ///
+ /// 透明度阈值
+ ///
+ [Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
+ public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; }
+
+ ///
+ /// 透明度阈值
+ ///
+ [Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")]
+ public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
+ }
}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/MkvExporter.cs
similarity index 53%
rename from SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/MkvExporter.cs
index 2c00492..c7be9c2 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/MkvExporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,48 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class MkvExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// MKV 导出参数
+ ///
+ public class MkvExporter : FFmpegVideoExporter
+ {
+ public MkvExporter()
+ {
+ BackgroundColor = new(0, 255, 0);
+ }
+
+ public override string Format => "matroska";
+
+ public override string Suffix => ".mkv";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "libx265";
+
+ ///
+ /// CRF
+ ///
+ public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
+ private int crf = 23;
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuv444p";
+
+ public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
+ }
+ }
+
+ public class MkvExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override MkvExporter Exporter => (MkvExporter)base.Exporter;
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/MovExporter.cs
similarity index 54%
rename from SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/MovExporter.cs
index 7bf3b68..dddcbb6 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/MovExporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,47 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class MovExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// MOV 导出参数
+ ///
+ public class MovExporter : FFmpegVideoExporter
+ {
+ public MovExporter()
+ {
+ BackgroundColor = new(0, 255, 0);
+ }
+
+ public override string Format => "mov";
+
+ public override string Suffix => ".mov";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "prores_ks";
+
+ ///
+ /// 预设
+ ///
+ public string Profile { get; set; } = "auto";
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuva444p10le";
+
+ public override string FileNameNoteSuffix => $"{Codec}_{Profile}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithFastStart().WithVideoCodec(Codec).WithCustomArgument($"-profile {Profile}").ForcePixelFormat(PixelFormat);
+ }
+ }
+
+ public class MovExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override MovExporter Exporter => (MovExporter)base.Exporter;
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/Mp4Exporter.cs
similarity index 52%
rename from SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/Mp4Exporter.cs
index be0529d..e6aac2a 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/Mp4Exporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,48 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class Mp4ExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// MP4 导出参数
+ ///
+ public class Mp4Exporter : FFmpegVideoExporter
+ {
+ public Mp4Exporter()
+ {
+ BackgroundColor = new(0, 255, 0);
+ }
+
+ public override string Format => "mp4";
+
+ public override string Suffix => ".mp4";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "libx264";
+
+ ///
+ /// CRF
+ ///
+ public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
+ private int crf = 23;
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuv444p";
+
+ public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithFastStart().WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
+ }
+ }
+
+ public class Mp4ExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override Mp4Exporter Exporter => (Mp4Exporter)base.Exporter;
diff --git a/SpineViewer/Exporter/VideoExporter.cs b/SpineViewer/Spine/SpineExporter/VideoExporter.cs
similarity index 75%
rename from SpineViewer/Exporter/VideoExporter.cs
rename to SpineViewer/Spine/SpineExporter/VideoExporter.cs
index a36ea2b..04bc6fe 100644
--- a/SpineViewer/Exporter/VideoExporter.cs
+++ b/SpineViewer/Spine/SpineExporter/VideoExporter.cs
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
///
/// 视频导出基类
@@ -41,7 +41,7 @@ namespace SpineViewer.Exporter
///
/// 生成单个模型的帧序列
///
- protected IEnumerable GetFrames(Spine.SpineObject spine, BackgroundWorker? worker = null)
+ protected IEnumerable GetFrames(SpineObject spine, BackgroundWorker? worker = null)
{
// 独立导出时如果 Duration 小于 0 则使用所有轨道上动画时长最大值
var duration = Duration;
@@ -51,7 +51,7 @@ namespace SpineViewer.Exporter
int total = (int)(duration * FPS); // 完整帧的数量
float deltaFinal = duration - delta * total; // 最后一帧时长
- int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0;
+ int final = KeepLast && deltaFinal > 1e-3 ? 1 : 0;
int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
@@ -90,7 +90,7 @@ namespace SpineViewer.Exporter
///
/// 生成多个模型的帧序列
///
- protected IEnumerable GetFrames(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
+ protected IEnumerable GetFrames(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
{
// 导出单个时必须根据 Duration 决定导出时长
var duration = Duration;
@@ -99,7 +99,7 @@ namespace SpineViewer.Exporter
int total = (int)(duration * FPS); // 完整帧的数量
float deltaFinal = duration - delta * total; // 最后一帧时长
- int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0;
+ int final = KeepLast && deltaFinal > 1e-3 ? 1 : 0;
int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
@@ -135,11 +135,35 @@ namespace SpineViewer.Exporter
}
}
- public override void Export(Spine.SpineObject[] spines, BackgroundWorker? worker = null)
+ public override void Export(SpineObject[] spines, BackgroundWorker? worker = null)
{
// 导出视频格式需要把模型时间都重置到 0
foreach (var spine in spines) spine.ResetAnimationsTime();
base.Export(spines, worker);
}
}
+
+ public class VideoExporterProperty(VideoExporter exporter) : ExporterProperty(exporter)
+ {
+ [Browsable(false)]
+ public override VideoExporter Exporter => (VideoExporter)base.Exporter;
+
+ ///
+ /// 导出时长
+ ///
+ [Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")]
+ public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; }
+
+ ///
+ /// 帧率
+ ///
+ [Category("[1] 视频参数"), DisplayName("帧率"), Description("每秒画面数")]
+ public float FPS { get => Exporter.FPS; set => Exporter.FPS = value; }
+
+ ///
+ /// 保留最后一帧
+ ///
+ [Category("[1] 视频参数"), DisplayName("保留最后一帧"), Description("当设置保留最后一帧时, 动图会更为连贯, 但是帧数可能比预期帧数多 1")]
+ public bool KeepLast { get => Exporter.KeepLast; set => Exporter.KeepLast = value; }
+ }
}
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/WebmExporter.cs
similarity index 52%
rename from SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/WebmExporter.cs
index 12e3cb0..560d8a0 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/WebmExporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,49 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class WebmExporterWrapper(WebmExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// WebM 导出参数
+ ///
+ public class WebmExporter : FFmpegVideoExporter
+ {
+ public WebmExporter()
+ {
+ // 默认用透明黑背景
+ BackgroundColor = new(0, 0, 0, 0);
+ }
+
+ public override string Format => "webm";
+
+ public override string Suffix => ".webm";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "libvpx-vp9";
+
+ ///
+ /// CRF
+ ///
+ public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
+ private int crf = 23;
+
+ ///
+ /// 像素格式
+ ///
+ public string PixelFormat { get; set; } = "yuva420p";
+
+ public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
+
+ public override void SetOutputOptions(FFMpegArgumentOptions options)
+ {
+ base.SetOutputOptions(options);
+ options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
+ }
+ }
+
+ public class WebmExporterProperty(WebmExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override WebmExporter Exporter => (WebmExporter)base.Exporter;
diff --git a/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/WebpExporter.cs
similarity index 53%
rename from SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs
rename to SpineViewer/Spine/SpineExporter/WebpExporter.cs
index ab3696e..fc62228 100644
--- a/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs
+++ b/SpineViewer/Spine/SpineExporter/WebpExporter.cs
@@ -1,4 +1,5 @@
-using SpineViewer.Exporter;
+using FFMpegCore;
+using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -6,9 +7,59 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers.Exporter
+namespace SpineViewer.Spine.SpineExporter
{
- public class WebpExporterWrapper(WebpExporter exporter) : FFmpegVideoExporterWrapper(exporter)
+ ///
+ /// MP4 导出参数
+ ///
+ public class WebpExporter : FFmpegVideoExporter
+ {
+ public WebpExporter()
+ {
+ FPS = 24;
+ }
+
+ public override string Format => "webp";
+
+ public override string Suffix => ".webp";
+
+ ///
+ /// 编码器
+ ///
+ public string Codec { get; set; } = "libwebp_anim";
+
+ ///
+ /// 是否无损
+ ///
+ 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).ForcePixelFormat(PixelFormat).WithCustomArgument($"-lossless {(Lossless ? 1 : 0)} -quality {Quality} -loop {Loop}");
+ }
+ }
+
+ public class WebpExporterProperty(WebpExporter exporter) : FFmpegVideoExporterProperty(exporter)
{
[Browsable(false)]
public override WebpExporter Exporter => (WebpExporter)base.Exporter;
diff --git a/SpineViewer/Utils/ImplementationResolver.cs b/SpineViewer/Utils/ImplementationResolver.cs
index eb1a1aa..be6d69e 100644
--- a/SpineViewer/Utils/ImplementationResolver.cs
+++ b/SpineViewer/Utils/ImplementationResolver.cs
@@ -1,5 +1,4 @@
-using SpineViewer.Exporter;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
diff --git a/SpineViewer/PropertyGridWrappers/TypeConverter.cs b/SpineViewer/Utils/TypeConverter.cs
similarity index 99%
rename from SpineViewer/PropertyGridWrappers/TypeConverter.cs
rename to SpineViewer/Utils/TypeConverter.cs
index 27f7592..6a5a577 100644
--- a/SpineViewer/PropertyGridWrappers/TypeConverter.cs
+++ b/SpineViewer/Utils/TypeConverter.cs
@@ -10,7 +10,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace SpineViewer.PropertyGridWrappers
+namespace SpineViewer.Utils
{
public class PointFConverter : ExpandableObjectConverter
{
diff --git a/SpineViewer/PropertyGridWrappers/UITypeEditor.cs b/SpineViewer/Utils/UITypeEditor.cs
similarity index 99%
rename from SpineViewer/PropertyGridWrappers/UITypeEditor.cs
rename to SpineViewer/Utils/UITypeEditor.cs
index bc2dd4d..94f9720 100644
--- a/SpineViewer/PropertyGridWrappers/UITypeEditor.cs
+++ b/SpineViewer/Utils/UITypeEditor.cs
@@ -9,7 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.Design;
-namespace SpineViewer.PropertyGridWrappers
+namespace SpineViewer.Utils
{
///
/// 使用 FolderBrowserDialog 的文件夹路径编辑器