feat: start handling metadata
This commit is contained in:
parent
33068f3ce8
commit
c0f12a4734
38
Command.cs
38
Command.cs
|
@ -1,5 +1,8 @@
|
|||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using Dalamud.Game.Command;
|
||||
using Newtonsoft.Json;
|
||||
using WebP.Net;
|
||||
|
||||
namespace Screenie;
|
||||
|
||||
|
@ -26,6 +29,7 @@ internal class Command : IDisposable {
|
|||
private void OnCommand(string command, string arguments) {
|
||||
if (arguments == "config") {
|
||||
this.Plugin.Ui.Visible ^= true;
|
||||
return;
|
||||
}
|
||||
|
||||
using var bitmap = Photographer.Capture();
|
||||
|
@ -33,16 +37,36 @@ internal class Command : IDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
var encoder = GetEncoder(this.Plugin.Config.SaveFormat.ToImageFormat());
|
||||
var saveAs = this.Plugin.Config.SaveFormat;
|
||||
if (saveAs.ToImageFormat() is { } format) {
|
||||
this.SaveNative(format, bitmap);
|
||||
} else if (saveAs is Format.WebpLossless or Format.WebpLossy) {
|
||||
using var webp = new WebPObject(bitmap);
|
||||
var bytes = saveAs == Format.WebpLossless
|
||||
? webp.GetWebPLossless()
|
||||
: webp.GetWebPLossy(this.Plugin.Config.SaveFormatData);
|
||||
using var stream = this.OpenFile("webp");
|
||||
stream.Write(bytes);
|
||||
}
|
||||
|
||||
var meta = ScreenshotMetadata.Capture(this.Plugin);
|
||||
var json = JsonConvert.SerializeObject(meta, Formatting.Indented);
|
||||
Plugin.Log.Info(json);
|
||||
|
||||
this.Plugin.ChatGui.Print("Screenshot saved.");
|
||||
}
|
||||
|
||||
private void SaveNative(ImageFormat format, Image bitmap) {
|
||||
var encoder = GetEncoder(format);
|
||||
if (encoder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
|
||||
var (param, ext) = this.Plugin.Config.SaveFormat switch {
|
||||
Format.Jpg => (Encoder.Quality, "jpg"),
|
||||
Format.Webp => (Encoder.Quality, "webp"),
|
||||
Format.Png => (Encoder.Compression, "png"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(this.Plugin.Config.SaveFormat), this.Plugin.Config.SaveFormat, null),
|
||||
_ => throw new ArgumentException("not a native-save format", nameof(format)),
|
||||
};
|
||||
|
||||
var parameters = new EncoderParameters(1) {
|
||||
|
@ -51,13 +75,17 @@ internal class Command : IDisposable {
|
|||
],
|
||||
};
|
||||
|
||||
using var stream = this.OpenFile(ext);
|
||||
bitmap.Save(stream, encoder, parameters);
|
||||
}
|
||||
|
||||
private FileStream OpenFile(string ext) {
|
||||
Directory.CreateDirectory(this.Plugin.Config.SaveDirectory);
|
||||
|
||||
var path = Path.ChangeExtension(
|
||||
Path.Join(this.Plugin.Config.SaveDirectory, "screenie"),
|
||||
ext
|
||||
);
|
||||
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||
bitmap.Save(stream, encoder, parameters);
|
||||
return new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,17 @@ public class Configuration : IPluginConfiguration {
|
|||
|
||||
public enum Format {
|
||||
Png,
|
||||
Webp,
|
||||
WebpLossless,
|
||||
WebpLossy,
|
||||
Jpg,
|
||||
}
|
||||
|
||||
public static class FormatExt {
|
||||
public static ImageFormat ToImageFormat(this Format format) {
|
||||
public static ImageFormat? ToImageFormat(this Format format) {
|
||||
return format switch {
|
||||
Format.Png => ImageFormat.Png,
|
||||
Format.Webp => ImageFormat.Webp,
|
||||
Format.WebpLossless => null,
|
||||
Format.WebpLossy => null,
|
||||
Format.Jpg => ImageFormat.Jpeg,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
|
||||
};
|
||||
|
|
15
Plugin.cs
15
Plugin.cs
|
@ -14,12 +14,27 @@ public class Plugin : IDalamudPlugin {
|
|||
[PluginService]
|
||||
internal DalamudPluginInterface Interface { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IChatGui ChatGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IClientState ClientState { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal ICommandManager CommandManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IDataManager DataManager { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IFramework Framework { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IGameGui GameGui { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IObjectTable ObjectTable { get; init; }
|
||||
|
||||
internal Configuration Config { get; }
|
||||
internal PluginUi Ui { get; }
|
||||
|
||||
|
|
|
@ -41,14 +41,24 @@
|
|||
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
|
||||
<PackageReference Include="FFXIVWeather.Lumina" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.2"/>
|
||||
<PackageReference Include="WebP_Net" Version="1.1.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVWeather.Lumina;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Screenie;
|
||||
|
||||
[Serializable]
|
||||
public class ScreenshotMetadata {
|
||||
public required string Blake3Hash;
|
||||
public required Character? ActiveCharacter;
|
||||
public required string Territory;
|
||||
public required uint TerritoryId;
|
||||
public required string World;
|
||||
public required uint WorldId;
|
||||
public required DateTime CapturedAtLocal;
|
||||
public required DateTime CapturedAtUtc;
|
||||
public required string EorzeaTime;
|
||||
public required string Weather;
|
||||
public required uint Ward;
|
||||
public required uint Plot;
|
||||
public required Character[] VisibleCharacters;
|
||||
|
||||
private const string Unknown = "Unknown";
|
||||
|
||||
private static Character GetCharacter(PlayerCharacter player) {
|
||||
return new Character {
|
||||
Name = player.Name.TextValue,
|
||||
HomeWorld = player.HomeWorld.GameData?.Name.ToDalamudString().TextValue ?? Unknown,
|
||||
HomeWorldId = player.HomeWorld.Id,
|
||||
Position = player.Position,
|
||||
Level = player.Level,
|
||||
Job = player.ClassJob.GameData?.Name.ToDalamudString().TextValue ?? Unknown,
|
||||
JobId = player.ClassJob.Id,
|
||||
};
|
||||
}
|
||||
|
||||
internal static ScreenshotMetadata Capture(Plugin plugin) {
|
||||
Character? active = null;
|
||||
World? world = null;
|
||||
if (plugin.ClientState.LocalPlayer is { } player) {
|
||||
world = plugin.DataManager.GetExcelSheet<World>()?.GetRow(player.CurrentWorld.Id);
|
||||
active = GetCharacter(player);
|
||||
}
|
||||
|
||||
var timeUtc = DateTime.UtcNow;
|
||||
var eorzea = GetEorzeaTime(timeUtc);
|
||||
var territory = plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(plugin.ClientState.TerritoryType);
|
||||
var visible = plugin.ObjectTable
|
||||
.Where(obj => obj is PlayerCharacter)
|
||||
.Cast<PlayerCharacter>()
|
||||
.Where(chara => plugin.GameGui.WorldToScreen(chara.Position, out _, out var inView) && inView)
|
||||
.Select(GetCharacter)
|
||||
.ToArray();
|
||||
|
||||
var (weather, _) = new FFXIVWeatherLuminaService(plugin.DataManager.GameData)
|
||||
.GetCurrentWeather(plugin.ClientState.TerritoryType);
|
||||
|
||||
return new ScreenshotMetadata {
|
||||
Blake3Hash = "",
|
||||
ActiveCharacter = active,
|
||||
Territory = territory?.Name.ToDalamudString().TextValue ?? Unknown,
|
||||
TerritoryId = plugin.ClientState.TerritoryType,
|
||||
World = world?.Name.ToDalamudString().TextValue ?? Unknown,
|
||||
WorldId = world?.RowId ?? 0,
|
||||
CapturedAtLocal = timeUtc.ToLocalTime(),
|
||||
CapturedAtUtc = timeUtc,
|
||||
EorzeaTime = $"{eorzea.Hour:00}:{eorzea.Minute:00}",
|
||||
Weather = weather?.Name.ToDalamudString().TextValue ?? Unknown,
|
||||
Ward = 0, // TODO
|
||||
Plot = 0, // TODO
|
||||
VisibleCharacters = visible,
|
||||
};
|
||||
}
|
||||
|
||||
private static EorzeaTime GetEorzeaTime(DateTime time) {
|
||||
const double eorzeaTimeConstant = (double) 3600 / 175;
|
||||
const double year = 33177600;
|
||||
const double month = 2764800;
|
||||
const double day = 86400;
|
||||
const double hour = 3600;
|
||||
const double minute = 60;
|
||||
const double second = 1;
|
||||
|
||||
var unix = ((DateTimeOffset) time).ToUnixTimeSeconds();
|
||||
var eorzea = (ulong) Math.Floor(unix * eorzeaTimeConstant);
|
||||
|
||||
return new EorzeaTime {
|
||||
Year = (ulong) Math.Floor(eorzea / year) + 1,
|
||||
Month = (ulong) Math.Floor(eorzea / month % 12) + 1,
|
||||
Day = (ulong) Math.Floor(eorzea / day % 32) + 1,
|
||||
Hour = (ulong) Math.Floor(eorzea / hour % 24),
|
||||
Minute = (ulong) Math.Floor(eorzea / minute % 60),
|
||||
Second = (ulong) Math.Floor(eorzea / second % 60),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Character {
|
||||
public required string Name;
|
||||
public required string HomeWorld;
|
||||
public required uint HomeWorldId;
|
||||
public required Vector3 Position;
|
||||
public required uint Level;
|
||||
public required string Job;
|
||||
public required uint JobId;
|
||||
}
|
||||
|
||||
public struct EorzeaTime {
|
||||
public required ulong Year;
|
||||
public required ulong Month;
|
||||
public required ulong Day;
|
||||
public required ulong Hour;
|
||||
public required ulong Minute;
|
||||
public required ulong Second;
|
||||
}
|
|
@ -52,6 +52,7 @@ internal class PluginUi : IDisposable {
|
|||
var anyChanged = false;
|
||||
anyChanged |= this.DrawScreenshotsFolderInput();
|
||||
|
||||
ImGui.TextUnformatted("Save format");
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.BeginCombo("##file-format", Enum.GetName(this.Plugin.Config.SaveFormat))) {
|
||||
using var endCombo = new OnDispose(ImGui.EndCombo);
|
||||
|
@ -65,14 +66,17 @@ internal class PluginUi : IDisposable {
|
|||
|
||||
var label = this.Plugin.Config.SaveFormat switch {
|
||||
Format.Jpg => "Quality",
|
||||
Format.Webp => "Quality",
|
||||
Format.WebpLossless => null,
|
||||
Format.WebpLossy => "Quality",
|
||||
Format.Png => "Compression level",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
ImGui.TextUnformatted(label);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
anyChanged |= ImGui.SliderInt("##format-data", ref this.Plugin.Config.SaveFormatData, 0, 100);
|
||||
if (label != null) {
|
||||
ImGui.TextUnformatted(label);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
anyChanged |= ImGui.SliderInt("##format-data", ref this.Plugin.Config.SaveFormatData, 0, 100);
|
||||
}
|
||||
|
||||
if (anyChanged) {
|
||||
this.Plugin.SaveConfig();
|
||||
|
|
|
@ -8,6 +8,16 @@
|
|||
"resolved": "2.1.12",
|
||||
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
|
||||
},
|
||||
"FFXIVWeather.Lumina": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.2.0, )",
|
||||
"resolved": "2.2.0",
|
||||
"contentHash": "zGYBjw7iRY3fUYuDGDaroiPES123esZ07th+H/cKsIZsfs5960B9EDjyEHxu1NlA0txKMGLbNMjINUmE4XImsg==",
|
||||
"dependencies": {
|
||||
"Lumina": "3.9.0",
|
||||
"Lumina.Excel": "6.2.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.49-beta, )",
|
||||
|
@ -30,6 +40,28 @@
|
|||
"Microsoft.Win32.SystemEvents": "8.0.0"
|
||||
}
|
||||
},
|
||||
"WebP_Net": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.1.1, )",
|
||||
"resolved": "1.1.1",
|
||||
"contentHash": "dFMBV4TXUbbIkspxa/Pb3420Qrel9AsHdthWHCwswn1D7dTa7CGb47ON10C1ERqcFaYdD0CgMUMNv2x4fJr4hg==",
|
||||
"dependencies": {
|
||||
"System.Drawing.Common": "7.0.0"
|
||||
}
|
||||
},
|
||||
"Lumina": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.9.0",
|
||||
"contentHash": "2ADC9iN8yUHXELq3IIQAK1cvi2kp53l1CDmAnrsTwBXJ9o9anIC+X6TzFRxWuRvTVI/MEMHTjwBobGwBGc3XhQ=="
|
||||
},
|
||||
"Lumina.Excel": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.2.0",
|
||||
"contentHash": "gIPr/Q4HhYDL65h/9b0srdL+Nfxk90T7aIciLnpl/QaBdc7tGRPzUmq0FU2QjErkikhrFOrb4Fp5NkQaN3DQDA==",
|
||||
"dependencies": {
|
||||
"Lumina": "3.9.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Win32.SystemEvents": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.0",
|
||||
|
|
Loading…
Reference in New Issue