Skip to content

Commit

Permalink
Add support for prints
Browse files Browse the repository at this point in the history
  • Loading branch information
Natsumi-sama committed Nov 15, 2024
1 parent 320104a commit 84e8de3
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 9 deletions.
51 changes: 51 additions & 0 deletions Dotnet/ScreenshotMetadata/ScreenshotHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Numerics;
using System.Text;
using System.Xml;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
Expand Down Expand Up @@ -172,6 +173,18 @@ public static ScreenshotMetadata GetScreenshotMetadata(string path, bool include
// If not JSON metadata, return early so we're not throwing/catching pointless exceptions
if (!metadataString.StartsWith("{"))
{
// parse VRC prints
var xmlIndex = metadataString.IndexOf("<x:xmpmeta");
if (xmlIndex != -1)
{
var xmlString = metadataString.Substring(xmlIndex);
// everything after index
var result = ParseVRCPrint(xmlString.Substring(xmlIndex - 7));
result.SourceFile = path;

return result;
}

logger.ConditionalDebug("Screenshot file '{0}' has unknown non-JSON metadata:\n{1}\n", path, metadataString);
return ScreenshotMetadata.JustError(path, "File has unknown non-JSON metadata.");
}
Expand All @@ -193,6 +206,44 @@ public static ScreenshotMetadata GetScreenshotMetadata(string path, bool include
return ScreenshotMetadata.JustError(path, "Failed to parse screenshot metadata JSON. Check logs.");
}
}

public static ScreenshotMetadata ParseVRCPrint(string xmlString)
{
var doc = new XmlDocument();
doc.LoadXml(xmlString);
var root = doc.DocumentElement;
var nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("x", "adobe:ns:meta/");
nsManager.AddNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
nsManager.AddNamespace("xmp", "http://ns.adobe.com/xap/1.0/");
nsManager.AddNamespace("tiff", "http://ns.adobe.com/tiff/1.0/");
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 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;

return new ScreenshotMetadata
{
Application = creatorTool,
Version = 1,
Author = new ScreenshotMetadata.AuthorDetail
{
Id = authorId,
DisplayName = null
},
World = new ScreenshotMetadata.WorldDetail
{
Id = worldId,
InstanceId = worldId,
Name = null
},
Timestamp = DateTime.TryParse(dateTime, out var dt) ? dt : null,
Note = note
};
}

