增加GIF导出格式
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
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>
|
||||||
|
/// GIF 导出参数
|
||||||
|
/// </summary>
|
||||||
|
[ExportImplementation(ExportType.GIF)]
|
||||||
|
public class GifExportArgs : VideoExportArgs
|
||||||
|
{
|
||||||
|
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
|
||||||
|
{
|
||||||
|
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
|
||||||
|
FPS = 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 调色板最大颜色数量
|
||||||
|
/// </summary>
|
||||||
|
[Category("GIF 参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
|
||||||
|
public uint MaxColors { get => maxColors; set => maxColors = Math.Clamp(value, 2, 256); }
|
||||||
|
private uint maxColors = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 透明度阈值
|
||||||
|
/// </summary>
|
||||||
|
[Category("GIF 参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
|
||||||
|
public byte AlphaThreshold { get => alphaThreshold; set => alphaThreshold = value; }
|
||||||
|
private byte alphaThreshold = 128;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取构造好的 FFMpegCore 自定义参数
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public string FFMpegCoreCustomArguments
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
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}\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
SpineViewer/Exporter/Implementations/Exporter/GifExporter.cs
Normal file
76
SpineViewer/Exporter/Implementations/Exporter/GifExporter.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace SpineViewer.Exporter.Implementations.Exporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// GIF 动图导出器
|
||||||
|
/// </summary>
|
||||||
|
[ExportImplementation(ExportType.GIF)]
|
||||||
|
public class GifExporter : VideoExporter
|
||||||
|
{
|
||||||
|
public GifExporter(GifExportArgs exportArgs) : base(exportArgs) { }
|
||||||
|
|
||||||
|
protected override void ExportSingle(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (GifExportArgs)ExportArgs;
|
||||||
|
|
||||||
|
// 导出单个时必定提供输出文件夹
|
||||||
|
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
|
||||||
|
{
|
||||||
|
FFMpegArguments
|
||||||
|
.FromPipeInput(videoFramesSource)
|
||||||
|
.OutputToFile(savePath, true, options => options
|
||||||
|
.ForceFormat("gif")
|
||||||
|
.WithCustomArgument(args.FFMpegCoreCustomArguments))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to export gif {}", savePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ExportIndividual(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (GifExportArgs)ExportArgs;
|
||||||
|
foreach (var spine in spinesToRender)
|
||||||
|
{
|
||||||
|
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||||
|
|
||||||
|
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||||
|
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 };
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FFMpegArguments
|
||||||
|
.FromPipeInput(videoFramesSource)
|
||||||
|
.OutputToFile(savePath, true, options => options
|
||||||
|
.ForceFormat("gif")
|
||||||
|
.WithCustomArgument(args.FFMpegCoreCustomArguments))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to export gif {} {}", savePath, spine.SkelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user