Update VRC image metadata parsing

This commit is contained in:
Natsumi
2025-08-01 16:55:59 +12:00
parent 3c21d88efa
commit 592983673d
5 changed files with 47 additions and 23 deletions
+1 -1
View File
@@ -43,7 +43,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="6.0.1" /> <PackageReference Include="NLog" Version="6.0.2" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" /> <PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" /> <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup> </ItemGroup>
+30 -13
View File
@@ -170,15 +170,15 @@ namespace VRCX
{ {
var xmlString = metadataString.Substring(xmlIndex); var xmlString = metadataString.Substring(xmlIndex);
// everything after index // everything after index
var result = ParseVRCPrint(xmlString.Substring(xmlIndex - 7)); var result = ParseVRCImage(xmlString);
result.SourceFile = path; result.SourceFile = path;
return result; return result;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error(ex, "Failed to parse VRCPrint XML metadata for file '{0}'", path); Logger.Error(ex, "Failed to parse VRC image XML metadata for file '{0}'", path);
return ScreenshotMetadata.JustError(path, "Failed to parse VRCPrint metadata."); return ScreenshotMetadata.JustError(path, "Failed to parse VRC image metadata.");
} }
} }
@@ -204,7 +204,7 @@ namespace VRCX
} }
} }
public static ScreenshotMetadata ParseVRCPrint(string xmlString) public static ScreenshotMetadata ParseVRCImage(string xmlString)
{ {
var doc = new XmlDocument(); var doc = new XmlDocument();
doc.LoadXml(xmlString); doc.LoadXml(xmlString);
@@ -217,10 +217,22 @@ namespace VRCX
nsManager.AddNamespace("dc", "http://purl.org/dc/elements/1.1/"); nsManager.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");
nsManager.AddNamespace("vrc", "http://ns.vrchat.com/vrc/1.0/"); nsManager.AddNamespace("vrc", "http://ns.vrchat.com/vrc/1.0/");
var creatorTool = root.SelectSingleNode("//xmp:CreatorTool", nsManager)?.InnerText; var creatorTool = root.SelectSingleNode("//xmp:CreatorTool", nsManager)?.InnerText;
var authorId = root.SelectSingleNode("//xmp:Author", nsManager)?.InnerText; var authorName = root.SelectSingleNode("//xmp:Author", nsManager)?.InnerText; // legacy, it was authorId
var dateTime = root.SelectSingleNode("//tiff:DateTime", nsManager)?.InnerText; var dateTime = root.SelectSingleNode("//tiff:DateTime", nsManager)?.InnerText;
var note = root.SelectSingleNode("//dc:title/rdf:Alt/rdf:li", nsManager)?.InnerText; var note = root.SelectSingleNode("//dc:title/rdf:Alt/rdf:li", nsManager)?.InnerText;
var worldId = root.SelectSingleNode("//vrc:World", nsManager)?.InnerText; var worldId = root.SelectSingleNode("//vrc:WorldID", nsManager)?.InnerText;
var worldDisplayName = root.SelectSingleNode("//vrc:WorldDisplayName", nsManager)?.InnerText; // new, 01.08.2025
var authorId = root.SelectSingleNode("//vrc:AuthorID", nsManager)?.InnerText; // new, 01.08.2025
if (string.IsNullOrEmpty(worldId))
worldId = root.SelectSingleNode("//vrc:World", nsManager)?.InnerText; // legacy, it's gone now
if (string.IsNullOrEmpty(authorId))
{
// If authorId is not set, we assume legacy metadata format where authorName is used as authorId.
authorId = authorName;
authorName = null;
}
return new ScreenshotMetadata return new ScreenshotMetadata
{ {
@@ -229,13 +241,13 @@ namespace VRCX
Author = new ScreenshotMetadata.AuthorDetail Author = new ScreenshotMetadata.AuthorDetail
{ {
Id = authorId, Id = authorId,
DisplayName = null DisplayName = authorName
}, },
World = new ScreenshotMetadata.WorldDetail World = new ScreenshotMetadata.WorldDetail
{ {
Id = worldId, Id = worldId,
InstanceId = worldId, InstanceId = worldId,
Name = null Name = worldDisplayName
}, },
Timestamp = DateTime.TryParse(dateTime, out var dt) ? dt : null, Timestamp = DateTime.TryParse(dateTime, out var dt) ? dt : null,
Note = note Note = note
@@ -254,16 +266,21 @@ namespace VRCX
/// </returns> /// </returns>
public static bool WritePNGDescription(string path, string text) public static bool WritePNGDescription(string path, string text)
{ {
if (!File.Exists(path) || !IsPNGFile(path)) return false; if (!File.Exists(path) || !IsPNGFile(path))
return false;
var png = File.ReadAllBytes(path); var png = File.ReadAllBytes(path);
var newChunkIndex = FindEndOfChunk(png, "IHDR"); var newChunkIndex = FindEndOfChunk(png, "IHDR");
if (newChunkIndex == -1) return false; if (newChunkIndex == -1)
return false;
// If this file already has a text chunk, chances are it got logged twice for some reason. Stop. // If this file already has a text chunk, chances are it got logged twice for some reason. Stop.
// var existingiTXt = FindChunkIndex(png, "iTXt"); var screenShotMetadata = GetScreenshotMetadata(path);
// if (existingiTXt != -1) return false; if (screenShotMetadata != null && screenShotMetadata.Application == "VRCX")
{
Logger.Error("Screenshot file '{0}' already has VRCX metadata", path);
return false;
}
var newChunk = new PNGChunk("iTXt"); var newChunk = new PNGChunk("iTXt");
newChunk.InitializeTextChunk("Description", text); newChunk.InitializeTextChunk("Description", text);
+2 -2
View File
@@ -96,10 +96,10 @@
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" /> <PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="6.0.1" /> <PackageReference Include="NLog" Version="6.0.2" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" /> <PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" /> <PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" /> <PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" /> <PackageReference Include="System.Data.SQLite" Version="1.0.119" />
+3 -3
View File
@@ -1,4 +1,4 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<BaseIntermediateOutputPath>obj1\</BaseIntermediateOutputPath> <BaseIntermediateOutputPath>obj1\</BaseIntermediateOutputPath>
<OutputPath>..\build\Electron\</OutputPath> <OutputPath>..\build\Electron\</OutputPath>
@@ -98,10 +98,10 @@
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" /> <PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="6.0.1" /> <PackageReference Include="NLog" Version="6.0.2" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" /> <PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
<PackageReference Include="SharpDX.Mathematics" Version="4.2.0" /> <PackageReference Include="SharpDX.Mathematics" Version="4.2.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" /> <PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" /> <PackageReference Include="System.Data.SQLite" Version="1.0.119" />
@@ -436,8 +436,9 @@
D.searchIndex = searchIndex; D.searchIndex = searchIndex;
} }
function getAndDisplayScreenshot(path, needsCarouselFiles = true) { async function getAndDisplayScreenshot(path, needsCarouselFiles = true) {
AppApi.GetScreenshotMetadata(path).then((metadata) => displayScreenshotMetadata(metadata, needsCarouselFiles)); const metadata = await AppApi.GetScreenshotMetadata(path);
displayScreenshotMetadata(metadata, needsCarouselFiles);
} }
/** /**
@@ -446,13 +447,19 @@
* Example: {"error":"Invalid file selected. Please select a valid VRChat screenshot."} * Example: {"error":"Invalid file selected. Please select a valid VRChat screenshot."}
* See docs/screenshotMetadata.json for schema * See docs/screenshotMetadata.json for schema
* @param {string} metadata - JSON string grabbed from PNG file * @param {string} metadata - JSON string grabbed from PNG file
* @param {string} needsCarouselFiles - Whether or not to get the last/next files for the carousel * @param {boolean} needsCarouselFiles - Whether or not to get the last/next files for the carousel
* @returns {void} * @returns {Promise<void>}
*/ */
async function displayScreenshotMetadata(json, needsCarouselFiles = true) { async function displayScreenshotMetadata(json, needsCarouselFiles = true) {
let time; let time;
let date; let date;
const D = props.screenshotMetadataDialog; const D = props.screenshotMetadataDialog;
D.metadata.author = {};
D.metadata.world = {};
D.metadata.players = [];
D.metadata.creationDate = '';
D.metadata.application = '';
const metadata = JSON.parse(json); const metadata = JSON.parse(json);
if (!metadata?.sourceFile) { if (!metadata?.sourceFile) {
D.metadata = {}; D.metadata = {};