Files
SpineViewer/Spine/Exporters/CustomFFmpegExporter.cs
2025-11-03 18:09:05 +08:00

101 lines
3.9 KiB
C#

using FFMpegCore;
using FFMpegCore.Pipes;
using SFML.Graphics;
using SFML.System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Exporters
{
/// <summary>
/// 自定义参数的 FFmpeg 导出类
/// </summary>
public class CustomFFmpegExporter : VideoExporter
{
public CustomFFmpegExporter(uint width = 100, uint height = 100) : base(width, height) { }
public CustomFFmpegExporter(Vector2u resolution) : base(resolution) { }
/// <summary>
/// <c>-f</c>
/// </summary>
public string? Format { get => _format; set => _format = value; }
private string? _format;
/// <summary>
/// <c>-c:v</c>
/// </summary>
public string? Codec { get => _codec; set => _codec = value; }
private string? _codec;
/// <summary>
/// <c>-pix_fmt</c>
/// </summary>
public string? PixelFormat { get => _pixelFormat; set => _pixelFormat = value; }
private string? _pixelFormat;
/// <summary>
/// <c>-b:v</c>
/// </summary>
public string? Bitrate { get => _bitrate; set => _bitrate = value; }
private string? _bitrate;
/// <summary>
/// <c>-vf</c>
/// </summary>
public string? Filter { get => _filter; set => _filter = value; }
private string? _filter;
/// <summary>
/// 其他自定义参数
/// </summary>
public string? CustomArgs { get => _customArgs; set => _customArgs = value; }
private string? _customArgs;
private void SetOutputOptions(FFMpegArgumentOptions options)
{
if (!string.IsNullOrEmpty(_format)) options.ForceFormat(_format);
if (!string.IsNullOrEmpty(_codec)) options.WithVideoCodec(_codec);
if (!string.IsNullOrEmpty(_pixelFormat)) options.ForcePixelFormat(_pixelFormat);
if (!string.IsNullOrEmpty(_bitrate)) options.WithCustomArgument($"-b:v {_bitrate}");
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf \"unpremultiply=inplace=1, {_filter}\"");
else options.WithCustomArgument("-vf unpremultiply=inplace=1");
if (!string.IsNullOrEmpty(_customArgs)) options.WithCustomArgument($"{_customArgs}");
}
/// <summary>
/// 获取的一帧, 结果是预乘的
/// </summary>
protected override SFMLImageVideoFrame GetFrame(SpineObject[] spines)
{
// BUG: 也许和 SFML 多线程或者 FFmpeg 调用有关, 当渲染线程也在运行的时候此处并行渲染会导致和 SFML 有关的内容都卡死
// 不知道为什么用 FFmpeg 必须临时创建 RenderTexture, 否则无法正常渲染, 会导致画面帧丢失
using var tex = new RenderTexture(_renderTexture.Size.X, _renderTexture.Size.Y);
using var view = _renderTexture.GetView();
tex.SetView(view);
tex.Clear(_backgroundColorPma);
foreach (var sp in spines.Reverse()) tex.Draw(sp);
tex.Display();
return new(tex.Texture.CopyToImage());
}
public override void Export(string output, CancellationToken ct, params SpineObject[] spines)
{
var videoFramesSource = new RawVideoPipeSource(GetFrames(spines, output, ct)) { FrameRate = _fps };
try
{
var ffmpegArgs = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(output, true, SetOutputOptions);
_logger.Info("FFmpeg arguments: {0}", ffmpegArgs.Arguments);
ffmpegArgs.ProcessSynchronously();
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
}
}
}
}