From 8b2c9715ae495daac69ef1cede4244d33bf50480 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:00:39 +0000 Subject: [PATCH 1/5] feat: Add option to display photo location from EXIF data This commit introduces a new feature to the screensaver that allows the user to display the geographical location of a photo. The key changes include: - A new utility method in `Utils.cs` to perform reverse geocoding using the Nominatim OpenStreetMap API. This method converts GPS coordinates (latitude and longitude) from the photo's EXIF data into a human-readable location name. It includes parsing for the DMS format. - The `Monitor.cs` file is updated to call this new utility. When a photo with GPS data is displayed, its location is fetched and passed to the frontend. - A new checkbox is added to the configuration screen (`config.html`) allowing users to enable or disable this feature for each monitor. - The screensaver display (`monitor.html` and `monitor.js`) is updated to include a new element that shows the location information when it's available and the setting is enabled. --- RPS 4/Monitor.cs | 7 +++++++ RPS 4/Utils.cs | 39 +++++++++++++++++++++++++++++++++++++++ RPS 4/data/config.html | 1 + RPS 4/data/js/monitor.js | 5 +++++ RPS 4/data/monitor.html | 1 + 5 files changed, 53 insertions(+) diff --git a/RPS 4/Monitor.cs b/RPS 4/Monitor.cs index 62bf5dd..1a34a97 100644 --- a/RPS 4/Monitor.cs +++ b/RPS 4/Monitor.cs @@ -97,6 +97,7 @@ public Monitor(IntPtr previewHwnd, int id, Screensaver screensaver): this(id, sc public void defaultShowHide() { this.InvokeScript("setBackgroundColour", new string[] { Convert.ToString(this.screensaver.config.getPersistant("backgroundColour")) }); this.InvokeScript("toggle", new string[] { "#quickMetadata", Convert.ToString(this.screensaver.config.getPersistantBool("showQuickMetadataM" + (this.id + 1))) }); + this.InvokeScript("toggle", new string[] { "#location", Convert.ToString(this.screensaver.config.getPersistantBool("showLocationM" + (this.id + 1))) }); this.InvokeScript("toggle", new string[] { "#filename", Convert.ToString(this.screensaver.config.getPersistantBool("showFilenameM" + (this.id + 1))) }); this.InvokeScript("toggle", new string[] { "#filename .root", Convert.ToString(this.screensaver.config.getPersistantBool("showPathRoot")) }); this.InvokeScript("toggle", new string[] { "#filename .subfolders", Convert.ToString(this.screensaver.config.getPersistantBool("showPathSubfolders")) }); @@ -440,6 +441,12 @@ public void readMetadataImage() { } if (rawMetadata != null && rawMetadata != "") { this.quickMetadata = new MetadataTemplate(rawMetadata, Utils.HtmlDecode(this.screensaver.config.getPersistantString("quickMetadata"))); + if (this.quickMetadata.metadata.ContainsKey("gpslatitude") && this.quickMetadata.metadata.ContainsKey("gpslongitude")) { + string location = Utils.GetLocationFromGps(this.quickMetadata.metadata["gpslatitude"], this.quickMetadata.metadata["gpslongitude"]); + if (location != null) { + this.imageSettings["location"] = location; + } + } this.imageSettings["metadata"] = this.quickMetadata.fillTemplate(); } this.imageSettings["mediatype"] = "image"; diff --git a/RPS 4/Utils.cs b/RPS 4/Utils.cs index dc57dbb..516bc77 100644 --- a/RPS 4/Utils.cs +++ b/RPS 4/Utils.cs @@ -11,9 +11,48 @@ using System.Globalization; using System.Collections; using System.Collections.Concurrent; +using System.Net; +using Newtonsoft.Json.Linq; namespace RPS { class Utils { + private static double ConvertDmsToDd(string dms) { + string[] parts = dms.Split(new char[] { ' ', '°', '\'', '"' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 3) { + double deg = double.Parse(parts[0], CultureInfo.InvariantCulture); + double min = double.Parse(parts[1], CultureInfo.InvariantCulture); + double sec = double.Parse(parts[2], CultureInfo.InvariantCulture); + return deg + (min / 60.0) + (sec / 3600.0); + } else if (parts.Length == 1) { + return double.Parse(parts[0], CultureInfo.InvariantCulture); + } + return 0; + } + + public static string GetLocationFromGps(string latitudeStr, string longitudeStr) { + if (latitudeStr == null || longitudeStr == null) return null; + try { + double latitude = ConvertDmsToDd(latitudeStr); + double longitude = ConvertDmsToDd(longitudeStr); + + if (latitudeStr.ToUpper().Contains("S")) latitude = -latitude; + if (longitudeStr.ToUpper().Contains("W")) longitude = -longitude; + + string url = $"https://nominatim.openstreetmap.org/reverse?format=json&lat={latitude.ToString(CultureInfo.InvariantCulture)}&lon={longitude.ToString(CultureInfo.InvariantCulture)}&accept-language=en"; + + using (WebClient wc = new WebClient()) { + wc.Headers.Add("User-Agent", "RPS/4.0 (a screensaver)"); // Per Nominatim requirements + string json = wc.DownloadString(url); + JObject data = JObject.Parse(json); + if (data["error"] == null && data["display_name"] != null) { + return (string)data["display_name"]; + } + } + } catch (Exception ex) { + Debug.WriteLine("Error in GetLocationFromGps: " + ex.Message); + } + return null; + } public struct MSG { IntPtr hwnd; uint message; diff --git a/RPS 4/data/config.html b/RPS 4/data/config.html index 54fe275..4947d12 100644 --- a/RPS 4/data/config.html +++ b/RPS 4/data/config.html @@ -473,6 +473,7 @@

Monitor 1

Information



+
diff --git a/RPS 4/data/js/monitor.js b/RPS 4/data/js/monitor.js index ea2d5f5..6ffaa79 100644 --- a/RPS 4/data/js/monitor.js +++ b/RPS 4/data/js/monitor.js @@ -536,6 +536,11 @@ function showImage(source, displayPath, settings) { $("#filename .filename")[0].innerHTML = settings["path.filename"]; $("#filename .extension")[0].innerHTML = settings["path.extension"]; document.getElementById("quickMetadata").innerHTML = settings["metadata"]; + if (settings["location"] != undefined) { + document.getElementById("location").innerHTML = settings["location"]; + } else { + document.getElementById("location").innerHTML = ""; + } if (settings["pano"] != undefined && settings["pano"] == true) { // window.prompt("Copy to clipboard: Ctrl+C, Enter", html); diff --git a/RPS 4/data/monitor.html b/RPS 4/data/monitor.html index 1c79487..0a27226 100644 --- a/RPS 4/data/monitor.html +++ b/RPS 4/data/monitor.html @@ -23,6 +23,7 @@
#indexprogress#
#debug#
#metadata#
+
#location#
hh:mm:ss
From d316b6ed8aec36be7cf1954b63b6a49520ea22fe Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:16:37 +0000 Subject: [PATCH 2/5] fix: Improve GPS coordinate parsing and add location display This commit addresses a bug that caused a "Input string was not in a correct format" error when parsing GPS coordinates from EXIF data. The `ConvertDmsToDd` method in `Utils.cs` has been updated to use regular expressions, making the parsing more robust against various formats. This commit also includes the initial implementation of the feature to display the geographical location of a photo: - A new utility method in `Utils.cs` performs reverse geocoding using the Nominatim OpenStreetMap API. - `Monitor.cs` is updated to call this utility and pass the location to the frontend. - A new checkbox in `config.html` allows users to enable or disable this feature. - The screensaver display (`monitor.html` and `monitor.js`) is updated to show the location information. --- RPS 4/Utils.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/RPS 4/Utils.cs b/RPS 4/Utils.cs index 516bc77..ce997c3 100644 --- a/RPS 4/Utils.cs +++ b/RPS 4/Utils.cs @@ -13,17 +13,20 @@ using System.Collections.Concurrent; using System.Net; using Newtonsoft.Json.Linq; +using System.Text.RegularExpressions; namespace RPS { class Utils { private static double ConvertDmsToDd(string dms) { - string[] parts = dms.Split(new char[] { ' ', '°', '\'', '"' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length >= 3) { + var matches = System.Text.RegularExpressions.Regex.Matches(dms, @"[0-9\.]+"); + var parts = matches.Cast().Select(m => m.Value).ToList(); + + if (parts.Count >= 3) { double deg = double.Parse(parts[0], CultureInfo.InvariantCulture); double min = double.Parse(parts[1], CultureInfo.InvariantCulture); double sec = double.Parse(parts[2], CultureInfo.InvariantCulture); return deg + (min / 60.0) + (sec / 3600.0); - } else if (parts.Length == 1) { + } else if (parts.Count == 1) { return double.Parse(parts[0], CultureInfo.InvariantCulture); } return 0; From c5d98222cb6c3880acefc60f949cf4d35f03a10b Mon Sep 17 00:00:00 2001 From: Ayal Org Date: Sat, 13 Sep 2025 18:48:47 +0300 Subject: [PATCH 3/5] better townish parsing fixed reference to batch file --- RPS 4/RPS 4.csproj | 2 +- RPS 4/Utils.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/RPS 4/RPS 4.csproj b/RPS 4/RPS 4.csproj index 5a39e93..100b4a3 100644 --- a/RPS 4/RPS 4.csproj +++ b/RPS 4/RPS 4.csproj @@ -222,7 +222,7 @@ - call "$(DevEnvDir)..\Tools\vsvars32.bat" + call "$(DevEnvDir)..\Tools\VsDevCmd.bat" IF NOT EXIST "$(TargetDir)vendor" MKDIR "$(TargetDir)vendor" xcopy /d /s "$(ProjectDir)vendor\*.*" "$(TargetDir)vendor" rem IF NOT EXIST "$(TargetDir)data" MKDIR "$(TargetDir)data" diff --git a/RPS 4/Utils.cs b/RPS 4/Utils.cs index ce997c3..df4156e 100644 --- a/RPS 4/Utils.cs +++ b/RPS 4/Utils.cs @@ -42,13 +42,22 @@ public static string GetLocationFromGps(string latitudeStr, string longitudeStr) if (longitudeStr.ToUpper().Contains("W")) longitude = -longitude; string url = $"https://nominatim.openstreetmap.org/reverse?format=json&lat={latitude.ToString(CultureInfo.InvariantCulture)}&lon={longitude.ToString(CultureInfo.InvariantCulture)}&accept-language=en"; - + Debug.WriteLine("GetLocationFromGps url:" + url); using (WebClient wc = new WebClient()) { wc.Headers.Add("User-Agent", "RPS/4.0 (a screensaver)"); // Per Nominatim requirements string json = wc.DownloadString(url); JObject data = JObject.Parse(json); - if (data["error"] == null && data["display_name"] != null) { - return (string)data["display_name"]; + if (data["error"] == null && data["address"] != null) { + string townish = (string)( + data["address"]["town"] ?? + data["address"]["city"] ?? + data["address"]["municipality"] ?? + data["address"]["village"] ?? + "" + ); + string prettyName = (townish.Length > 0 ? townish +", " : "") + data["address"]["country"]; + Debug.WriteLine("GetLocationFromGps:" + prettyName); + return prettyName; } } } catch (Exception ex) { From f2b10131111f607a44cdc084f2f1953e2a41df97 Mon Sep 17 00:00:00 2001 From: Ayal Org Date: Sat, 20 Sep 2025 17:15:43 +0300 Subject: [PATCH 4/5] Location is now showing on screen --- RPS 4/Utils.cs | 13 +++++-------- RPS 4/data/css/monitor.css | 7 +++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/RPS 4/Utils.cs b/RPS 4/Utils.cs index df4156e..7045568 100644 --- a/RPS 4/Utils.cs +++ b/RPS 4/Utils.cs @@ -48,14 +48,11 @@ public static string GetLocationFromGps(string latitudeStr, string longitudeStr) string json = wc.DownloadString(url); JObject data = JObject.Parse(json); if (data["error"] == null && data["address"] != null) { - string townish = (string)( - data["address"]["town"] ?? - data["address"]["city"] ?? - data["address"]["municipality"] ?? - data["address"]["village"] ?? - "" - ); - string prettyName = (townish.Length > 0 ? townish +", " : "") + data["address"]["country"]; + string townish = new[] { "town", "city", "municipality", "village" } + .Select(key => (string)data["address"][key]) + .FirstOrDefault(val => !string.IsNullOrEmpty(val)) ?? ""; + string country = (string)data["address"]["country"] ?? ""; + string prettyName = !string.IsNullOrEmpty(townish) ? $"{townish}, {country}" : country; Debug.WriteLine("GetLocationFromGps:" + prettyName); return prettyName; } diff --git a/RPS 4/data/css/monitor.css b/RPS 4/data/css/monitor.css index c5bdbc2..8067cb8 100644 --- a/RPS 4/data/css/monitor.css +++ b/RPS 4/data/css/monitor.css @@ -128,6 +128,13 @@ html, body { text-align: right; } +#location { + z-index: 100; + right: 0.5em; + bottom: 2.5em; + text-align: right; +} + #debug { z-index: 100; right: 0.5em; From 07eaf83d0bb63651d322f28b2398ad6288a27d76 Mon Sep 17 00:00:00 2001 From: Ayal Org Date: Sat, 20 Sep 2025 17:23:00 +0300 Subject: [PATCH 5/5] modified the readme --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 70ca0a8..567d25d 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,8 @@ Random Photo Screensaver(tm) 4 Random Photo Screensaver 4 (RPS4) is a photo slideshow screensaver written in Visual Studio C#. +* This fork adds support for displaying photo location using EXIF GPS data. Most of the work was done by Jules, I just added some finishing touches and tweaks. + Download / preview ------------------ You can download the latest executable for Windows 7 & 8/8.1 from [abScreensavers.com](http://www.abscreensavers.com/random-photo-screensaver). This also showcases some of its many features.