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 { /// /// 自定义参数的 FFmpeg 导出类 /// public class CustomFFmpegExporter : VideoExporter { public CustomFFmpegExporter(uint width = 100, uint height = 100) : base(width, height) { } public CustomFFmpegExporter(Vector2u resolution) : base(resolution) { } /// /// -f /// public string? Format { get => _format; set => _format = value; } private string? _format; /// /// -c:v /// public string? Codec { get => _codec; set => _codec = value; } private string? _codec; /// /// -pix_fmt /// public string? PixelFormat { get => _pixelFormat; set => _pixelFormat = value; } private string? _pixelFormat; /// /// -b:v /// public string? Bitrate { get => _bitrate; set => _bitrate = value; } private string? _bitrate; /// /// -vf /// public string? Filter { get => _filter; set => _filter = value; } private string? _filter; /// /// 其他自定义参数 /// 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, {_customArgs}"); else options.WithCustomArgument("-vf unpremultiply=inplace=1"); } /// /// 获取的一帧, 结果是预乘的 /// 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.Trace(ex.ToString()); _logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message); } } } }