Introducing: Lightroom Preset Renamer

As you may know, besides software, I do photography. In fact, on my photoblog, I’m approaching 15 years of posting a new photo every day (as of this writing, that’s 5448 days of new photos). My main tool for managing, organizing and processing my photos is Adobe’s Lightroom (Classic), which I’ve used since version 1 when I started my “modern” photography hobby/side-business in 2007 (I’d done photography in the past, but had pretty much stopped after college as I no-longer had access to a darkroom, was too busy/poor/had other priorities/obligations for film and digital wasn’t cheap enough/good enough for what I wanted to do).

One of the key features of Lightroom is the ability to create and use presets, which apply develop settings to the selected image, adjusting exposure, color, crop, etc., without user intervention. Some photographers are adamantly against the idea of presets, as they view each photo to be processed as a clean slate that requires careful finessing of values to process. On the other end of the spectrum, you have lazy people who apply a preset without any additional work, export the photo and call it a day. However, most Lightroom users lie somewhere in the middle—using a preset as a starting point for processing an image, followed by fine-tuning it to perfection. 

I am one of the latter Lightroom users. I’ll often open a photo and scroll through my presets looking for a good starting point for the “look” I want a photo to have. Besides creating my own presets, I like to explore other people’s presets they’ve shared in the Lightoom (Cloud) or Mobile app (note that Adobe has two confusingly-named Lightroom applications…one is the professional version (Classic) that most serious photographers use and the stripped-down, amateur/mobile-focused Lightroom (Cloud) that doesn’t, in my opinion, have a place in a professional’s toolkit). In Lightroom Cloud, there’s a “Discover” section that allows you to browse shared presets and download them to use for yourself:

If you filter by “Preset downloadable”, you can scroll through the photo grid and download the preset for any photo:

Lightroom Cloud will then save it to your account and it can be used on any photo in Lightroom Cloud or Lightroom Mobile (the presets will sync to the Lightroom app on your phone).

At this point, however, you can’t use them in Classic, yet. Since the Lightroom Cloud presets don’t sync to Lightroom Classic, you have to do some work to get them there. 

