mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-01 20:53:45 +02:00
Update VRC image metadata parsing
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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 = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user