/// <summary>
/// Writes a text description into a PNG file at the specified path.
Expand Down
7 changes: 7 additions & 0 deletions Dotnet/ScreenshotMetadata/ScreenshotMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public class ScreenshotMetadata
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public Vector3? Pos { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public DateTime? Timestamp { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? Note { get; set; }


/// <summary>
/// Any error that occurred while parsing the file. This being true implies nothing else is set.
Expand Down
65 changes: 64 additions & 1 deletion Dotnet/WebApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ private static async Task ImageUpload(HttpWebRequest request, IDictionary<string
if (options.TryGetValue("postData", out object postDataObject))
{
var jsonPostData = (JObject)JsonConvert.DeserializeObject((string)postDataObject);
Dictionary<string, string> postData = new Dictionary<string, string>();
string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n";
if (jsonPostData != null)
{
Expand Down Expand Up @@ -294,6 +293,65 @@ private static async Task ImageUpload(HttpWebRequest request, IDictionary<string
await requestStream.WriteAsync(endBytes, 0, endBytes.Length);
requestStream.Close();
}

private static async Task PrintImageUpload(HttpWebRequest request, IDictionary<string, object> options)
{
if (ProxySet)
request.Proxy = Proxy;

request.AutomaticDecompression = DecompressionMethods.All;
request.Method = "POST";
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
request.ContentType = "multipart/form-data; boundary=" + boundary;
var requestStream = request.GetRequestStream();
// var requestStream = new MemoryStream();
var imageData = options["imageData"] as string;
var fileToUpload = Convert.FromBase64String(imageData);
const string fileFormKey = "image";
const string fileName = "image";
const string fileMimeType = "image/png";
var fileSize = fileToUpload.Length;
const string headerTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\nContent-Length: {4}\r\n";
var header = string.Format(headerTemplate, boundary, fileFormKey, fileName, fileMimeType, fileSize);
var headerBytes = Encoding.UTF8.GetBytes(header);
await requestStream.WriteAsync(headerBytes);
using (var fileStream = new MemoryStream(fileToUpload))
{
var buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
await requestStream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
fileStream.Close();
}
var newlineBytes = Encoding.UTF8.GetBytes("\r\n");
await requestStream.WriteAsync(newlineBytes);
const string textContentType = "text/plain; charset=utf-8";
const string formDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\nContent-Type: {2}\r\nContent-Length: {3}\r\n{4}\r\n";
if (options.TryGetValue("postData", out var postDataObject))
{
var jsonPostData = JsonConvert.DeserializeObject<Dictionary<string, string>>(postDataObject.ToString());
if (jsonPostData != null)
{
foreach (var (key, value) in jsonPostData)
{
var section = string.Format(formDataTemplate, boundary, key, textContentType, value.Length, value);
var sectionBytes = Encoding.UTF8.GetBytes(section);
await requestStream.WriteAsync(sectionBytes);
}
}
}
var endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes);
// test file
// var newFileStream = new FileStream(@"D:\WindowsFiles\Desktop\test", FileMode.Create, FileAccess.Write);
// requestStream.WriteTo(newFileStream);
// newFileStream.Close();

requestStream.Close();
// throw new NotImplementedException();
}

#pragma warning disable CS4014

Expand Down Expand Up @@ -364,6 +422,11 @@ public async void Execute(IDictionary<string, object> options, IJavascriptCallba
{
await LegacyImageUpload(request, options);
}

if (options.TryGetValue("uploadImagePrint", out _))
{
await PrintImageUpload(request, options);
}

try
{
Expand Down
122 changes: 121 additions & 1 deletion html/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7598,6 +7598,7 @@ speechSynthesis.getVoices();
},
layout: 'table'
};
$app.data.printTable = [];
$app.data.stickerTable = [];
$app.data.emojiTable = [];
$app.data.VRCPlusIconsTable = [];
Expand Down Expand Up @@ -16093,8 +16094,11 @@ speechSynthesis.getVoices();
// D.metadata.resolution = `${regex[18]}x${regex[19]}`;
}
}
if (metadata.timestamp) {
D.metadata.dateTime = Date.parse(metadata.timestamp);
}
if (!D.metadata.dateTime) {
D.metadata.dateTime = Date.parse(json.creationDate);
D.metadata.dateTime = Date.parse(metadata.creationDate);
}

if (this.fullscreenImageDialog?.visible) {
Expand Down Expand Up @@ -17145,6 +17149,7 @@ speechSynthesis.getVoices();
$app.data.galleryDialogIconsLoading = false;
$app.data.galleryDialogEmojisLoading = false;
$app.data.galleryDialogStickersLoading = false;
$app.data.galleryDialogPrintsLoading = false;

API.$on('LOGIN', function () {
$app.galleryTable = [];
Expand All @@ -17157,6 +17162,7 @@ speechSynthesis.getVoices();
this.refreshVRCPlusIconsTable();
this.refreshEmojiTable();
this.refreshStickerTable();
this.refreshPrintTable();
workerTimers.setTimeout(() => this.setGalleryTab(pageNum), 100);
};

Expand Down Expand Up @@ -17420,6 +17426,120 @@ speechSynthesis.getVoices();
}
});

// #endregion
// #region | Prints
API.$on('LOGIN', function () {
$app.printTable = [];
});

$app.methods.refreshPrintTable = function () {
this.galleryDialogPrintsLoading = true;
var params = {
n: 100,
tag: 'print'
};
API.getFileList(params);
};

API.$on('FILES:LIST', function (args) {
if (args.params.tag === 'print') {
$app.printTable = args.json.reverse();
$app.galleryDialogPrintsLoading = false;
}
});

$app.methods.deletePrint = function (fileId) {
API.deleteFile(fileId).then((args) => {
API.$emit('PRINT:DELETE', args);
return args;
});
};

API.$on('PRINT:DELETE', function (args) {
var array = $app.printTable;
var { length } = array;
for (var i = 0; i < length; ++i) {
if (args.fileId === array[i].id) {
array.splice(i, 1);
break;
}
}
});

$app.methods.onFileChangePrint = function (e) {
var clearFile = function () {
if (document.querySelector('#PrintUploadButton')) {
document.querySelector('#PrintUploadButton').value = '';
}
};
var files = e.target.files || e.dataTransfer.files;
if (!files.length) {
return;
}
if (files[0].size >= 100000000) {
// 100MB
$app.$message({
message: 'File size too large',
type: 'error'
});
clearFile();
return;
}
if (!files[0].type.match(/image.*/)) {
$app.$message({
message: "File isn't an image",
type: 'error'
});
clearFile();
return;
}
var r = new FileReader();
r.onload = function () {
var date = new Date();
var timestamp = date.toISOString().slice(0, 19);
var params = {
note: 'test print',
worldId: 'wrld_10e5e467-fc65-42ed-8957-f02cace1398c',
timestamp
};
var base64Body = btoa(r.result);
API.uploadPrint(base64Body, params).then((args) => {
$app.$message({
message: 'Print uploaded',
type: 'success'
});
return args;
});
};
r.readAsBinaryString(files[0]);
clearFile();
};

$app.methods.displayPrintUpload = function () {
document.getElementById('PrintUploadButton').click();
};

API.uploadPrint = function (imageData, params) {
return this.call('prints', {
uploadImagePrint: true,
postData: JSON.stringify(params),
imageData
}).then((json) => {
var args = {
json,
params
};
this.$emit('PRINT:ADD', args);
return args;
});
};

API.$on('PRINT:ADD', function (args) {
if (Object.keys($app.printTable).length !== 0) {
$app.printTable.unshift(args.json);
}
});

// #endregion
// #region | Emoji

Expand Down
10 changes: 8 additions & 2 deletions html/src/classes/uiComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,11 @@ export default class extends baseClass {
props: {
userid: String,
location: String,
key: Number
key: Number,
hint: {
type: String,
default: ''
}
},
data() {
return {
Expand All @@ -569,7 +573,9 @@ export default class extends baseClass {
methods: {
async parse() {
this.username = this.userid;
if (this.userid) {
if (this.hint) {
this.username = this.hint;
} else if (this.userid) {
var args = await API.getCachedUser({
userId: this.userid
});
Expand Down
4 changes: 4 additions & 0 deletions html/src/classes/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,10 @@ export default class extends baseClass {
if ($app.galleryDialogVisible) {
$app.refreshEmojiTable();
}
} else if (contentType === 'print') {
if ($app.galleryDialogVisible) {
$app.refreshPrintTable();
}
} else if (contentType === 'avatar') {
// hmm, utilizing this might be too spamy and cause UI to move around
} else if (contentType === 'world') {
Expand Down
1 change: 1 addition & 0 deletions html/src/localization/en/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,7 @@
"icons": "Icons",
"emojis": "Emojis",
"stickers": "Stickers",
"prints": "Prints",
"refresh": "Refresh",
"upload": "Upload",
"clear": "Clear",
Expand Down
Loading

0 comments on commit 84e8de3

Please sign in to comment.