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

View File

@@ -43,7 +43,7 @@
</PropertyGroup>
<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="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

View File

@@ -170,15 +170,15 @@ namespace VRCX
{
var xmlString = metadataString.Substring(xmlIndex);
// everything after index
var result = ParseVRCPrint(xmlString.Substring(xmlIndex - 7));
var result = ParseVRCImage(xmlString);
result.SourceFile = path;
return result;
}
catch (Exception ex)
{
Logger.Error(ex, "Failed to parse VRCPrint XML metadata for file '{0}'", path);
return ScreenshotMetadata.JustError(path, "Failed to parse VRCPrint metadata.");
Logger.Error(ex, "Failed to parse VRC image XML metadata for file '{0}'", path);
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();
doc.LoadXml(xmlString);
@@ -217,10 +217,22 @@ namespace VRCX
nsManager.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");
nsManager.AddNamespace("vrc", "http://ns.vrchat.com/vrc/1.0/");
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 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
{
@@ -229,13 +241,13 @@ namespace VRCX
Author = new ScreenshotMetadata.AuthorDetail
{
Id = authorId,
DisplayName = null
DisplayName = authorName
},
World = new ScreenshotMetadata.WorldDetail
{
Id = worldId,
InstanceId = worldId,
Name = null
Name = worldDisplayName
},
Timestamp = DateTime.TryParse(dateTime, out var dt) ? dt : null,
Note = note
@@ -254,16 +266,21 @@ namespace VRCX
/// </returns>
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 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.
// var existingiTXt = FindChunkIndex(png, "iTXt");
// if (existingiTXt != -1) return false;
var screenShotMetadata = GetScreenshotMetadata(path);
if (screenShotMetadata != null && screenShotMetadata.Application == "VRCX")
{
Logger.Error("Screenshot file '{0}' already has VRCX metadata", path);
return false;
}
var newChunk = new PNGChunk("iTXt");
newChunk.InitializeTextChunk("Description", text);

View File

@@ -96,10 +96,10 @@
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.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.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="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />

View File

@@ -1,4 +1,4 @@
<Project>
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>obj1\</BaseIntermediateOutputPath>
<OutputPath>..\build\Electron\</OutputPath>
@@ -98,10 +98,10 @@
<PackageReference Include="DiscordRichPresence" Version="1.3.0.28" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.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.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="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />

View File

@@ -436,8 +436,9 @@
D.searchIndex = searchIndex;
}
function getAndDisplayScreenshot(path, needsCarouselFiles = true) {
AppApi.GetScreenshotMetadata(path).then((metadata) => displayScreenshotMetadata(metadata, needsCarouselFiles));
async function getAndDisplayScreenshot(path, needsCarouselFiles = true) {
const metadata = await AppApi.GetScreenshotMetadata(path);
displayScreenshotMetadata(metadata, needsCarouselFiles);
}
/**
@@ -446,13 +447,19 @@
* Example: {"error":"Invalid file selected. Please select a valid VRChat screenshot."}
* See docs/screenshotMetadata.json for schema
* @param {string} metadata - JSON string grabbed from PNG file
* @param {string} needsCarouselFiles - Whether or not to get the last/next files for the carousel
* @returns {void}
* @param {boolean} needsCarouselFiles - Whether or not to get the last/next files for the carousel
* @returns {Promise<void>}
*/
async function displayScreenshotMetadata(json, needsCarouselFiles = true) {
let time;
let date;
const D = props.screenshotMetadataDialog;
D.metadata.author = {};
D.metadata.world = {};
D.metadata.players = [];
D.metadata.creationDate = '';
D.metadata.application = '';
const metadata = JSON.parse(json);
if (!metadata?.sourceFile) {
D.metadata = {};