Compare commits

..

18 Commits

Author SHA1 Message Date
ww-rm
df798b481d Merge pull request #65 from ww-rm/dev/wpf
Dev/wpf
2025-07-11 23:32:05 +08:00
ww-rm
578a9ad3f3 更新至v0.15.4 2025-07-11 23:31:10 +08:00
ww-rm
b765b5f7ea update changelog 2025-07-11 23:31:05 +08:00
ww-rm
f1c013bd82 增加webp格式无损参数 2025-07-11 23:28:36 +08:00
ww-rm
65c1012205 更改提示文本 2025-07-11 16:56:01 +08:00
ww-rm
f1cd9e25e5 修复使用FFmpeg导出时的卡死问题 2025-07-11 16:52:50 +08:00
ww-rm
b2861ffb93 Merge pull request #62 from ww-rm/dev/wpf
Dev/wpf
2025-06-29 19:51:55 +09:00
ww-rm
b01d112d63 Update CHANGELOG.md 2025-06-29 19:51:20 +09:00
ww-rm
58b13d00c1 Update Spine.csproj 2025-06-29 19:49:58 +09:00
ww-rm
0f5539ad41 Update SpineViewer.csproj 2025-06-29 19:49:29 +09:00
ww-rm
6d18ce882c Update SpineViewer.csproj 2025-06-29 19:46:04 +09:00
ww-rm
e1ea95c195 Merge pull request #61 from xiantuan/dev/wpf
Update SpineObject.cs add .skel.bytes Support
2025-06-29 11:47:51 +09:00
饭团
48ee61d1c6 Update SpineObject.cs add .skel.bytes Support
add .skel.bytes Support #58
2025-06-28 20:40:56 +08:00
ww-rm
3ca22c3f00 Merge pull request #55 from ww-rm/dev/wpf
Dev/wpf
2025-06-19 23:22:35 +08:00
ww-rm
593d9b771c 更新至v0.15.2 2025-06-19 23:21:43 +08:00
ww-rm
35f9357355 update changelog 2025-06-19 23:20:20 +08:00
ww-rm
427d18df4c 工作区保存参数增加浏览路径 2025-06-19 23:19:04 +08:00
ww-rm
7d1a1f1aeb 修复首选项文件不存在时的提示信息 2025-06-19 22:54:12 +08:00
21 changed files with 92 additions and 26 deletions

View File

@@ -1,5 +1,23 @@
# CHANGELOG
## v0.15.4
- 修复导出时可能的卡死问题
- 增加 webp 格式无损压缩参数
## v0.15.3
- 增加 skel.bytes 后缀识别
## v0.15.2
- 修复首选项文件读取为空时的提示信息
- 工作区参数增加浏览路径
## v0.15.1
- 新版本正式发布
## v0.15.0
### 项目分支变更

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.1</Version>
<Version>0.15.4</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.1</Version>
<Version>0.15.4</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>

View File

