增加所有导出格式
This commit is contained in:
@@ -65,7 +65,7 @@ namespace SpineViewer.Dialogs
|
||||
skelPath = Path.GetFullPath(skelPath);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(atlasPath))
|
||||
if (string.IsNullOrWhiteSpace(atlasPath))
|
||||
{
|
||||
atlasPath = null;
|
||||
}
|
||||
|
||||
@@ -79,14 +79,14 @@ namespace SpineViewer.Exporter
|
||||
/// </summary>
|
||||
public virtual string? Validate()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(OutputDir) && File.Exists(OutputDir))
|
||||
if (!string.IsNullOrWhiteSpace(OutputDir) && File.Exists(OutputDir))
|
||||
return "输出文件夹无效";
|
||||
if (!string.IsNullOrEmpty(OutputDir) && !Directory.Exists(OutputDir))
|
||||
if (!string.IsNullOrWhiteSpace(OutputDir) && !Directory.Exists(OutputDir))
|
||||
return $"文件夹 {OutputDir} 不存在";
|
||||
if (ExportSingle && string.IsNullOrEmpty(OutputDir))
|
||||
if (ExportSingle && string.IsNullOrWhiteSpace(OutputDir))
|
||||
return "导出单个时必须提供输出文件夹";
|
||||
|
||||
OutputDir = string.IsNullOrEmpty(OutputDir) ? null : Path.GetFullPath(OutputDir);
|
||||
OutputDir = string.IsNullOrWhiteSpace(OutputDir) ? null : Path.GetFullPath(OutputDir);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace SpineViewer.Exporter
|
||||
{
|
||||
Frame,
|
||||
FrameSequence,
|
||||
GIF,
|
||||
MKV,
|
||||
MP4,
|
||||
MOV,
|
||||
WebM
|
||||
Gif,
|
||||
Mp4,
|
||||
Webm,
|
||||
Mkv,
|
||||
Mov,
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// FFmpeg 自定义视频导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.Custom)]
|
||||
public class CustomExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public CustomExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly) { }
|
||||
|
||||
public override string Format => CustomFormat;
|
||||
|
||||
public override string Suffix => CustomSuffix;
|
||||
|
||||
public override string FileNameNoteSuffix => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 文件格式
|
||||
/// </summary>
|
||||
[Category("[3] 自定义参数"), DisplayName("文件格式"), Description("文件格式")]
|
||||
public string CustomFormat { get; set; } = "mp4";
|
||||
|
||||
/// <summary>
|
||||
/// 文件名后缀
|
||||
/// </summary>
|
||||
[Category("[3] 自定义参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
|
||||
public string CustomSuffix { get; set; } = ".mp4";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 FFmpeg 视频导出参数
|
||||
/// </summary>
|
||||
public abstract class FFmpegVideoExportArgs : VideoExportArgs
|
||||
{
|
||||
public FFmpegVideoExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly) { }
|
||||
|
||||
/// <summary>
|
||||
/// 文件格式
|
||||
/// </summary>
|
||||
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("文件格式")]
|
||||
public abstract string Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件名后缀
|
||||
/// </summary>
|
||||
[Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
|
||||
public abstract string Suffix { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件名后缀
|
||||
/// </summary>
|
||||
[Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("提供给 FFmpeg 的自定义参数, 除非很清楚自己在做什么, 否则请勿填写此参数")]
|
||||
public string CustomArgument { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取输出附加选项
|
||||
/// </summary>
|
||||
public virtual void SetOutputOptions(FFMpegArgumentOptions options) => options.ForceFormat(Format).WithCustomArgument(CustomArgument);
|
||||
|
||||
/// <summary>
|
||||
/// 要追加在文件名末尾的信息字串, 首尾不需要提供额外分隔符
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public abstract string FileNameNoteSuffix { get; }
|
||||
|
||||
public override string? Validate()
|
||||
{
|
||||
if (base.Validate() is string error)
|
||||
return error;
|
||||
if (string.IsNullOrWhiteSpace(Format))
|
||||
return "需要提供有效的格式";
|
||||
if (string.IsNullOrWhiteSpace(Suffix))
|
||||
return "需要提供有效的文件名后缀";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
/// 文件名后缀
|
||||
/// </summary>
|
||||
[Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
|
||||
public string FileSuffix { get => imageFormat.GetSuffix(); }
|
||||
public string Suffix { get => imageFormat.GetSuffix(); }
|
||||
|
||||
/// <summary>
|
||||
/// DPI
|
||||
|
||||
@@ -21,6 +21,6 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")]
|
||||
[Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
|
||||
public string FileSuffix { get; set; } = ".png";
|
||||
public string Suffix { get; set; } = ".png";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
@@ -10,8 +11,8 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
/// <summary>
|
||||
/// GIF 导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.GIF)]
|
||||
public class GifExportArgs : VideoExportArgs
|
||||
[ExportImplementation(ExportType.Gif)]
|
||||
public class GifExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||
{
|
||||
@@ -22,33 +23,34 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
FPS = 12;
|
||||
}
|
||||
|
||||
public override string Format => "gif";
|
||||
|
||||
public override string Suffix => ".gif";
|
||||
|
||||
/// <summary>
|
||||
/// 调色板最大颜色数量
|
||||
/// </summary>
|
||||
[Category("[2] GIF 参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
|
||||
[Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
|
||||
public uint MaxColors { get => maxColors; set => maxColors = Math.Clamp(value, 2, 256); }
|
||||
private uint maxColors = 256;
|
||||
|
||||
/// <summary>
|
||||
/// 透明度阈值
|
||||
/// </summary>
|
||||
[Category("[2] GIF 参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
|
||||
[Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
|
||||
public byte AlphaThreshold { get => alphaThreshold; set => alphaThreshold = value; }
|
||||
private byte alphaThreshold = 128;
|
||||
|
||||
/// <summary>
|
||||
/// 获取构造好的 FFMpegCore 自定义参数
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public string FFMpegCoreCustomArguments
|
||||
{
|
||||
get
|
||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
base.SetOutputOptions(options);
|
||||
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}";
|
||||
return $"-filter_complex \"{v};{s0};{s1}\"";
|
||||
}
|
||||
}
|
||||
var customArgs = $"-filter_complex \"{v};{s0};{s1}\"";
|
||||
options.WithCustomArgument(customArgs);
|
||||
}
|
||||
|
||||
public override string FileNameNoteSuffix => $"{MaxColors}_{AlphaThreshold}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// MKV 导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.Mkv)]
|
||||
public class MkvExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public MkvExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||
{
|
||||
BackgroundColor = new(0, 255, 0, 0);
|
||||
}
|
||||
|
||||
public override string Format => "matroska";
|
||||
|
||||
public override string Suffix => ".mkv";
|
||||
|
||||
/// <summary>
|
||||
/// 编码器
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("libx264", "libx265", "libvpx-vp9", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
||||
public string Codec { get; set; } = "libx265";
|
||||
|
||||
/// <summary>
|
||||
/// CRF
|
||||
/// </summary>
|
||||
[Category("[3] 格式参数"), DisplayName("CRF"), Description("-crf, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||
private int crf = 23;
|
||||
|
||||
/// <summary>
|
||||
/// 像素格式
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
||||
public string PixelFormat { get; set; } = "yuv444p";
|
||||
|
||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
base.SetOutputOptions(options);
|
||||
options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
|
||||
}
|
||||
|
||||
public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// MOV 导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.Mov)]
|
||||
public class MovExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public MovExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||
{
|
||||
BackgroundColor = new(0, 255, 0, 0);
|
||||
}
|
||||
|
||||
public override string Format => "mov";
|
||||
|
||||
public override string Suffix => ".mov";
|
||||
|
||||
/// <summary>
|
||||
/// 编码器
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("prores_ks", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
||||
public string Codec { get; set; } = "prores_ks";
|
||||
|
||||
/// <summary>
|
||||
/// 预设
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("auto", "proxy", "lt", "standard", "hq", "4444", "444xq")]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("预设"), Description("-profile, 预设配置")]
|
||||
public string Profile { get; set; } = "auto";
|
||||
|
||||
/// <summary>
|
||||
/// 像素格式
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("yuv422p10le", "yuv444p10le", "yuva444p10le", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
||||
public string PixelFormat { get; set; } = "yuva444p10le";
|
||||
|
||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
base.SetOutputOptions(options);
|
||||
options.WithFastStart().WithVideoCodec(Codec).WithCustomArgument($"-profile {Profile}").ForcePixelFormat(PixelFormat);
|
||||
}
|
||||
|
||||
public override string FileNameNoteSuffix => $"{Codec}_{Profile}_{PixelFormat}";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
@@ -10,26 +11,47 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
/// <summary>
|
||||
/// MP4 导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.MP4)]
|
||||
public class Mp4ExportArgs : VideoExportArgs
|
||||
[ExportImplementation(ExportType.Mp4)]
|
||||
public class Mp4ExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public Mp4ExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||
{
|
||||
// MP4 默认用绿幕
|
||||
BackgroundColor = new(0, 255, 0, 0);
|
||||
}
|
||||
|
||||
public override string Format => "mp4";
|
||||
|
||||
public override string Suffix => ".mp4";
|
||||
|
||||
/// <summary>
|
||||
/// 编码器
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("libx264", "libx265", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
||||
public string Codec { get; set; } = "libx264";
|
||||
|
||||
/// <summary>
|
||||
/// CRF
|
||||
/// </summary>
|
||||
[Category("[2] MP4 参数"), DisplayName("CRF"), Description("Constant Rate Factor, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
||||
[Category("[3] 格式参数"), DisplayName("CRF"), Description("-crf, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||
private int crf = 23;
|
||||
|
||||
/// <summary>
|
||||
/// 编码器 TODO: 增加其他编码器
|
||||
/// 像素格式
|
||||
/// </summary>
|
||||
[Category("[2] MP4 参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
||||
public string Codec { get => "libx264"; }
|
||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
||||
public string PixelFormat { get; set; } = "yuv444p";
|
||||
|
||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
base.SetOutputOptions(options);
|
||||
options.WithFastStart().WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
|
||||
}
|
||||
|
||||
public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using FFMpegCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.ExportArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// WebM 导出参数
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.Webm)]
|
||||
public class WebmExportArgs : FFmpegVideoExportArgs
|
||||
{
|
||||
public WebmExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||
{
|
||||
// 默认用透明黑背景
|
||||
BackgroundColor = new(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public override string Format => "webm";
|
||||
|
||||
public override string Suffix => ".webm";
|
||||
|
||||
/// <summary>
|
||||
/// 编码器
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("libvpx-vp9", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("编码器"), Description("要使用的编码器")]
|
||||
public string Codec { get; set; } = "libvpx-vp9";
|
||||
|
||||
/// <summary>
|
||||
/// CRF
|
||||
/// </summary>
|
||||
[Category("[3] 格式参数"), DisplayName("CRF"), Description("Constant Rate Factor, 取值范围 0-63, 建议范围 18-28, 默认取值 23, 数值越小则输出质量越高")]
|
||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||
private int crf = 23;
|
||||
|
||||
/// <summary>
|
||||
/// 像素格式
|
||||
/// </summary>
|
||||
[StringEnumConverter.StandardValues("yuv420p", "yuv422p", "yuv444p", "yuva420p", Customizable = true)]
|
||||
[TypeConverter(typeof(StringEnumConverter))]
|
||||
[Category("[3] 格式参数"), DisplayName("像素格式"), Description("要使用的像素格式")]
|
||||
public string PixelFormat { get; set; } = "yuva420p";
|
||||
|
||||
public override void SetOutputOptions(FFMpegArgumentOptions options)
|
||||
{
|
||||
base.SetOutputOptions(options);
|
||||
options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat);
|
||||
}
|
||||
|
||||
public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}";
|
||||
}
|
||||
}
|
||||
@@ -13,49 +13,57 @@ using System.Diagnostics;
|
||||
namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// GIF 动图导出器
|
||||
/// 使用 FFmpeg 的视频导出器
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.GIF)]
|
||||
public class GifExporter : VideoExporter
|
||||
[ExportImplementation(ExportType.Gif)]
|
||||
[ExportImplementation(ExportType.Mp4)]
|
||||
[ExportImplementation(ExportType.Webm)]
|
||||
[ExportImplementation(ExportType.Mkv)]
|
||||
[ExportImplementation(ExportType.Mov)]
|
||||
[ExportImplementation(ExportType.Custom)]
|
||||
public class FFmpegVideoExporter : VideoExporter
|
||||
{
|
||||
public GifExporter(GifExportArgs exportArgs) : base(exportArgs) { }
|
||||
public FFmpegVideoExporter(FFmpegVideoExportArgs exportArgs) : base(exportArgs) { }
|
||||
|
||||
protected override void ExportSingle(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
var args = (GifExportArgs)ExportArgs;
|
||||
var args = (FFmpegVideoExportArgs)ExportArgs;
|
||||
var noteSuffix = args.FileNameNoteSuffix;
|
||||
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
|
||||
|
||||
var filename = $"{timestamp}_{args.FPS:f0}{noteSuffix}{args.Suffix}";
|
||||
|
||||
// 导出单个时必定提供输出文件夹
|
||||
var filename = $"{timestamp}_{args.FPS:f0}_{args.MaxColors}_{args.AlphaThreshold}.gif";
|
||||
var savePath = Path.Combine(args.OutputDir, filename);
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(GetFrames(spinesToRender, worker)) { FrameRate = args.FPS };
|
||||
try
|
||||
{
|
||||
var ffmpegArgs = FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(savePath, true, options => options
|
||||
.ForceFormat("gif")
|
||||
.WithCustomArgument(args.FFMpegCoreCustomArguments));
|
||||
var ffmpegArgs = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(savePath, true, args.SetOutputOptions);
|
||||
|
||||
logger.Info("FFMpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
logger.Info("FFmpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
ffmpegArgs.ProcessSynchronously();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex.ToString());
|
||||
logger.Error("Failed to export gif {}", savePath);
|
||||
logger.Error("Failed to export {} {}", args.Format, savePath);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExportIndividual(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
var args = (GifExportArgs)ExportArgs;
|
||||
var args = (FFmpegVideoExportArgs)ExportArgs;
|
||||
var noteSuffix = args.FileNameNoteSuffix;
|
||||
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
|
||||
|
||||
foreach (var spine in spinesToRender)
|
||||
{
|
||||
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||
|
||||
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}{noteSuffix}{args.Suffix}";
|
||||
|
||||
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}_{args.MaxColors}_{args.AlphaThreshold}.gif";
|
||||
var savePath = Path.Combine(args.OutputDir ?? spine.AssetsDir, filename);
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(GetFrames(spine, worker)) { FrameRate = args.FPS };
|
||||
@@ -63,17 +71,15 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
{
|
||||
var ffmpegArgs = FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(savePath, true, options => options
|
||||
.ForceFormat("gif")
|
||||
.WithCustomArgument(args.FFMpegCoreCustomArguments));
|
||||
.OutputToFile(savePath, true, args.SetOutputOptions);
|
||||
|
||||
logger.Info("FFMpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
logger.Info("FFmpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
ffmpegArgs.ProcessSynchronously();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex.ToString());
|
||||
logger.Error("Failed to export gif {} {}", savePath, spine.SkelPath);
|
||||
logger.Error("Failed to export {} {} {}", args.Format, savePath, spine.SkelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
var args = (FrameExportArgs)ExportArgs;
|
||||
|
||||
// 导出单个时必定提供输出文件夹
|
||||
var filename = $"frame_{timestamp}{args.FileSuffix}";
|
||||
var filename = $"frame_{timestamp}{args.Suffix}";
|
||||
var savePath = Path.Combine(args.OutputDir, filename);
|
||||
|
||||
worker?.ReportProgress(0, $"已处理 0/1");
|
||||
@@ -55,7 +55,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
var spine = spinesToRender[i];
|
||||
|
||||
// 逐个导出时如果提供了输出文件夹, 则全部导出到输出文件夹, 否则输出到各自的文件夹
|
||||
var filename = $"{spine.Name}_{timestamp}{args.FileSuffix}";
|
||||
var filename = $"{spine.Name}_{timestamp}{args.Suffix}";
|
||||
var savePath = args.OutputDir is null ? Path.Combine(spine.AssetsDir, filename) : Path.Combine(args.OutputDir, filename);
|
||||
|
||||
try
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
int frameIdx = 0;
|
||||
foreach (var frame in GetFrames(spinesToRender, worker))
|
||||
{
|
||||
var filename = $"frames_{timestamp}_{args.FPS:f0}_{frameIdx:d6}{args.FileSuffix}";
|
||||
var filename = $"frames_{timestamp}_{args.FPS:f0}_{frameIdx:d6}{args.Suffix}";
|
||||
var savePath = Path.Combine(saveDir, filename);
|
||||
|
||||
try
|
||||
@@ -63,7 +63,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
int frameIdx = 0;
|
||||
foreach (var frame in GetFrames(spine, worker))
|
||||
{
|
||||
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}_{frameIdx:d6}{args.FileSuffix}";
|
||||
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}_{frameIdx:d6}{args.Suffix}";
|
||||
var savePath = Path.Combine(saveDir, filename);
|
||||
|
||||
try
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore;
|
||||
using SpineViewer.Exporter.Implementations.ExportArgs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Arguments;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SpineViewer.Exporter.Implementations.Exporter
|
||||
{
|
||||
/// <summary>
|
||||
/// MP4 导出器
|
||||
/// </summary>
|
||||
[ExportImplementation(ExportType.MP4)]
|
||||
public class Mp4Exporter : VideoExporter
|
||||
{
|
||||
public Mp4Exporter(Mp4ExportArgs exportArgs) : base(exportArgs) { }
|
||||
|
||||
protected override void ExportSingle(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
var args = (Mp4ExportArgs)ExportArgs;
|
||||
|
||||
// 导出单个时必定提供输出文件夹
|
||||
var filename = $"{timestamp}_{args.FPS:f0}_{args.CRF}.mp4";
|
||||
var savePath = Path.Combine(args.OutputDir, filename);
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(GetFrames(spinesToRender, worker)) { FrameRate = args.FPS };
|
||||
try
|
||||
{
|
||||
var ffmpegArgs = FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(savePath, true, options => options
|
||||
.ForceFormat("mp4")
|
||||
.WithVideoCodec(args.Codec)
|
||||
.WithConstantRateFactor(args.CRF)
|
||||
.WithFastStart());
|
||||
|
||||
logger.Info("FFMpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
ffmpegArgs.ProcessSynchronously();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex.ToString());
|
||||
logger.Error("Failed to export mp4 {}", savePath);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExportIndividual(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||
{
|
||||
var args = (Mp4ExportArgs)ExportArgs;
|
||||
foreach (var spine in spinesToRender)
|
||||
{
|
||||
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||
|
||||
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}_{args.CRF}.mp4";
|
||||
var savePath = Path.Combine(args.OutputDir ?? spine.AssetsDir, filename);
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(GetFrames(spine, worker)) { FrameRate = args.FPS };
|
||||
try
|
||||
{
|
||||
var ffmpegArgs = FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(savePath, true, options => options
|
||||
.ForceFormat("mp4")
|
||||
.WithVideoCodec(args.Codec)
|
||||
.WithConstantRateFactor(args.CRF)
|
||||
.WithFastStart());
|
||||
|
||||
logger.Info("FFMpeg arguments: {}", ffmpegArgs.Arguments);
|
||||
ffmpegArgs.ProcessSynchronously();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex.ToString());
|
||||
logger.Error("Failed to export mp4 {} {}", savePath, spine.SkelPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
SpineViewer/MainForm.Designer.cs
generated
52
SpineViewer/MainForm.Designer.cs
generated
@@ -43,6 +43,7 @@
|
||||
toolStripMenuItem_ExportMp4 = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ExportMov = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ExportWebm = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ExportCustom = new ToolStripMenuItem();
|
||||
toolStripSeparator2 = new ToolStripSeparator();
|
||||
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Tool = new ToolStripMenuItem();
|
||||
@@ -132,7 +133,7 @@
|
||||
//
|
||||
// toolStripMenuItem_Export
|
||||
//
|
||||
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportWebm });
|
||||
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportWebm, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportCustom });
|
||||
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
|
||||
toolStripMenuItem_Export.Size = new Size(270, 34);
|
||||
toolStripMenuItem_Export.Text = "导出(&E)";
|
||||
@@ -140,55 +141,59 @@
|
||||
// toolStripMenuItem_ExportFrame
|
||||
//
|
||||
toolStripMenuItem_ExportFrame.Name = "toolStripMenuItem_ExportFrame";
|
||||
toolStripMenuItem_ExportFrame.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportFrame.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportFrame.Text = "单帧画面...";
|
||||
toolStripMenuItem_ExportFrame.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportFrameSequence
|
||||
//
|
||||
toolStripMenuItem_ExportFrameSequence.Name = "toolStripMenuItem_ExportFrameSequence";
|
||||
toolStripMenuItem_ExportFrameSequence.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportFrameSequence.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportFrameSequence.Text = "帧序列...";
|
||||
toolStripMenuItem_ExportFrameSequence.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportGif
|
||||
//
|
||||
toolStripMenuItem_ExportGif.Name = "toolStripMenuItem_ExportGif";
|
||||
toolStripMenuItem_ExportGif.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportGif.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportGif.Text = "GIF...";
|
||||
toolStripMenuItem_ExportGif.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportMkv
|
||||
//
|
||||
toolStripMenuItem_ExportMkv.Name = "toolStripMenuItem_ExportMkv";
|
||||
toolStripMenuItem_ExportMkv.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportMkv.Text = "MKV";
|
||||
toolStripMenuItem_ExportMkv.Visible = false;
|
||||
toolStripMenuItem_ExportMkv.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportMkv.Text = "MKV...";
|
||||
toolStripMenuItem_ExportMkv.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportMp4
|
||||
//
|
||||
toolStripMenuItem_ExportMp4.Name = "toolStripMenuItem_ExportMp4";
|
||||
toolStripMenuItem_ExportMp4.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportMp4.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportMp4.Text = "MP4...";
|
||||
toolStripMenuItem_ExportMp4.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportMov
|
||||
//
|
||||
toolStripMenuItem_ExportMov.Name = "toolStripMenuItem_ExportMov";
|
||||
toolStripMenuItem_ExportMov.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportMov.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportMov.Text = "MOV...";
|
||||
toolStripMenuItem_ExportMov.Visible = false;
|
||||
toolStripMenuItem_ExportMov.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportWebm
|
||||
//
|
||||
toolStripMenuItem_ExportWebm.Name = "toolStripMenuItem_ExportWebm";
|
||||
toolStripMenuItem_ExportWebm.Size = new Size(270, 34);
|
||||
toolStripMenuItem_ExportWebm.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportWebm.Text = "WebM...";
|
||||
toolStripMenuItem_ExportWebm.Visible = false;
|
||||
toolStripMenuItem_ExportWebm.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportCustom
|
||||
//
|
||||
toolStripMenuItem_ExportCustom.Name = "toolStripMenuItem_ExportCustom";
|
||||
toolStripMenuItem_ExportCustom.Size = new Size(288, 34);
|
||||
toolStripMenuItem_ExportCustom.Text = "FFmpeg 自定义导出...";
|
||||
toolStripMenuItem_ExportCustom.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripSeparator2
|
||||
//
|
||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||
@@ -266,7 +271,7 @@
|
||||
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
||||
rtbLog.Name = "rtbLog";
|
||||
rtbLog.ReadOnly = true;
|
||||
rtbLog.Size = new Size(1758, 134);
|
||||
rtbLog.Size = new Size(1758, 146);
|
||||
rtbLog.TabIndex = 0;
|
||||
rtbLog.Text = "";
|
||||
rtbLog.WordWrap = false;
|
||||
@@ -290,7 +295,7 @@
|
||||
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
||||
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_MainForm.Size = new Size(1758, 1097);
|
||||
splitContainer_MainForm.SplitterDistance = 955;
|
||||
splitContainer_MainForm.SplitterDistance = 943;
|
||||
splitContainer_MainForm.SplitterWidth = 8;
|
||||
splitContainer_MainForm.TabIndex = 3;
|
||||
splitContainer_MainForm.TabStop = false;
|
||||
@@ -314,7 +319,7 @@
|
||||
//
|
||||
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
||||
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Functional.Size = new Size(1758, 955);
|
||||
splitContainer_Functional.Size = new Size(1758, 943);
|
||||
splitContainer_Functional.SplitterDistance = 759;
|
||||
splitContainer_Functional.SplitterWidth = 8;
|
||||
splitContainer_Functional.TabIndex = 2;
|
||||
@@ -338,7 +343,7 @@
|
||||
//
|
||||
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
||||
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Information.Size = new Size(759, 955);
|
||||
splitContainer_Information.Size = new Size(759, 943);
|
||||
splitContainer_Information.SplitterDistance = 354;
|
||||
splitContainer_Information.SplitterWidth = 8;
|
||||
splitContainer_Information.TabIndex = 1;
|
||||
@@ -352,7 +357,7 @@
|
||||
groupBox_SkelList.Dock = DockStyle.Fill;
|
||||
groupBox_SkelList.Location = new Point(0, 0);
|
||||
groupBox_SkelList.Name = "groupBox_SkelList";
|
||||
groupBox_SkelList.Size = new Size(354, 955);
|
||||
groupBox_SkelList.Size = new Size(354, 943);
|
||||
groupBox_SkelList.TabIndex = 0;
|
||||
groupBox_SkelList.TabStop = false;
|
||||
groupBox_SkelList.Text = "模型列表";
|
||||
@@ -363,7 +368,7 @@
|
||||
spineListView.Location = new Point(3, 26);
|
||||
spineListView.Name = "spineListView";
|
||||
spineListView.PropertyGrid = propertyGrid_Spine;
|
||||
spineListView.Size = new Size(348, 926);
|
||||
spineListView.Size = new Size(348, 914);
|
||||
spineListView.TabIndex = 0;
|
||||
//
|
||||
// propertyGrid_Spine
|
||||
@@ -372,7 +377,7 @@
|
||||
propertyGrid_Spine.HelpVisible = false;
|
||||
propertyGrid_Spine.Location = new Point(3, 26);
|
||||
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
||||
propertyGrid_Spine.Size = new Size(391, 592);
|
||||
propertyGrid_Spine.Size = new Size(391, 580);
|
||||
propertyGrid_Spine.TabIndex = 0;
|
||||
propertyGrid_Spine.ToolbarVisible = false;
|
||||
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
@@ -395,7 +400,7 @@
|
||||
//
|
||||
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
|
||||
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Config.Size = new Size(397, 955);
|
||||
splitContainer_Config.Size = new Size(397, 943);
|
||||
splitContainer_Config.SplitterDistance = 326;
|
||||
splitContainer_Config.SplitterWidth = 8;
|
||||
splitContainer_Config.TabIndex = 0;
|
||||
@@ -431,7 +436,7 @@
|
||||
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
||||
groupBox_SkelConfig.Location = new Point(0, 0);
|
||||
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
||||
groupBox_SkelConfig.Size = new Size(397, 621);
|
||||
groupBox_SkelConfig.Size = new Size(397, 609);
|
||||
groupBox_SkelConfig.TabIndex = 0;
|
||||
groupBox_SkelConfig.TabStop = false;
|
||||
groupBox_SkelConfig.Text = "模型参数";
|
||||
@@ -442,7 +447,7 @@
|
||||
groupBox_Preview.Dock = DockStyle.Fill;
|
||||
groupBox_Preview.Location = new Point(0, 0);
|
||||
groupBox_Preview.Name = "groupBox_Preview";
|
||||
groupBox_Preview.Size = new Size(991, 955);
|
||||
groupBox_Preview.Size = new Size(991, 943);
|
||||
groupBox_Preview.TabIndex = 1;
|
||||
groupBox_Preview.TabStop = false;
|
||||
groupBox_Preview.Text = "预览画面";
|
||||
@@ -453,7 +458,7 @@
|
||||
spinePreviewer.Location = new Point(3, 26);
|
||||
spinePreviewer.Name = "spinePreviewer";
|
||||
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
||||
spinePreviewer.Size = new Size(985, 926);
|
||||
spinePreviewer.Size = new Size(985, 914);
|
||||
spinePreviewer.SpineListView = spineListView;
|
||||
spinePreviewer.TabIndex = 0;
|
||||
//
|
||||
@@ -553,5 +558,6 @@
|
||||
private ToolStripMenuItem toolStripMenuItem_ExportMov;
|
||||
private ToolStripMenuItem toolStripMenuItem_ExportMkv;
|
||||
private ToolStripMenuItem toolStripMenuItem_ExportWebm;
|
||||
private ToolStripMenuItem toolStripMenuItem_ExportCustom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@ namespace SpineViewer
|
||||
// 在此处将导出菜单需要的类绑定起来
|
||||
toolStripMenuItem_ExportFrame.Tag = ExportType.Frame;
|
||||
toolStripMenuItem_ExportFrameSequence.Tag = ExportType.FrameSequence;
|
||||
toolStripMenuItem_ExportGif.Tag = ExportType.GIF;
|
||||
toolStripMenuItem_ExportMkv.Tag = ExportType.MKV;
|
||||
toolStripMenuItem_ExportMp4.Tag = ExportType.MP4;
|
||||
toolStripMenuItem_ExportMov.Tag = ExportType.MOV;
|
||||
toolStripMenuItem_ExportWebm.Tag = ExportType.WebM;
|
||||
toolStripMenuItem_ExportGif.Tag = ExportType.Gif;
|
||||
toolStripMenuItem_ExportMkv.Tag = ExportType.Mkv;
|
||||
toolStripMenuItem_ExportMp4.Tag = ExportType.Mp4;
|
||||
toolStripMenuItem_ExportMov.Tag = ExportType.Mov;
|
||||
toolStripMenuItem_ExportWebm.Tag = ExportType.Webm;
|
||||
toolStripMenuItem_ExportCustom.Tag = ExportType.Custom;
|
||||
|
||||
// 执行一些初始化工作
|
||||
try
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace SpineViewer
|
||||
public ReadOnlyCollection<string> StandardValues { get; private set; }
|
||||
private readonly List<string> standardValues = [];
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许用户自定义
|
||||
/// </summary>
|
||||
public bool Customizable { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 字符串标准值列表
|
||||
/// </summary>
|
||||
@@ -74,7 +79,11 @@ namespace SpineViewer
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
|
||||
{
|
||||
var customizable = context?.PropertyDescriptor?.Attributes.OfType<StandardValuesAttribute>().FirstOrDefault()?.Customizable ?? false;
|
||||
return !customizable;
|
||||
}
|
||||
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user