feat: start handling metadata

This commit is contained in:
Anna 2024-02-17 21:36:57 -05:00
parent 33068f3ce8
commit c0f12a4734
Signed by: anna
GPG Key ID: D0943384CD9F87D1
7 changed files with 222 additions and 13 deletions

View File

@ -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);
}
}

View File

@ -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),
};

View File

@ -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; }

View File

@ -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>

118
ScreenshotMetadata.cs Normal file
View File

@ -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;
}

View File

@ -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();

View File

@ -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",