@@ -51,6 +51,12 @@ namespace Spine.Exporters
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
private int _quality = 75;
/// <summary>
/// 无损压缩 (Webp)
/// </summary>
public bool Lossless { get => _lossless; set => _lossless = value; }
private bool _lossless = false;
/// <summary>
/// CRF
/// </summary>
@@ -62,7 +68,8 @@ namespace Spine.Exporters
/// </summary>
protected override SFMLImageVideoFrame GetFrame(SpineObject[] spines)
{
// XXX: 不知道为什么用 FFmpeg 必须临时创建 RenderTexture, 否则无法正常渲染
// 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);
@@ -112,7 +119,7 @@ namespace Spine.Exporters
private void SetWebpOptions(FFMpegArgumentOptions options)
{
var customArgs = $"-vf unpremultiply=inplace=1 -quality {_quality} -loop {(_loop ? 0 : 1)}";
var customArgs = $"-vf unpremultiply=inplace=1 -quality {_quality} -loop {(_loop ? 0 : 1)} -lossless {(_lossless ? 1 : 0)}";
options.ForceFormat("webp").WithVideoCodec("libwebp_anim").ForcePixelFormat("yuva420p")
.WithCustomArgument(customArgs);
}

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.1</Version>
<Version>0.15.4</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -20,6 +20,7 @@ namespace Spine
public static readonly FrozenDictionary<string, string> PossibleSuffixMapping = new Dictionary<string, string>()
{
[".skel"] = ".atlas",
[".skel.bytes"] = ".atlas.txt",
[".json"] = ".atlas",
}.ToFrozenDictionary();

View File

@@ -12,6 +12,7 @@ namespace SpineViewer.Models
{
public class WorkspaceModel
{
public string? ExploringDirectory { get; set; }
public RendererWorkspaceConfigModel RendererConfig { get; set; } = new();
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects { get; set; } = [];
}

View File

@@ -174,6 +174,8 @@
<s:String x:Key="Str_LoopPlayTooltip">Loop animation; only effective for GIF/WebP formats</s:String>
<s:String x:Key="Str_QualityParameter">Quality Parameter</s:String>
<s:String x:Key="Str_QualityParameterTooltip">Range 0100; higher is better; only for WebP format</s:String>
<s:String x:Key="Str_LosslessParam">Lossless Compression</s:String>
<s:String x:Key="Str_LosslessParamTooltip">Lossless compression. Ignores the quality parameter and only applies to WebP format.</s:String>
<s:String x:Key="Str_CrfParameter">CRF Parameter</s:String>
<s:String x:Key="Str_CrfParameterTooltip">Range 063; lower is higher quality; only for MP4/WebM/MKV formats</s:String>

View File

@@ -174,6 +174,8 @@
<s:String x:Key="Str_LoopPlayTooltip">アニメーションをループ再生するか。Gif/Webp形式のみ有効です</s:String>
<s:String x:Key="Str_QualityParameter">品質パラメーター</s:String>
<s:String x:Key="Str_QualityParameterTooltip">品質パラメーター。値の範囲は0-100。数値が大きいほど品質が高くなります。Webp形式のみ有効です</s:String>
<s:String x:Key="Str_LosslessParam">可逆圧縮</s:String>
<s:String x:Key="Str_LosslessParamTooltip">可逆圧縮を行います。品質パラメータは無視され、WebP形式にのみ適用されます。</s:String>
<s:String x:Key="Str_CrfParameter">CRFパラメーター</s:String>
<s:String x:Key="Str_CrfParameterTooltip">CRFパラメーター。値の範囲は0-63。数値が小さいほど品質が高くなります。Mp4/Webm/Mkv形式のみ有効です</s:String>

View File

@@ -173,7 +173,9 @@
<s:String x:Key="Str_LoopPlay">循环播放</s:String>
<s:String x:Key="Str_LoopPlayTooltip">动图是否循环播放,仅对 Gif/Webp 格式生效</s:String>
<s:String x:Key="Str_QualityParameter">质量参数</s:String>
<s:String x:Key="Str_QualityParameterTooltip">质量参数,取值范围 0-100越高质量越好 仅对 Webp 格式生效</s:String>
<s:String x:Key="Str_QualityParameterTooltip">质量参数,取值范围 0-100越高质量越好, 仅对 Webp 格式生效</s:String>
<s:String x:Key="Str_LosslessParam">无损压缩</s:String>
<s:String x:Key="Str_LosslessParamTooltip">无损压缩, 会忽略质量参数, 仅对 Webp 格式生效</s:String>
<s:String x:Key="Str_CrfParameter">CRF 参数</s:String>
<s:String x:Key="Str_CrfParameterTooltip">CRF 参数,取值范围 0-63越小质量越高仅对 Mp4/Webm/Mkv 格式生效</s:String>

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.1</Version>
<Version>0.15.4</Version>
<OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF>
</PropertyGroup>

View File

@@ -40,12 +40,15 @@ namespace SpineViewer.Utils
/// <summary>
/// 从文件反序列对象, 不会抛出异常
/// </summary>
public static bool Deserialize<T>(string path, out T obj)
public static bool Deserialize<T>(string path, out T obj, bool quietForNotExist = false)
{
if (!File.Exists(path))
{
_logger.Error("Json file {0} not found", path);
MessagePopupService.Error($"Json file {path} not found");
if (!quietForNotExist)
{
_logger.Error("Json file {0} not found", path);
MessagePopupService.Error($"Json file {path} not found");
}
}
else
{

View File

@@ -142,7 +142,6 @@ namespace SpineViewer.ViewModels.Exporters
{
if (!Export_CanExecute(args)) return;
Export(args.Cast<SpineObjectModel>().ToArray());
// XXX: 导出途中应该停掉渲染好一些, 让性能专注在导出上
}
private bool Export_CanExecute(IList? args)

View File

@@ -83,6 +83,8 @@ namespace SpineViewer.ViewModels.Exporters
exporter.Rotation = view.Rotation;
}
_vmMain.SFMLRendererViewModel.StopRender();
if (_exportSingle)
{
var filename = $"ffmpeg_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}{FormatSuffix}";
@@ -168,6 +170,8 @@ namespace SpineViewer.ViewModels.Exporters
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
}
_vmMain.SFMLRendererViewModel.StartRender();
}
}
}

View File

@@ -29,6 +29,9 @@ namespace SpineViewer.ViewModels.Exporters
public int Quality { get => _quality; set => SetProperty(ref _quality, Math.Clamp(value, 0, 100)); }
protected int _quality = 75;
public bool Lossless { get => _lossless; set => SetProperty(ref _lossless, value); }
protected bool _lossless = false;
public int Crf { get => _crf; set => SetProperty(ref _crf, Math.Clamp(value, 0, 63)); }
protected int _crf = 23;
@@ -55,6 +58,7 @@ namespace SpineViewer.ViewModels.Exporters
Format = _format,
Loop = _loop,
Quality = _quality,
Lossless = _lossless,
Crf = _crf
};
@@ -68,6 +72,10 @@ namespace SpineViewer.ViewModels.Exporters
exporter.Rotation = view.Rotation;
}
// BUG: FFmpeg 导出时对 RenderTexture 的频繁资源申请释放似乎使 SFML 库内部出现问题, 会卡死所有使用 SFML 的地方, 包括渲染线程
// 所以临时把渲染线程停掉, 只让此处使用 SFML 资源, 这个问题或许和多个线程同时使用渲染资源有关
_vmMain.SFMLRendererViewModel.StopRender();
if (_exportSingle)
{
var filename = $"video_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}{FormatSuffix}";
@@ -153,6 +161,8 @@ namespace SpineViewer.ViewModels.Exporters
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
}
_vmMain.SFMLRendererViewModel.StartRender();
}
}
}

