diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b116a5..dab3b60 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# CHANGELOG
+## v0.15.5
+
+- 修复自定义导出时的画面错误
+- 设置 mp4 像素格式为 yuv420p 避免 windows 默认播放器无法打开
+- 增加预览画面和导出时的速度参数设置
+- 修复一些提示文本错误
+- 导出时自动将分辨率向下调整为 2 的倍数, 避免 yuv420p 格式出错
+
## v0.15.4
- 修复导出时可能的卡死问题
diff --git a/Spine/Exporters/BaseExporter.cs b/Spine/Exporters/BaseExporter.cs
index 858cf76..7614a92 100644
--- a/Spine/Exporters/BaseExporter.cs
+++ b/Spine/Exporters/BaseExporter.cs
@@ -31,6 +31,9 @@ namespace Spine.Exporters
/// 画布高像素值
public BaseExporter(uint width , uint height)
{
+ // XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
+ width = width >> 1 << 1;
+ height = height >> 1 << 1;
if (width <= 0 || height <= 0)
throw new ArgumentException($"Invalid resolution: {width}, {height}");
_renderTexture = new(width, height);
@@ -42,6 +45,9 @@ namespace Spine.Exporters
///
public BaseExporter(Vector2u resolution)
{
+ // XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
+ resolution.X = resolution.X >> 1 << 1;
+ resolution.Y = resolution.Y >> 1 << 1;
if (resolution.X <= 0 || resolution.Y <= 0)
throw new ArgumentException($"Invalid resolution: {resolution}");
_renderTexture = new(resolution.X, resolution.Y);
@@ -92,6 +98,9 @@ namespace Spine.Exporters
get => _renderTexture.Size;
set
{
+ // XXX: 强制变成 2 的倍数, 防止像是 yuv420p 这种像素格式报错
+ value.X = value.X >> 1 << 1;
+ value.Y = value.Y >> 1 << 1;
if (value.X <= 0 || value.Y <= 0)
{
_logger.Warn("Omit invalid exporter resolution: {0}", value);
diff --git a/Spine/Exporters/CustomFFmpegExporter.cs b/Spine/Exporters/CustomFFmpegExporter.cs
index 174a70b..b1c5507 100644
--- a/Spine/Exporters/CustomFFmpegExporter.cs
+++ b/Spine/Exporters/CustomFFmpegExporter.cs
@@ -1,5 +1,6 @@
using FFMpegCore;
using FFMpegCore.Pipes;
+using SFML.Graphics;
using SFML.System;
using System;
using System.Collections.Generic;
@@ -63,6 +64,22 @@ namespace Spine.Exporters
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 };
diff --git a/Spine/Exporters/FFmpegVideoExporter.cs b/Spine/Exporters/FFmpegVideoExporter.cs
index cce74c7..adf183b 100644
--- a/Spine/Exporters/FFmpegVideoExporter.cs
+++ b/Spine/Exporters/FFmpegVideoExporter.cs
@@ -126,8 +126,10 @@ namespace Spine.Exporters
private void SetMp4Options(FFMpegArgumentOptions options)
{
+ // XXX: windows 默认播放器在播放 MP4 格式时对于 libx264 编码器只支持 yuv420p 的像素格式
+ // 但是如果是 libx265 则没有该限制
var customArgs = "-vf unpremultiply=inplace=1";
- options.ForceFormat("mp4").WithVideoCodec("libx264").ForcePixelFormat("yuv444p")
+ options.ForceFormat("mp4").WithVideoCodec("libx264").ForcePixelFormat("yuv420p")
.WithFastStart()
.WithConstantRateFactor(_crf)
.WithCustomArgument(customArgs);
diff --git a/Spine/Exporters/VideoExporter.cs b/Spine/Exporters/VideoExporter.cs
index c3755dd..e1bb672 100644
--- a/Spine/Exporters/VideoExporter.cs
+++ b/Spine/Exporters/VideoExporter.cs
@@ -55,6 +55,21 @@ namespace Spine.Exporters
}
protected float _fps = 24;
+ public float Speed
+ {
+ get => _speed;
+ set
+ {
+ if (_speed <= 0)
+ {
+ _logger.Warn("Omit invalid speed: {0}", value);
+ return;
+ }
+ _speed = value;
+ }
+ }
+ protected float _speed = 1f;
+
///
/// 是否保留最后一帧
///
@@ -92,7 +107,7 @@ namespace Spine.Exporters
// 导出完整帧
for (int i = 0; i < total; i++)
{
- foreach (var spine in spines) spine.Update(delta);
+ foreach (var spine in spines) spine.Update(delta * _speed);
yield return GetFrame(spines);
}
@@ -100,7 +115,7 @@ namespace Spine.Exporters
if (hasFinal)
{
// XXX: 此处还是按照完整的一帧时长进行更新, 也许可以只更新准确的最后一帧时长
- foreach (var spine in spines) spine.Update(delta);
+ foreach (var spine in spines) spine.Update(delta * _speed);
yield return GetFrame(spines);
}
}
diff --git a/Spine/Spine.csproj b/Spine/Spine.csproj
index fabb4ea..922df70 100644
--- a/Spine/Spine.csproj
+++ b/Spine/Spine.csproj
@@ -7,7 +7,7 @@
net8.0-windows
$(SolutionDir)out
false
- 0.15.4
+ 0.15.5
diff --git a/SpineViewer/Models/WorkspaceModel.cs b/SpineViewer/Models/WorkspaceModel.cs
index 534d13e..88d963e 100644
--- a/SpineViewer/Models/WorkspaceModel.cs
+++ b/SpineViewer/Models/WorkspaceModel.cs
@@ -37,6 +37,8 @@ namespace SpineViewer.Models
public uint MaxFps { get; set; } = 30;
+ public float Speed { get; set; } = 1f;
+
public bool ShowAxis { get; set; } = true;
public Color BackgroundColor { get; set; }
diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml
index 34e67ef..4f4399d 100644
--- a/SpineViewer/Resources/Strings/en.xaml
+++ b/SpineViewer/Resources/Strings/en.xaml
@@ -104,6 +104,7 @@
Zoom
Rotation (Degrees)
Max FPS
+ Playback Speed
Render Selected Only
Show Axis
Background Color
@@ -166,6 +167,8 @@
Export duration; if less than 0, the maximum duration of all animations in all models will be used during export.
FPS
+ Export Speed
+ Export speed factor; only affects the animation speed of the model, not the export duration or frame rate.
Keep Last Frame
When keeping the last frame, animation is smoother but frame count may be one higher
@@ -184,7 +187,7 @@
Codec
FFmpeg codec (equivalent to "-c:v"), e.g. "libx264", "libx265"
Pixel Format
- FFmpeg pixel format (equivalent to "-pix_fmt"), e.g. "yuv420", "yuv444"
+ FFmpeg pixel format (equivalent to "-pix_fmt"), e.g. "yuv420p", "yuv444p"
Bitrate
FFmpeg bitrate (equivalent to "-b:v"), e.g. "6K", "2M"
Filter
diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml
index f3c1b3d..86dcaf9 100644
--- a/SpineViewer/Resources/Strings/ja.xaml
+++ b/SpineViewer/Resources/Strings/ja.xaml
@@ -104,6 +104,7 @@
ズーム
回転(度)
最大FPS
+ 再生速度
選択のみレンダリング
座標軸を表示
背景色
@@ -166,6 +167,8 @@
エクスポート時間。0 未満の場合、エクスポート時にすべてのモデルのすべてのアニメーションの最大時間が使用されます。
FPS
+ エクスポート速度
+ エクスポート速度係数。モデルの動作速度のみに影響し、エクスポート時間やフレームレートなどには影響しません。
最後のフレームを保持
最後のフレームを保持すると、アニメーションはより連続して見えますが、フレーム数が予想より1フレーム多くなる可能性があります
@@ -184,7 +187,7 @@
コーデック
FFmpegコーデック。パラメーター“-c:v”に相当します。例: “libx264”、“libx265”
ピクセルフォーマット
- FFmpegピクセルフォーマット。パラメーター“-pix_fmt”に相当します。例: “yuv420”、“yuv444”
+ FFmpegピクセルフォーマット。パラメーター“-pix_fmt”に相当します。例: “yuv420p”、“yuv444p”
ビットレート
FFmpegビットレート。パラメーター“-b:v”に相当します。例: “6K”、“2M”
フィルター
diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml
index 53c90de..2a3eab0 100644
--- a/SpineViewer/Resources/Strings/zh.xaml
+++ b/SpineViewer/Resources/Strings/zh.xaml
@@ -104,6 +104,7 @@
缩放
旋转(角度)
最大帧率
+ 播放速度
仅渲染选中
显示坐标轴
背景颜色
@@ -166,6 +167,8 @@
导出时长,如果小于 0,则在导出时使用所有模型所有动画的最大时长
帧率
+ 导出速度
+ 导出速度因子, 仅影响模型的动作速度, 不影响导出时长和帧率等参数
保留最后一帧
当设置保留最后一帧时,动图会更为连贯,但是帧数可能比预期帧数多 1
@@ -184,7 +187,7 @@
编码器
FFmpeg 编码器,等价于参数 “-c:v”,例如 “libx264”、“libx265”
像素格式
- FFmpeg 像素格式,等价于参数 “-pix_fmt”,例如 “yuv420”、“yuv444”
+ FFmpeg 像素格式,等价于参数 “-pix_fmt”,例如 “yuv420p”、“yuv444p”
比特率
FFmpeg 比特率,等价于参数 “-b:v”,例如 “6K”、“2M”
滤镜
diff --git a/SpineViewer/SpineViewer.csproj b/SpineViewer/SpineViewer.csproj
index c66181d..9422452 100644
--- a/SpineViewer/SpineViewer.csproj
+++ b/SpineViewer/SpineViewer.csproj
@@ -7,7 +7,7 @@
net8.0-windows
$(SolutionDir)out
false
- 0.15.4
+ 0.15.5
WinExe
true
diff --git a/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs
index c31f8c2..bb90b00 100644
--- a/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs
+++ b/SpineViewer/ViewModels/Exporters/CustomFFmpegExporterViewModel.cs
@@ -64,6 +64,7 @@ namespace SpineViewer.ViewModels.Exporters
{
BackgroundColor = new(_backgroundColor.R, _backgroundColor.G, _backgroundColor.B, _backgroundColor.A),
Fps = _fps,
+ Speed = _speed,
KeepLast = _keepLast,
Format = _format,
Codec = _codec,
diff --git a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs
index 37471ff..5af1e78 100644
--- a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs
+++ b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs
@@ -54,6 +54,7 @@ namespace SpineViewer.ViewModels.Exporters
{
BackgroundColor = new(_backgroundColor.R, _backgroundColor.G, _backgroundColor.B, _backgroundColor.A),
Fps = _fps,
+ Speed = _speed,
KeepLast = _keepLast,
Format = _format,
Loop = _loop,
diff --git a/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs
index fab8999..ac91c91 100644
--- a/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs
+++ b/SpineViewer/ViewModels/Exporters/FrameSequenceExporterViewModel.cs
@@ -34,6 +34,7 @@ namespace SpineViewer.ViewModels.Exporters
{
BackgroundColor = new(_backgroundColor.R, _backgroundColor.G, _backgroundColor.B, _backgroundColor.A),
Fps = _fps,
+ Speed = _speed,
KeepLast = _keepLast
};
diff --git a/SpineViewer/ViewModels/Exporters/VideoExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/VideoExporterViewModel.cs
index 43353ec..460ef92 100644
--- a/SpineViewer/ViewModels/Exporters/VideoExporterViewModel.cs
+++ b/SpineViewer/ViewModels/Exporters/VideoExporterViewModel.cs
@@ -16,6 +16,9 @@ namespace SpineViewer.ViewModels.Exporters
public uint Fps { get => _fps; set => SetProperty(ref _fps, Math.Max(1, value)); }
protected uint _fps = 30;
+ public float Speed { get => _speed; set => SetProperty(ref _speed, Math.Clamp(value, 0.001f, 1000f)); }
+ protected float _speed = 1f;
+
public bool KeepLast { get => _keepLast; set => SetProperty(ref _keepLast, value); }
protected bool _keepLast = true;
}
diff --git a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs
index 54a7d60..1b2d58a 100644
--- a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs
+++ b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs
@@ -140,6 +140,13 @@ namespace SpineViewer.ViewModels.MainWindow
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = value);
}
+ public float Speed
+ {
+ get => _speed;
+ set => SetProperty(ref _speed, Math.Clamp(value, 0.01f, 100f));
+ }
+ private float _speed = 1f;
+
public bool ShowAxis
{
get => _showAxis;
@@ -193,15 +200,15 @@ namespace SpineViewer.ViewModels.MainWindow
});
private RelayCommand? _cmd_Restart;
- public RelayCommand Cmd_ForwardStep => _cmd_ForwardStep ??= new(() =>
- {
- lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 1f / _renderer.MaxFps : 0.001f;
+ public RelayCommand Cmd_ForwardStep => _cmd_ForwardStep ??= new(() =>
+ {
+ lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 1f / _renderer.MaxFps : 0.001f;
});
private RelayCommand? _cmd_ForwardStep;
public RelayCommand Cmd_ForwardFast => _cmd_ForwardFast ??= new(() =>
- {
- lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 10f / _renderer.MaxFps : 0.01f;
+ {
+ lock (_forwardDeltaLock) _forwardDelta += _renderer.MaxFps > 0 ? 10f / _renderer.MaxFps : 0.01f;
});
private RelayCommand? _cmd_ForwardFast;
@@ -390,7 +397,7 @@ namespace SpineViewer.ViewModels.MainWindow
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
sp.Update(0); // 避免物理效果出现问题
- sp.Update(delta);
+ sp.Update(delta * _speed);
// 为选中对象绘制一个半透明背景
if (sp.IsSelected)
@@ -426,7 +433,7 @@ namespace SpineViewer.ViewModels.MainWindow
}
public RendererWorkspaceConfigModel WorkspaceConfig
- {
+ {
// TODO: 背景图片
get
{
@@ -441,23 +448,25 @@ namespace SpineViewer.ViewModels.MainWindow
FlipX = FlipX,
FlipY = FlipY,
MaxFps = MaxFps,
+ Speed = Speed,
ShowAxis = ShowAxis,
BackgroundColor = BackgroundColor,
};
}
set
{
- ResolutionX = value.ResolutionX;
- ResolutionY = value.ResolutionY;
- CenterX = value.CenterX;
- CenterY = value.CenterY;
- Zoom = value.Zoom;
- Rotation = value.Rotation;
- FlipX = value.FlipX;
- FlipY = value.FlipY;
- MaxFps = value.MaxFps;
- ShowAxis = value.ShowAxis;
- BackgroundColor = value.BackgroundColor;
+ ResolutionX = value.ResolutionX;
+ ResolutionY = value.ResolutionY;
+ CenterX = value.CenterX;
+ CenterY = value.CenterY;
+ Zoom = value.Zoom;
+ Rotation = value.Rotation;
+ FlipX = value.FlipX;
+ FlipY = value.FlipY;
+ MaxFps = value.MaxFps;
+ Speed = value.Speed;
+ ShowAxis = value.ShowAxis;
+ BackgroundColor = value.BackgroundColor;
}
}
}
diff --git a/SpineViewer/Views/ExporterDialogs/CustomFFmpegExporterDialog.xaml b/SpineViewer/Views/ExporterDialogs/CustomFFmpegExporterDialog.xaml
index 66a0a53..0cf7b22 100644
--- a/SpineViewer/Views/ExporterDialogs/CustomFFmpegExporterDialog.xaml
+++ b/SpineViewer/Views/ExporterDialogs/CustomFFmpegExporterDialog.xaml
@@ -67,6 +67,7 @@
+
@@ -122,38 +123,42 @@
-
-
-
+
+
+
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
@@ -121,31 +122,35 @@
-
-
-
+
+
+
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/SpineViewer/Views/ExporterDialogs/FrameSequenceExporterDialog.xaml b/SpineViewer/Views/ExporterDialogs/FrameSequenceExporterDialog.xaml
index 42d6811..9310b6b 100644
--- a/SpineViewer/Views/ExporterDialogs/FrameSequenceExporterDialog.xaml
+++ b/SpineViewer/Views/ExporterDialogs/FrameSequenceExporterDialog.xaml
@@ -62,6 +62,7 @@
+
@@ -116,9 +117,13 @@
+
+
+
+
-
-
+
+
diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml
index b9db125..90625c3 100644
--- a/SpineViewer/Views/MainWindow.xaml
+++ b/SpineViewer/Views/MainWindow.xaml
@@ -693,6 +693,7 @@
+
@@ -731,13 +732,17 @@
+
+
+
+
-
-
+
+
-
-
+
+