Building a command line photo tagger using Docker, .Net and ExifTool
Tags: geotagging, docker, dotnet, gps
Published: 2024-06-09 19:33:38

In this post, I will cover what photo Geotagging is, my use of the concept over time and a brief discussion of a basic tool I put together for the travels over the past few weeks. 

## Part 1 - Geotagging

Geotagging (adding location information as a metadata) images is quite fun allowing us not only revisit the memories but also where they were and even explore further in the future. This has been a transparent feature offered by mobile phones and devices. A concept that has been ubiquitous for some time.

Over the years, I have used various cameras some of which allowed native Geotagging support using accessories that allow linking with bluetooth GPS receivers and some without such feature, meaning having to use local GPS loggers that have sufficient storage and battery life to be placed in a backpack or even in the camera strap. Depending on the approach, the workflow and pros / cons can be very different.

### Using mobile phones / devices with built in support

- Convenient / easy to use as it works out of the box.
- Improved accuracy as the operating systems can utilise appropriate algorithms to reject outliers and provide best estimate of a position at a given time.
- Image Quality of the mobile phones improved a lot but still not comparable to a purpose built device.

### Using cameras with native GPS support

- Provide a seamless experience with potential impact on battery
- Photos tagged out of the box
- Able to use preferred systems / lens combinations and so on
- It is a niche market and still not many option (for example a small mirrorless that also has built in GPS or a connection port for external accessories)
- Depending on the setup, if the decide was off, time to first fix can be slow ending up with some images without GPS tags.
- I have used this approach for a few years around early 2010s utilising a receiver on the camera and an external GPS accessory as outlined in the following blog post: 
  - [Unleashed GPS Bluetooth Geotagging Solution for Nikon DSLRs - Terry White](https://terrywhite.com/unleashed-gps-bluetooth-geotagging-solution-for-nikon-dslrs/)
  - [Unleashed D90 Instruction Manual](https://www.foolography.com/wp-content/uploads/instructions-en-d90.pdf)

### Using any camera and an offline GPS logger

- A specific hardware or even a mobile phone can be used as a gps logger.
- During post processing, images from camera might be matched to the entries from the logs and metadata added for the images where there is a good match.
- Still need to be aware of battery implications on the loggers.
- GPS data can be noisy and might need to deal with outliers.
- Plenty open source applications allow tagging images using these logs.
- This has been my go to overall with cameras ranging from bridge format, Mirrorless and pocket cameras too with success.
- After a few years of hiatus, back again and this is the focus of this post.

[ExifTool by Phil Harvey](https://exiftool.org "ExifTool" target="_blank") is an open source cross platform tool to manage [exif data](https://www.canon.co.uk/pro/infobank/all-about-exif/) on images. Given that we do not want to corrupt our images when manipulating metadata, using a tried and tested approach will mean peace of mind in contrast to building oneself or using obscure packages for the functionality.

### Mobile app companions

It is also possible to use companion apps for the cameras and use tagging using this approach. The ones I have come across has not been productive for my style and therefor not getting into in details. Here is some information about [FujiFilm Xapp](https://www.cined.com/fujifilm-xapp-review-finally-a-good-camera-companion-app/)

### Current workflow

My current workflow involves the following:

- [i-GotU GT600](https://www.filesaveas.com/igotu_gt600.html)
  - This is the item on the left on the image below next to AAA battery for comparison.
- A pocket camera (Sony RX100 VII)
- A mobile phone to manage the logger (change settings, export logs, clear space)
- Google Drive to share the logs with my laptop
- Adobe Lightroom CC
- And the command line tool subject of this post
  - I used to have an older version of I-GotU and the supplied desktop application also did a great job at geotagging photos and part of my previous workflows.


![Various GPS loggers.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b95nsvqj65v67nsst0rh.jpg)

The two on the rights are the ones I probably used around early 2010s and late 2000s and the left is my current setup. 

## Part 2 - Combining System.Commandline, Docker and ExifTool for adding GPS tags to images in bulk

### System.Commandline

[System.Commandline](https://learn.microsoft.com/en-us/dotnet/standard/commandline/) is a handy library that allows us to build Command Line applications that can be packaged as usual clit tools or even as dotnet tools that can be used like any other botnet tool. 

It is still under preview and the api can have breaking changes. Although parsing command line arguments is not a challenge and has been possible regardless the frameworks we used, the benefit of this approach is to use certain conventions when we are building our application and focus on the functionality instead of boilerplate / plumbing code.

For instance, with only the code below, we define what the command name is, what the arguments are and if they have defaults and at runtime, if this command is triggered, all the arguments will be passed without further effort from us. Using a simple Dependency Injection (DI) approach also allows registering these as hierarchical commands where we can almost define our own DSL in terms of interaction with the CLI. In our case this is: `photo-tool metadata gps add --arguments`. Given this is a photo tool, we could then add a new command that maybe resizes the images and outputs to another directory and the execution could look like: `photo-tool manipulate scale --scale-factor xyzzy --source-directory abc --out-directory def` For those familiar with Kubernetes, this is similar to how `kubectl` cli tool is organised this idea has originated from my ex colleague Dom so credit goes to him. 

So in our case, the handler for removing tags from images looks like below:

```c#
public class RemoveGpsTagCommand : MetadataCommand 
{
    private readonly IPhotoTagger _photoTagger;
    private readonly ILogger<RemoveGpsTagCommand> _logger;

    public RemoveGpsTagCommand(IPhotoTagger photoTagger, ILogger<RemoveGpsTagCommand> logger) : base("remove", "Removes location metadata from the images.")
    {
        _photoTagger = photoTagger;
        _logger = logger;
        
        // example usage: dotnet PhotoTool.Cli.dll metadata gps remove --image-directory "/where-images-are"
        AddOption( new Option<string>("--image-directory",  "Location for the images to clean up."));        
       
        // The default naming convention is to convert the option name to camel case and remove the leading dashes.
        //     example: --image-directory -> imageDirectory
        Handler = CommandHandler.Create(async (string imageDirectory) =>
        {
            await TagImages(imageDirectory);
        });
    }

    private async Task<int> TagImages(string imageDirectory)
    {
       ....
    }
}

```

### Why Docker?

As discussed, a cli tool can be used natively in a few ways:
- As an executable
- As a custom dotnet tool
- running from source with dotnet run
- Or with Docker

Given that in essence this is just a wrapper around ExifTool, the cli only works if ExifTool is installed. So we can always ensure the tool is installed for our Operating System / Distribution or we can build an image that contains our application as well as ExifTool and runs on any Operating system / processor architecture such as arm64, amd64, arm v7, v8 and so on.

And this is the approach adopted here. The tool can be run using docker and mounting relevant directories as below:

```bash

# To add tags
docker run -it --rm -v /local-photo-directory:/app/import -v /local-logs-directory:/app/gps syamaner/photo-tool:1 metadata gps add --image-directory "/app/import" --gps-log-directory "/app/gps" --max-matching-seconds 200

# To remove tags
docker run -it --rm -v /local-photo-directory:/app/import syamaner/photo-tool:1 metadata gps remove --image-directory "/app/import" --gps-log-directory "/app/gps" --max-matching-seconds 200

```

Current version is only linux/arm64 but next step will be building with multi architecture support.

## What does it do?

As seen below with the screen capture from LightRoom, once we tag and import, then the location of the photo can be seen on map. Online tools such as Flickr also support this natively. 

![AC/DC Sevilla and location on map in Adobe Lightroom](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irunihiiv3jlo3b7uxw6.png)

Similarly [Flickr map view](https://www.flickr.com/map/?fLat=25.03245&fLon=121.518544&zl=13&everyone_nearby=1&photo=5420786045) also displays ours and other public images in a given location. 


## Next Steps

- Currently only csv format exported from i-GotU device is supported.
  - I had [GPX](https://wiki.openstreetmap.org/wiki/GPX) support as well but removed so next steps will involve:
    - Make csv parsing configurable so mappings can be changed at runtime
    - Bring back GPX Support so that this could be a wider supported format.
- I have only tested with Sony .arw raw files and will add tests for other raw / compressed formats
- GPS logs are noisy, so depending on the use case (moving vehicle. walking around, mostly stationary, we might use different algorithms to eliminate the outliers and extrapolate the positions so this will be a topic.
  - This is something I have implemented back at university around 2004: [Determining the locations visited by GPS users: a clustering approach](https://salford-repository.worktribe.com/output/1442237/determining-the-locations-visited-by-gps-users-a-clustering-approach)
  - Image below depicts this issue. Even in seemingly ideal circumstances, there can be several outliers over a period of time without moving much.


![Noisy GPS reading while stationary for several hours in open air](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ca1pjd8p8en38qestqn5.png)

## Links

- [Source Code](https://github.com/syamaner/photo-tool)
- [DockerHub](https://hub.docker.com/repository/docker/syamaner/photo-tool/general)
- [ExifTool by Phil Harvey](https://exiftool.org)
- [A nice article demonstrating a simple approach to System.Commandline Dependency Injection](https://endjin.com/blog/2020/09/simple-pattern-for-using-system-commandline-with-dependency-injection)