View File

@@ -47,6 +47,8 @@ namespace SpineViewer.ViewModels.Exporters
exporter.Rotation = view.Rotation;
}
_vmMain.SFMLRendererViewModel.StopRender();
if (_exportSingle)
{
var folderName = $"frames_{timestamp}_{Guid.NewGuid().ToString()[..6]}_{_fps}";
@@ -132,6 +134,8 @@ namespace SpineViewer.ViewModels.Exporters
}
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
}
_vmMain.SFMLRendererViewModel.StartRender();
}
}
}

View File

@@ -39,11 +39,6 @@ namespace SpineViewer.ViewModels.MainWindow
private readonly MainWindowViewModel _vmMain;
/// <summary>
/// 当前目录路径
/// </summary>
private string? _currentDirectory;
/// <summary>
/// 当前目录下文件项缓存
/// </summary>
@@ -54,12 +49,26 @@ namespace SpineViewer.ViewModels.MainWindow
_vmMain = vmMain;
}
/// <summary>
/// 当前目录路径
/// </summary>
public string? CurrentDirectory
{
get => string.IsNullOrWhiteSpace(_currentDirectory) ? null : _currentDirectory;
set
{
if (!SetProperty(ref _currentDirectory, value)) return;
RefreshItems();
}
}
private string? _currentDirectory;
/// <summary>
/// 筛选字符串
/// </summary>
public string? FilterString
{
get => _filterString;
get => string.IsNullOrWhiteSpace(_filterString) ? null : _filterString;
set
{
if (!SetProperty(ref _filterString, value)) return;
@@ -95,10 +104,7 @@ namespace SpineViewer.ViewModels.MainWindow
public RelayCommand Cmd_ChangeCurrentDirectory => _cmd_ChangeCurrentDirectory ??= new(() =>
{
if (DialogService.ShowOpenFolderDialog(out var selectedPath))
{
_currentDirectory = selectedPath;
RefreshItems();
}
CurrentDirectory = selectedPath;
});
private RelayCommand? _cmd_ChangeCurrentDirectory;

View File

@@ -118,12 +118,14 @@ namespace SpineViewer.ViewModels.MainWindow
{
return new()
{
ExploringDirectory = _explorerListViewModel.CurrentDirectory,
RendererConfig = _sfmlRendererViewModel.WorkspaceConfig,
LoadedSpineObjects = _spineObjectListViewModel.LoadedSpineObjects
};
}
set
{
_explorerListViewModel.CurrentDirectory = value.ExploringDirectory;
_sfmlRendererViewModel.WorkspaceConfig = value.RendererConfig;
_spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects;
}

View File

@@ -63,7 +63,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// </summary>
public void LoadPreference()
{
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj))
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
Preference = obj;
}

View File

@@ -101,7 +101,7 @@ namespace SpineViewer.ViewModels.MainWindow
private void AddSpineObject_Execute()
{
MessagePopupService.Info("Not Implemented, try next version :)");
MessagePopupService.Info("Not Implemented, please drag files into here or add them from clipboard :)");
}
/// <summary>

View File

@@ -9,7 +9,7 @@
mc:Ignorable="d"
Title="{DynamicResource Str_FFmpegVideoExporterTitle}"
Width="450"
Height="550"
Height="580"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner">
<DockPanel>
@@ -66,6 +66,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 水平分辨率 -->
@@ -138,9 +139,13 @@
<Label Grid.Row="15" Grid.Column="0" Content="{DynamicResource Str_QualityParameter}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
<TextBox Grid.Row="15" Grid.Column="1" Text="{Binding Quality}" ToolTip="{DynamicResource Str_QualityParameterTooltip}"/>
<!-- 无损压缩 -->
<Label Grid.Row="16" Grid.Column="0" Content="{DynamicResource Str_LosslessParam}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
<ToggleButton Grid.Row="16" Grid.Column="1" IsChecked="{Binding Lossless}" ToolTip="{DynamicResource Str_LosslessParamTooltip}"/>
<!-- CRF 参数 -->
<Label Grid.Row="16" Grid.Column="0" Content="{DynamicResource Str_CrfParameter}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
<TextBox Grid.Row="16" Grid.Column="1" Text="{Binding Crf}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
<Label Grid.Row="17" Grid.Column="0" Content="{DynamicResource Str_CrfParameter}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
<TextBox Grid.Row="17" Grid.Column="1" Text="{Binding Crf}" ToolTip="{DynamicResource Str_CrfParameterTooltip}"/>
</Grid>
</ScrollViewer>