(Note that the following instructions are for MacOS, but should be similar on Windows)

  1. Find your Lightroom Cloud library (~/Pictures/Lightroom Library.lrlibrary)
  2. Right click and select “Show Package Contents”
  3. In that folder, there are 4 subfolders. One has a bunch of random characters; the others are profiles, TemporaryEdits and user.
  4. Click into the random characters folder.
  5. Find the cr_settings subfolder and click into it.
  6. The .xmp files here are your presets.
  7. Copy these to where your Classic presets are stored (should be something like ~/Library/Application Support/Adobe/CameraRaw/Settings
  8. Restart Classic

Voila! Your presets should be there in the Presets pane in Lightroom Classic. The problem with this is that Adobe saves the presets to your local drive as a guid rather than a human-readable name:

If you’re like me, though, this is unacceptable. I want to be able to read the names of the presets while in Finder. So, to solve this, I built a tiny MacOS application I call Lightroom Preset Renamer. This is how I use it:

  1. Copy the .xmp preset files you found in the Lightroom Library.lrlibrary file to a temporary folder.
  2. Run the Preset Renamer application and choose this folder by clicking the “Choose Preset Folder to Process”:
  3. Once you’ve chosen the folder, it will automatically process any .xmp file in that folder, renaming it to it’s “proper name”, while preserving the original file by changing the extension to “xmp_old”:
  4. Now, copy these .xmp files to the CameraRaw/Settings folder as outlined above and then reset Classic.

A couple of notes: 

  1. This app isn’t signed, so you may need to follow the instructions here to run it. Or, if you’re adventurous and have to run unsigned apps often, you can disable Gatekeeper by following these instructions.
  2. If you’re concerned about security, you can inspect the source code and build the app yourself in Visual Studio Mac by going to this Github repository.
  3. Since this is built on .Net, I plan on building a Windows version soon…stay tuned!
  4. You can download the application at the link below:

Download the latest release here

How I Added Maps to 75CentralPhotography.Com

For years, I’ve geo-tagged my photos on my photography site, 75CentralPhotography. In fact, I one wrote about my geotagging workflow over there…the workflow is a bit outdated, but still works and is still relevant.

Recently, I was thinking about ways to enhance the browsing/user experience of the site, when I hit upon the idea of including a map with each photo showing where the photos was taken, since I already had all this GPS metadata embedded in each photo.

The first step was figuring out how to extract the GPS data from the photo’s embedded metadata. Luckily, the site is built on WordPress and WordPress is built using PHP. And PHP has a built-in function for extracting EXIF and IPTC metadata from a given image, exif_read_data(), so I just needed to pass in an image path and it would return the full image metadata, then parse that to extract the longitude and latitude of where the photo is geotagged, which I could then use to place the photo’s location on a map.

The code I used to get the GPS coordinates
 

 
Explained
  1. First, I needed to get the attachment ID for the photo. Since I only post one photo for each blog post, I knew I could use the handy catch_that_image() function that returns the id of the first image in a post when called from within a post:
  2. However, since catch_that_image() only returns the attachment ID and exif_read_data() needs a relative path to the image file, I needed to get the path to the file from the attachment ID. Luckily, WordPress offers the  get_attached_file() function to do just that:
  3. Now that we have the relative path, we can finally pass that to the exif_read_data() PHP function to get the EXIF data back as arrays.
  4. Finally, we can extract the latitude and longitude from the EXIF data using  a custom getGPS() function:

Now that we’ve successfully extracted the embedded GPS coordinate’s from the photo’s EXIF data, we can use the leaflet.js Javascript library to display it.

Before we can display the map, we do three things:

  1. Check to make sure that the photo actually has GPS data (as extracted in the previous step)
  2. Add these coordinates to custom post metadata in WordPress
  3. Check to see if a custom zoom level has been defined for a particular image in custom post metadata, else set the default zoom level of the map.
    1. We do this as for some photos, there may not be any contextual information available for a map to display, such as a photo taken in the middle of the ocean, which would result in a blank map.
The code that does this is some simple PHP:

Now that we have that, we can embed the leaflet.js map:

Explained
  1. First, we use an HTML <div> tag to set the display <div> for the map:
      <div id=”mapid” ></div>
  2. Next, we define the map and set the view and zoom level:
    var mymap = L.map(‘mapid’, {scrollWheelZoom: false}).setView([<?php echo $lat ?>, <?php echo $lon ?>], <?php echo $zoom[0] ?>);
  3. Add the zoom controls to the map:
    L.control.scale({position: ‘topright’}).addTo(mymap);
  4. Add the map tiles from a custom map in MapBox, as well as the custom icon or pin we created to pinpoint the photo’s location:
    L.tileLayer(‘https://api.mapbox.com/styles/v1/75central/{id}/tiles/{z}/{x}/{y}?access_token=SECRET_TOKEN’, {
    attribution: ‘Map data &copy; <a href=”https://www.openstreetmap.org/copyright”>OpenStreetMap</a> contributors, Imagery © <a href=”https://www.mapbox.com/”>Mapbox</a>. Some photo locations may be <a href=”https://www.75centralphotography.com/location-information/”>inaccurate or obfuscated</a>.’,
    maxZoom: 20,
    id: ‘SECRET_ID’,
    tileSize: 512,
    zoomOffset: -1,
    accessToken: ‘SECRET_TOKEN’
    }).addTo(mymap);
    var LeafIcon = L.Icon.extend({
    options: {
    iconSize: [50, 50],
    iconAnchor: [22, 50],
    popupAnchor: [-3, -76]
    }
    });
    var greenIcon = new LeafIcon({iconUrl: ‘https://www.75centralphotography.com/assets/camera.png’});
    window.dispatchEvent(new Event(‘resize’));
  5. And, finally, add the defined marker/pin to the map:
    var marker = L.marker([<?php echo $lat ?>, <?php echo $lon ?>], {icon: greenIcon}).addTo(mymap);

And, voila, we have a map with the photo’s location on each photo on 75CentralPhotography:

EXIFViewer, Redux

Last year, I wrote about a small photo EXIF data viewer I’d built. Unfortunately, I hadn’t really given the project much thought since then, especially since it was written to run on Windows and late last year, I switched back to MacOS. 

Recently, however, i’ve been toying with idea of porting it to MacOS, especially since Microsoft’s Xamarin lets you write .NET code and compile it with MacOS as the target OS. However, to do so, I needed to rewrite for the Windows platform.

The first problem is that the original was written in Visual Basic.NET, which is great for rapidly-building applications, but is not a modern language and is on its way to being deprecated by Microsoft.

The second problem, and this is somewhat-embarassing considering that I’m a software development manager and solutions architect at my day job, but the application was poorly-built (I threw it together in a couple of hours). No modularity. No proper design patterns. Logic intermingled with UI. Lots of global variables. 

So, to port to MacOS via Xamarin, I’d need to rewrite the code in C# (since VB.NET isn’t supported) and I’d need to make it more modular, so that the processing/backend was abstracted away from the user interface. This way, I could use the codebase that extracts the EXIF data in my Mac version without modification and will only need to build the UI elements for MacOS. 

At any rate, I’ve started making my first stabs at writing the Mac version, but until then, the Windows version is available on GitHub here. You can download the installer here.

I welcome feedback, contributions and pull requests!

Cleaning Windows

I’ll admit it…I’m kind of a messy person. My desk is covered with various tools, gadgets, opened and unopened mail, receipts and whatnot. In my house, it’s okay to drape your jacket over the back of the easy chair rather than hanging it in the closet. And maybe the dishes don’t get done right after dinner. We’re not gross, unclean people like you’d see on Hoarders, just slightly messy, have-a-lot-of-things-going-on people. 

There is, however, one place that I like to keep pristine. Unfortunately, it’s in the nerdy, digital realm…my Windows desktop. I like there to be no icons, just the background and the Recycle Bin:

 

To achieve this, I periodically minimize all windows, select any detritus that’s gathered and drag it to the Recycle Bin. 

Perfection!

Since I don’t consciously ever save anything to the desktop, the only things that gather there are shortcuts “helpfully” created when an application is installed. While some installers nicely ask you if you want to create a desktop shortcut, in my experience, these are pretty rare. Most just plop one down there and make themselves at home.

Keeping up with this virtual cleaning is a manual task, and while it doesn’t take but a few seconds at most, it’s still something that I have to do. 

So, while the world has been obsessed with keeping meatspace clean due to COVID-19, I decided to come up with a way to keep my virtual space clean automatically, because I’m both nerdy and lazy.

So, introducing ShortcutCleaner—a small program that lives in your system tray and watches for shortcuts to be created on the desktop, then quietly whisks them away into nothingness.

You can install it by going to the release page on GitHub, downloading the .msi file and running.

If you need a deeper dive in installing (i.e. you run into issues with Window disallowing install because of Windows Defender SmartScreen), take a look at my post on the ExifViewer I built.

Now for the nerdy part: a breakdown of how the code works. So exciting!

First, we do some boilerplate Visual Studio C# stuff when the program starts to instantiate our main form

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new formMain());

When formMain() is instantiated, we go ahead an declare a few class properties to get started:

 [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

        public const int SHCNE_ASSOCCHANGED = 0x8000000;
        public const int SHCNF_IDLIST = 0;
        public string commonPath;
        public string specialPath;

DllImport does what it says on the box: it imports shell32.dll into the project so we can access some low-level Windows functionality. In this case, we want to be able to force Windows to refresh the desktop after we’ve cleaned it…otherwise the shortcuts will be logically deleted, but they might hang around on the desktop until Windows does some automated housecleaning. And once we’ve imported the .dll, we can go ahead and declare the SHChangeNotify function and associated constants, which will actually do the refresh. Finally, we define a couple of class-level variables to hold the two paths for the Windows desktop. (Yes, there are two paths in Windows…the desktop for all users and the desktop specific to the currently logged-in user).

 private void formMain_Load(object sender, EventArgs e)
        {
            commonPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
            specialPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            fileSystemWatcher1.Path = commonPath;
            fileSystemWatcher2.Path = specialPath;
            this.ShowInTaskbar = false;
            loadClean(commonPath);
            loadClean(specialPath);
        }

Next, we load formMain(). We set the two previsouly-mentioned desktop paths, then we instantiate two fileSystemWatcher objects. These are built-in components in .NET that will watch a specified folder for changes. Since we have two paths, we need to fileSystemWatchers. Since we want this utility to only live in the system tray, we tell the application to not be shown in the taskbar. Finally, on load, we call our function that actually does the cleaning twice, once for each of the watched paths.

private void loadClean(string cleanPath)
        {
            string checkEx = @".lnk";

            foreach (string fileName in Directory.GetFiles(cleanPath))
            {
                string extension = Path.GetExtension(fileName);
                if (extension == checkEx)
                {
                    File.Delete(fileName);
                }
            }
        }

loadClean(), called at startup, simply takes the specified path as an argument and loops through all the files at that path. Since we want to delete shortcuts, we look for files with the extension “.lnk”. If we find one, we delete it.

 private void fileSystemWatcher1_Created(object sender, FileSystemEventArgs e)
        {
            clean(e.FullPath);
        }

        private void fileSystemWatcher2_Created(object sender, FileSystemEventArgs e)
        {
            clean(e.FullPath);
        }

Our two fileSystemWatchers are the same. If a file is created in the object’s path, we call the clean() function with the file’s full path as the argument.

private void clean(string cleanPath)
        {
            string checkEx = @".lnk";

            string extension = Path.GetExtension(cleanPath);
            if (extension == checkEx)
            {
                Thread.Sleep(2000);
                File.Delete(cleanPath);
            }

            SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
        }

clean() is slightly different than loadClean() in that we don’t need to iterate through all the files in a given path since we know the relevant filename from invocation. In this function, we simple check if the extension is “.lnk” and delete it. Before we delete, however, we let the program’s execution thread sleep for two seconds to give Window’s time to complete creating the shortcut, otherwise our application might not be able to access the file as it will be locked during the write process. Finally, we call the SHChangeNotify function declared earlier to force Windows to refresh the desktop to ensure that the now-deleted shortcut icons are no longer visible to the user.

All-in-all, it’s a pretty small application and doesn’t use a lot of memory and was pretty simple to implement in only a few minutes. If you want to examine the source code closer, by all means take a look at it on GitHub. if you find any issues, log it on the GitHub repository or shoot me an email at matt@75central.com.

A Lightweight EXIF Data Viewer

If you’ve read the title of this post and are wondering “what is this EXIF thing?”, then here’s a bit of information. EXIF is an acronym for EXchangeable Image File Format. And, no, I don’t know why it’s not “EXIFF”. Basically, it’s metadata tagged onto a digital image that contains information about that image. This, along with another group of metadata, IPTC, is used by digital photographers to keep track of information about such things as camera/lens settings, geographic information and copyright of a given photo.

Some photographers post their images online with this information intact, while others will strip it out when posting, keeping their secret sauce to themselves. For myself, I keep it intact as I hope it might be helpful to other photographers to understand how a photo was capture as well as being an aid in enforcing copyright. Most, if not all, photos on my photography site have this data tagged onto them and the basic data can be viewed by clicking the “View Photo Data and Location” button under the photo:

Basic EXIF data on 75CentralPhotography.Com

However, there are a lot of times that I want to view this data locally for unpublished photos on my PC. To make this easy, I wrote a simple Windows application that will display this data for a selected photo:

Main Interface

It displays the most-commonly used EXIF data on the main interface; and, if there’s GPS information embedded in the metadata, it shows a button to view the photo’s location on Google Maps. If you want all the EXIF data, you can click “File→Show All EXIF Data…” and a dialog will appear showing everything:

Everything, Everything

This application is written in VB.NET and the source code is available on GitHub. If you want to install it, you’re welcome to download it here.

A couple of installation notes:

When downloading the installer, you may get this warning:

Because this app hasn’t been installed enough times for Windows to “trust” it, Windows Defender wants you to really think about it before installing. To continue, click the three dots and choose “Keep”.

You might then get another warning:

Go ahead and click the down arrow next to “Show more” and click “Keep Anyway”. Then, navigate to your download location and doubleclick EXIFViewer.msi to install. You might get another warning:

Click More info and you’ll get the option to run anyway. At this point, the installer will launch and you can install the application.

A lot of rigmarole to install an app, but it’s for most people’s own good, as Windows tries its best to prevent you from installing malicious software using Defender Smartscreen. In this case, you’re going to have to trust me that this isn’t malicious. You have the option, of course, to review the source code at the Github repository listed above. And you know where to find me. If enough people install, Windows will eventually allow it past Smartscreen without complaint.

If you download and use the EXIFViewer and have any feedback or find any bugs, please submit an issue here or send me an email at matt@75central.com.