Skip to content

Commit

Permalink
feat: Add searching & fullscreen mode to screenshot viewer (vrcx-team…
Browse files Browse the repository at this point in the history
…#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>
  • Loading branch information
3 people committed Sep 2, 2023
1 parent 96f7567 commit 79dc5c6
Show file tree
Hide file tree
Showing 7 changed files with 892 additions and 222 deletions.
166 changes: 94 additions & 72 deletions AppApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,16 @@
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using Newtonsoft.Json.Serialization;

namespace VRCX
{
public class AppApi
{
public static readonly AppApi Instance;

private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly MD5 _hasher = MD5.Create();
private static bool dialogOpen;

Expand Down Expand Up @@ -959,118 +962,137 @@ public void OpenScreenshotFileDialog()
if (string.IsNullOrEmpty(path))
return;
GetScreenshotMetadata(path);
ExecuteAppFunction("screenshotMetadataResetSearch", null);
ExecuteAppFunction("getAndDisplayScreenshot", path);
}
});

thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}

/// <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 GetExtraScreenshotData(string path, bool carouselCache)
{
if (string.IsNullOrEmpty(path))
return;

var fileName = Path.GetFileNameWithoutExtension(path);
var metadata = new JObject();
if (File.Exists(path) && path.EndsWith(".png"))
{
string metadataString = null;
var readPNGFailed = false;

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;
}
if (!File.Exists(path) || !path.EndsWith(".png"))
return null;

if (!string.IsNullOrEmpty(metadataString))
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)
{
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}");
}
}
metadata.Add("previousFilePath", files[index - 1]);
}
else
if (index < files.Length - 1)
{
if (!readPNGFailed)
metadata.Add("error", "No metadata found in this file.");
metadata.Add("nextFilePath", files[index + 1]);
}
}
else

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 string GetScreenshotMetadata(string path)
{
if (string.IsNullOrEmpty(path))
return null;


var metadata = ScreenshotHelper.GetScreenshotMetadata(path);

if (metadata == null)
{
metadata.Add("error", "Invalid file selected. Please select a valid VRChat screenshot.");
}
var obj = new JObject
{
{ "sourceFile", path },
{ "error", "Screenshot contains no metadata." }
};

var files = Directory.GetFiles(Path.GetDirectoryName(path), "*.png");
var index = Array.IndexOf(files, path);
if (index > 0)
return obj.ToString(Formatting.Indented);
};

if (metadata.Error != null)
{
metadata.Add("previousFilePath", files[index - 1]);
var obj = new JObject
{
{ "sourceFile", path },
{ "error", metadata.Error }
};

return obj.ToString(Formatting.Indented);
}

if (index < files.Length - 1)
return JsonConvert.SerializeObject(metadata, Formatting.Indented, new JsonSerializerSettings
{
metadata.Add("nextFilePath", 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);
}

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));
stopwatch.Stop();

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>
Expand Down
Loading

0 comments on commit 79dc5c6

Please sign in to comment.