feat: Add searching & fullscreen mode to screenshot viewer (#627)

* Optimized search screenshots by metadata

* feat: Screenshot metadata search bar

* fix: Reset search when selecting a file manually

* refactor: Re-do the whole search thing. Add number of results to dialog when searching

* fix: Add check & error for null metadata

* fix: Add sourceFile to error obj on return

* fix: Fix screenshot file dialog not sending path back to JS

* fix: Stop lfs parsing from dying if a value doesn't exist

* fix: Fix and optimize FileStream reading of metadata for searches

* fix: Reset search data and revert to normal when user clears out search box

* refactor: Remove/optimize some old screenshot helper stuff

- Use FileStream in ReadPNGResolution
- Limit the FindChunkIndex search range used when writing metadata
- Remove old ReadPNGDescription, just use filestream version now

* fix: Reset metadata search state if a file is added manually

* feat: Move viewer popover dialog to the fullscreen image viewer

* refactor: Change how parsing errors are handled... again

* refactor: Let the search carousel loop around

* fix: Re-do legacy parsing /wo JObject. Fix legacy instance ids/pos.

Also adds further docs to the legacy parsing for the various formats

* feat: Add persistent metadata cache for search

* Clean up

* fix: Fix viewer dying

sourceFile wasn't being included for vrcx pics

* refactor: Cache the state of files with no metadata

This is so we're not constantly re-processing these files with no metadata on every first search after a restart; These files won't magically gain metadata and this could cause a lot of hitching for someone that had potentially thousands of screenshots before using VRCX.

* Screenshot viewer loading

---------

Co-authored-by: Nekromateion <43814053+Nekromateion@users.noreply.github.com>
Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
Teacup
2023-09-02 17:02:09 -04:00
committed by GitHub
parent eecbba3c33
commit 0a0f166a2b
7 changed files with 895 additions and 225 deletions
+95 -73
View File
@@ -25,6 +25,8 @@ using librsync.net;
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using Newtonsoft.Json.Serialization;
namespace VRCX
{
@@ -32,6 +34,7 @@ namespace VRCX
{
public static readonly AppApi Instance;
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly MD5 _hasher = MD5.Create();
private static bool dialogOpen;
@@ -959,7 +962,8 @@ namespace VRCX
if (string.IsNullOrEmpty(path))
return;
GetScreenshotMetadata(path);
ExecuteAppFunction("screenshotMetadataResetSearch", null);
ExecuteAppFunction("getAndDisplayScreenshot", path);
}
});
@@ -967,110 +971,128 @@ namespace VRCX
thread.Start();
}
public string GetExtraScreenshotData(string path, bool carouselCache)
{
var fileName = Path.GetFileNameWithoutExtension(path);
var metadata = new JObject();
if (!File.Exists(path) || !path.EndsWith(".png"))
return null;
var files = Directory.GetFiles(Path.GetDirectoryName(path), "*.png");
// Add previous/next file paths to metadata so the screenshot viewer carousel can request metadata for next/previous images in directory
if (carouselCache)
{
var index = Array.IndexOf(files, path);
if (index > 0)
{
metadata.Add("previousFilePath", files[index - 1]);
}
if (index < files.Length - 1)
{
metadata.Add("nextFilePath", files[index + 1]);
}
}
metadata.Add("fileResolution", ScreenshotHelper.ReadPNGResolution(path));
var creationDate = File.GetCreationTime(path);
metadata.Add("creationDate", creationDate.ToString("yyyy-MM-dd HH:mm:ss"));
var fileSizeBytes = new FileInfo(path).Length;
metadata.Add("fileSizeBytes", fileSizeBytes.ToString());
metadata.Add("fileName", fileName);
metadata.Add("filePath", path);
metadata.Add("fileSize", $"{(fileSizeBytes / 1024f / 1024f).ToString("0.00")} MB");
return metadata.ToString(Formatting.Indented);
}
/// <summary>
/// Retrieves metadata from a PNG screenshot file and send the result to displayScreenshotMetadata in app.js
/// </summary>
/// <param name="path">The path to the PNG screenshot file.</param>
public void GetScreenshotMetadata(string path)
public string GetScreenshotMetadata(string path)
{
if (string.IsNullOrEmpty(path))
return;
return null;
var fileName = Path.GetFileNameWithoutExtension(path);
var metadata = new JObject();
if (File.Exists(path) && path.EndsWith(".png"))
var metadata = ScreenshotHelper.GetScreenshotMetadata(path);
if (metadata == null)
{
string metadataString = null;
var readPNGFailed = false;
var obj = new JObject
{
{ "sourceFile", path },
{ "error", "Screenshot contains no metadata." }
};
try
{
metadataString = ScreenshotHelper.ReadPNGDescription(path);
}
catch (Exception ex)
{
metadata.Add("error", $"VRCX encountered an error while trying to parse this file. The file might be an invalid/corrupted PNG file.\n({ex.Message})");
readPNGFailed = true;
}
return obj.ToString(Formatting.Indented);
};
if (!string.IsNullOrEmpty(metadataString))
{
if (metadataString.StartsWith("lfs") || metadataString.StartsWith("screenshotmanager"))
{
try
{
metadata = ScreenshotHelper.ParseLfsPicture(metadataString);
}
catch (Exception ex)
{
metadata.Add("error", $"This file contains invalid LFS/SSM metadata unable to be parsed by VRCX. \n({ex.Message})\nText: {metadataString}");
}
}
else
{
try
{
metadata = JObject.Parse(metadataString);
}
catch (JsonReaderException ex)
{
metadata.Add("error", $"This file contains invalid metadata unable to be parsed by VRCX. \n({ex.Message})\nText: {metadataString}");
}
}
}
else
{
if (!readPNGFailed)
metadata.Add("error", "No metadata found in this file.");
}
}
else
if (metadata.Error != null)
{
metadata.Add("error", "Invalid file selected. Please select a valid VRChat screenshot.");
var obj = new JObject
{
{ "sourceFile", path },
{ "error", metadata.Error }
};
return obj.ToString(Formatting.Indented);
}
var files = Directory.GetFiles(Path.GetDirectoryName(path), "*.png");
var index = Array.IndexOf(files, path);
if (index > 0)
return JsonConvert.SerializeObject(metadata, Formatting.Indented, new JsonSerializerSettings
{
metadata.Add("previousFilePath", files[index - 1]);
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy() // This'll serialize our .net property names to their camelCase equivalents. Ex; "FileName" -> "fileName"
}
});
}
public string FindScreenshotsBySearch(string searchQuery, int searchType = 0)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var searchPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "VRChat");
var screenshots = ScreenshotHelper.FindScreenshots(searchQuery, searchPath, (ScreenshotHelper.ScreenshotSearchType)searchType);
JArray json = new JArray();
foreach (var screenshot in screenshots)
{
json.Add(screenshot.SourceFile);
}
if (index < files.Length - 1)
{
metadata.Add("nextFilePath", files[index + 1]);
}
stopwatch.Stop();
metadata.Add("fileResolution", ScreenshotHelper.ReadPNGResolution(path));
var creationDate = File.GetCreationTime(path);
metadata.Add("creationDate", creationDate.ToString("yyyy-MM-dd HH:mm:ss"));
metadata.Add("fileName", fileName);
metadata.Add("filePath", path);
var fileSizeBytes = new FileInfo(path).Length;
metadata.Add("fileSizeBytes", fileSizeBytes.ToString());
metadata.Add("fileSize", $"{(fileSizeBytes / 1024f / 1024f).ToString("0.00")} MB");
ExecuteAppFunction("displayScreenshotMetadata", metadata.ToString(Formatting.Indented));
logger.Info($"FindScreenshotsBySearch took {stopwatch.ElapsedMilliseconds}ms to complete.");
return json.ToString();
}
/// <summary>
/// Gets the last screenshot taken by VRChat and retrieves its metadata.
/// Gets and returns the path of the last screenshot taken by VRChat.
/// </summary>
public void GetLastScreenshot()
public string GetLastScreenshot()
{
// Get the last screenshot taken by VRChat
var path = GetVRChatPhotosLocation();
if (!Directory.Exists(path))
return;
return null;
var lastDirectory = Directory.GetDirectories(path).OrderByDescending(Directory.GetCreationTime).FirstOrDefault();
if (lastDirectory == null)
return;
return null;
var lastScreenshot = Directory.GetFiles(lastDirectory, "*.png").OrderByDescending(File.GetCreationTime).FirstOrDefault();
if (lastScreenshot == null)
return;
return null;
GetScreenshotMetadata(lastScreenshot);
return lastScreenshot;
}
/// <summary>