Category Archives: Software

Photo Organization Part 1: Importing Existing Files

Over the years, my wife and I have accumulated a large volume of digital photos. Nary a vacation has gone by that didn’t result in 1000+ photos of every landmark, vista, and museum artifact that we discovered during our travels.

As it turns out, the Continental Hotel from John Wick is actually a sushi bar

Unfortunately, the majority of these photos have ended up haphazardly organized in various folders on my media server, all with different directory structures and naming methodologies, which makes it difficult to lay my hands on a particular photo from some time in the past.

One of the projects that I’ve decided to tackle this year is to come up with some method by which to rein in this madness and restore some semblance of order to the realm.

I’m not sure exactly what tools or processes I will use to tackle this problem, but I figure that the best way to start is to split it up into smaller, more manageable sub-problems.

This, then, is the first in what I hope to be a series of posts about organizing my family photo collection. Subsequent posts will (hopefully) deal with a pipeline for importing new photos, establishing a reliable offsite backup solution, and maybe even experimenting with some deep learning to automatically tag photos that contain recognizable faces.

Why not use [Insert Cloud Service Here]?

“But,” I hear you protest in an exasperated tone of voice, “you could just upload all of your photos to Google Photos and let The Cloud solve this problem for you.”

While it’s true that there are a number of cloud providers that offer reasonably-priced storage solutions, and that some of them even use the metadata in your photos to impose some kind of organization solution, I have a few concerns with these products:

  1. They cost money: Nothing in life is free, at least not if you have more than a few gigabytes of it. The largest of my photo folders contains around 70GB of files, and with the recent arrival of our son, we take new photos at a heretofore unimaginable clip. I already have a media server, and storage on it is effectively free.
  2. My metadata isn’t complete/correct: Garbage in, garbage out, as they say. Most (all?) of the cloud storage solutions that I’ve seen that purport to organize your photos will do a messy job of the task if the metadata on your photos is incorrect or missing. Any tool that I use will need to correct for this problem.
  3. Google is a creep: The same is true of Facebook et al. I’d rather generate less digital data for the multinational companies that control our modern world to indiscriminately slurp up and process in hopes of getting me to click on a single ad, thank you very much. Doubly so if the storage provider in question is going to use facial recognition to tie a name to faces in my photos, especially photos of my son.

Organizing Files with Elodie

My initial inclination, when considering the problem of sorting many gigabytes of photos into a reasonably logical folder structure was to write a script that would read the EXIF data out of the file, interpret it, and use it to determine where the file belongs.

But the decrepit fire station from Ghostbusters? Actually a fire station.

In one of the better decisions that I’ve made so far this year (hey, it’s only January), I decided to take a look around GitHub to see if somebody had already written a script to do this work, and man, am I glad that I did.

As with most things that seem simple, it turns out that the task of reading EXIF data can get really complicated in a hurry. There are a bunch of different historical format differences, every camera manufacturer implements some number of custom extensions to the format, etc.

Enter Elodie

Elodie is a cutely-named tool with an even cuter mascot that uses exiftool to read the metadata out of your photos, and then sorts them into a configurable folder structure based on that data. If your camera or phone wrote GPS coordinates indicating where the photo was taken, Elodie can even query the MapQuest API to translate those coordinates into a human-readable place name that is added to the directory structure.

The documentation is comprehensive, albeit brief, but I’ll include a handful of the commands that I used in this post just to demonstrate the workflow that I ended up with.

There are basically two major operations in Elodie: import and update. The former is used to move pictures from some source directory into the target directory, reading their metadata, renaming them, and organizing them within a configurable directory structure along the way. The latter, meanwhile, is essentially used to correct mistakes that were made during the import process. It lets you correct the date, location, or album metadata for previously imported files, and appropriately re-sorts them into the directory hierarchy.

The command for importing existing files into your collection is simple:

~/elodie $ ./elodie.py import --trash --album-from-folder --destination=/mnt/media/Pictures/ /mnt/media/Pictures.old/Montreal\  Trip/

In this case, I’m importing vacation photos from /mnt/media/Pictures.old/Montreal Trip. The destination folder is /mnt/media/Pictures, and I’m using the --album-from-folder option to tell Elodie that I want it to keep all of the pictures in this batch together in a folder called Montreal Trip in the destination directory. We went on this trip in September of 2010, so the resulting folder structure looks like this:

mnt/
├─ media/
│  ├─ Pictures/
│  │  ├─ 2010/
│  │  │  ├─ September/
│  │  │  │  ├─ Montreal Trip/
│  │  │  │  │  ├─ 2010-09-08_19-29-44-dsc00279.jpg
│  │  │  │  │  ├─ 2010-09-09_20-15-44-dsc00346.jpg
│  │  │  │  │  ├─ ...

There may be other pictures that were taken in September of 2010 in the September folder, but they won’t be sorted into the Montreal Trip folder unless they are marked as being a part of the Montreal Trip album.

It should be noted at this point that Elodie goes to great lengths to avoid maintaining a database of imported files. Instead, it reads metadata from and writes metadata to the image files that it is organizing. This ensures that the resulting directory structure is as cross-platform and broadly compatible as possible without locking your photo collection into a proprietary file format.

The Men in Black Headquarters at 504 Battery Drive

Elodie does offer one database-lite feature that helps to detect bit rot: The generate-db operation records a cryptographic hash of every photo in your collection into a file. Months or years down the road, you can check if any of the files in your collection have become corrupted by running the verify operation. This will recompute the hashes of all of the files in your collection, compare them against the previously recorded values, and let you know if anything has changed.

One place where not having a database falls short is if a small handful of images within a directory that contains hundreds or thousands of vacation photos have incorrect or missing EXIF data. In this case, it’s possible for those photos to be written to the wrong place in the target directory structure, and if you don’t catch the problem during the import, you’re not likely to find and fix the mistake. If Elodie were to maintain a database that tracked the source and destination locations for every imported photo, these mistakes would be easy to find. This in turn means that importing a large existing photo collection with Elodie becomes a job that requires human supervision.

Here’s a brief rundown of some of the issues that I ran into while importing a large collection of existing files:

  • Missing metadata: Some photos, particularly those taken by pre-smartphone cameras, didn’t have the date on which the photo was taken in their EXIF data. When it encounters this problem, Elodie falls back to the lesser of the file created or file updated date. Because of the way that I managed my import, all of these files ended up in Pictures/2021/January/Unknown Location/, but if you didn’t accidentally overwrite the file created date on all of your photos as a part of your import process, Elodie may put them into an unexpected location in your target directory tree.

    If you happen to be importing an album (i.e. a bunch of photos that were taken as a part of a named event) and your target directory structure includes the album name, you can find the improperly organized photos by running find /path/to/photos -type d "album name" -print to find directories in your collection that have the same name as the album. Once found, you can use Elodie’s update operation to fix the problem:
    ~/elodie $ ./elodie.py /update --time="2014-10-22" --album="album name" /path/to/photos
  • Incorrect metadata: In many ways, incorrect metadata is worse than missing metadata, because it causes photos to be organized into unexpected locations. As far as I can tell, the cause of this problem was a pre-smartphone camera that had its date and time set incorrectly. Remember when you used to have to set the date and time on all of your electronics? Boy, that sucked.

    You’ll know this is the problem if you are importing photos from a vacation that you took in 2012, but they’re inexplicably being put into the 2008 directory. In this case, you can use exiftool, the underlying library that Elodie uses to read and write metadata, to add or subtract some number of years, months, days, or hours to or from the dates recorded in the photos’ EXIF data.
  • Geotag lookup doesn’t appear to work: As mentioned above, Elodie has the ability to convert GPS coordinates found in the EXIF data of photos into human-readable place names by way of a call to the MapQuest API. This, of course, only works if your camera was aware of its location at the time that the photo was taken, which wasn’t really a thing in the pre-smartphone era.

    This isn’t really a problem for vacations, as I can import all photos from a trip into an album, and that album name will be used in the folder structure that Elodie creates. For example, if you import photos from a folder called Egypt using the --album-from-folder option, they’ll end up in a directory structure like this: 2012/March/Egypt/.

    It does, however, get annoying for photos that were taken closer to home with a non-GPS-aware camera. These all get sorted into a year/month/Unknown Location/ directory. I can’t find anything in the Elodie docs or source code that allows this behaviour to be changed. I would rather that these photos end up in the root of the year/month/ folder, because I think that the extra Unknown Location directory is, well, less than helpful, but I recognize that this is a matter of preference. For now, I think I’ll solve this problem by writing a quick script to move these photos as I see fit.

What’s next?

Even with the help of a tool like Elodie, reorganizing tens of gigabytes worth of photos is not a quick task. I can hear the fans on my Drobo humming, and am only too aware that this kind of mass file migration is a great way to find the breaking point of old hard drives.

This isn’t from a movie. It’s just a cool shot looking North from the top of the Freedom Tower

Once I’m done importing all of the pictures that I already have, I’ll move on to figuring out an import pipeline for photos that I haven’t yet taken. Off the top of my head, it needs to be mostly automatic, work well with the iPhones that my wife and I use to take most photos these days, and should ideally email me the Elodie log so that I can see what photos ended up where and whether or not I need to manually correct any mistakes.

I’m hopeful that once I get around to importing photos that were taken with a smartphone that has internet time and some notion of its location, the metadata problems that I catalogued above will become the exception instead of the rule.

Once I figure out that next step, I’ll write about it here. In the meantime, go organize all of those photos that are cluttering up your media server. You’ll feel better once you do. I promise.

Leave a Comment

Filed under Software

CAT Control with N1MM+ and a Yaesu FT-450D

When participating in amateur radio contests, my logging software of choice is N1MM+. This tidy little logger is highly optimized for contesting, automatically updating its user interface to prompt the user for the information required to log a valid contact.

One major quality of life improvement for me has been rigging up N1MM+ to talk to my HF rig via CAT control. This allows the logging software to automatically transmit pre-recorded macros on my behalf at the appropriate times during a contact, which saves me from having to yell my callsign over and over again when trying to break through a pileup.

Because every radio is different, configuring N1MM+ to control your rig can be a bit of a bear. Below are the steps that I followed to get things working:

Tell N1MM+ What Kind of Radio You Have

From the main window of N1MM+, select Configure Ports, Mode Control, Winkey, etc… from the Config menu.

In the Configurer window that appears, activate the Hardware tab. Select the COM port that your radio is attached to from the Port dropdown, and the make and model of your radio from the Radio dropdown.

Next, click on the Set button under the Details header. In the window that pops up, select the baud rate of the serial connection with your radio from the Speed dropdown.

Click the OK button twice to dismiss both windows and navigate back to the main window of N1MM+.

Customize the CAT Commands that N1MM+ Sends to your Radio

From the Config menu, select Change CW/SSB/Digital Function Key Definitions > Change SSB Function Key Definitions. In the SSB Message Editor window that appears, you can edit the contents of the config file that controls the CAT commands that N1MM+ sends to your radio at different stages of the contact.

When running (i.e. sitting on a particular frequency calling CQ and waiting for other operators to call me back), I execute the VM1TX function on my Yaesu FT-450D, which transmits a pre-recorded macro that says something like “CQ Contest CQ Contest, Victor Alpha Three Juliet Foxtrot Zulu”.

To execute this command, I have to configure N1MM+ to execute the {CAT1ASC PB7;} command. CAT1ASC tells N1MM+ to send an ASCII command down the serial connection, and PB7; tells my rig to execute the VM1TX function, which transmits the pre-recorded macro.

Similarly, when searching and pouncing (i.e. tooling around the band looking for other operators who are calling CQ), I execute the VM2TX function on my radio, which transmits a different pre-recorded macro says my callsign. I use this when answering another operator and waiting for them to acknowledge me.

Executing this command is much the same as the previous. I configure N1MM+ to execute the {CAT1ASC PB8;} command, which tells my rig to execute the VM2TX function, transmitting the pre-recorded macro.

For reference, here’s the contents of my entire SSB Message Editor config file:

#
# SSB Function Key File
#
# Edits may be necessary before using this file
# Use Ctrl+O in the program to set the Operator callsign
#
###################
#   RUN Messages
###################
F1 CQ,{CAT1ASC PB7;}
F2 Exch,{OPERATOR}\CqwwExchange.wav
F3 TNX,{OPERATOR}\Thanks.wav
F4 {MYCALL},{CAT1ASC PB7;}
# Add "!" to the F5 message if you are using voicing of callsigns 
F5 His Call,
F6 Spare,
F7 QRZ?,{OPERATOR}\QRZ.wav
F8 Agn?,{OPERATOR}\AllAgain.wav
F9 Zone?,{OPERATOR}\ZoneQuery.wav
F10 Spare,
F11 Spare,
F12 Wipe,{WIPE}
#
###################
#   S&P Messages
###################
# "&" doubled, displays one "&" in the button label
F1 S&&P CQ,{CAT1ASC PB8;}
F2 Exch,{OPERATOR}\S&PExchange.wav
F3 Spare,
F4 {MYCALL},{CAT1ASC PB8;}
# Add "!" to the F5 message if you are using voicing of callsigns 
F5 His Call,
F6 Spare,
F7 Rpt Exch,{OPERATOR}\RepeatExchange.wav
F8 Agn?,{OPERATOR\AllAgain.wav
F9 Zone,{OPERATOR}\RepeatZone.wav
F10 Spare,
F11 Spare,
F12 Wipe,{WIPE}

Note of course that unless you also run a Yaesu FT-450D, lines 10, 13, 28, and 31 will need to be customized to send CAT commands appropriate to your radio.

Reading through the file may also suggest to you that there is more than one way to skin this cat; indeed, it is possible to configure N1MM+ to key your radio and then play a WAV file from your computer, assuming that you pipe the audio from your computer into your rig. This can be a solution for radios that don’t allow you to record voice macros, or for operators who want to use a wider range of macros than their radio supports.

Good luck and happy contesting!

4 Comments

Filed under Amateur Radio, Software

CAT Control from Log4OM 1.x Using hamlib

In a previous post, I wrote about using hamlib to control my Yeasu FT-450D from the Windows command line.

This time, we’ll look at integrating hamlib with Log4OM to achieve cat control from within the logging software, primarily so that I don’t have to record the frequency whenever I enter a new QSO.

If you haven’t already installed hamlib, you’ll want to follow the instructions in the previous post. If you can run the rigctl examples in that post, you should be good to go.

Configuring Log40M

First, we need to tell Log4OM to use hamlib. Unlike N1MM+, Log4OM does not come with the ability to talk to your radio, and it needs some help to do so.

From the Settings menu in Log4OM, choose Options, and in the dialog box that appears, select the Cat & Cluster tab. We only care about two controls on this tab:

  • Under the CAT SOFTWARE heading, choose hamlib
  • Under the CAT & Cluster heading, select the Open CAT on program start checkbox

Click on the big floppy disk in the bottom right corner to close the dialog box.

Back on the main screen of Log4OM, click on the icon that looks like a pair of headphones in the toolbar:

Why it’s a pair of headphones is beyond me. While I do wear headphones while using my HF radio, they don’t have anything to do with cat control.

Anyway, clicking on that button will open the Log4OM Cat dialog box, where you can configure cat integration with your radio. There are three settings that you want to change in this window:

  • Select the make and model of your rig from the RIG Model dropdown box. My primary HF radio is a Yaesu FT-450D, and I use the Yaesu FT-450 0.22.1 Beta | 127 profile.
  • Select the COM port that your radio is connected to. My radio typically connects to COM4, but that can change if I plug its serial cable into a different USB port
  • Choose the appropriate Baud Rate for your radio. My radio is set to 9600 baud, but this can be changed in its menu. The radio and Log4OM have to be expecting the same Baud Rate for communication to be successful

Once configured, press the Open button in the bottom right hand corner of the dialog box to test and save your settings. if the CAT Status indicator in the bottom left corner of the dialog box turns green, you can close the box.

So What’s the Point?

Once you configure cat control, Log4OM will communicate with your radio as you use it. Most notably, the frequency that your radio is tuned to will appear in the top right-hand corner of the logger, and will automatically record that frequency in any new QSOs that you enter.

Cat control also allows Log4OM to change your radio’s transmit/receive frequency, and to activate the radio’s transmitter. This in turn allows you to connect your radio’s audio input and output to your computer, and play pre-recorded audio snippets from the logger, which can be very useful while contesting.

Leave a Comment

Filed under Amateur Radio, Software

Using ImageMagick to Prepare Images for Upload on Windows

A few years ago, I wrote a short post detailing how to automatically rotate, resize, and strip EXIF data from the images that I upload to this website.

At the time, I was working on Linux, so the instructions in that article target Linux-based operating systems. Over the intervening years, I’ve switched back to running Windows 10 on my home laptop, so I figured it was time to update my original instructions to target my new platform of choice.

Installing ImageMagick

One of the reasons that I now run Windows at home (or at least one of the reasons why I don’t mind running Windows at home as much as I once did) is that the Windows command line experience has improved by leaps and bounds over the past few years.

For command line goodness in Windows, I run PowerShell inside of ConEmu, and I use Chocolatey to install and manage command line utilities.

Luckily ImageMagick is available for Windows, and you can install it via Chocolatey as follows:

$ choco install imagemagick.tool

Note that I’m purposefully installing the portable release here, because it includes all of the command line tools in addition to the main ImageMagick GUI application.

The installer modifies your $PATH variable, so you’ll have to run refreshenv once it’s complete.

Modifying Images with Mogrify

As in the original tutorial, I still use mogrify to prepare my images for upload.

Start by copying the images that you want to use into a temporary folder. Mogrify is going to modify them in place, so you definitely don’t want to work against the original images.

Next, run the following command from PowerShell/cmd in the temporary folder:

mogrify -auto-orient -resize 584x438 -strip -quality 85% *.jpg

This command will re-orient and resize your images, strip any EXIF data from them, and re-encode them as jpegs at 85% quality. The full manpage for mogrify can be found on the ImageMagick website.

Leave a Comment

Filed under Software

Using hamlib to Control my Yeasu FT-450D

A couple of years ago, I became VA3JFZ after studying for and passing my amateur radio exam. Since then, I have been building out my shack (ham radio enthusiast lingo for the place where you keep your rigs, er… radios).

As my setup has matured, I’ve started to look for interesting ways to interconnect my equipment. Most amateur radio operators use software packages called loggers to keep track of who they’ve talked to when on the air. I use two different loggers: log4OM is my everyday driver, and N1MM+ is for contesting.

Some of my recent contacts, or QSOs, as recorded in log4om

While contesting, I got used to N1MM+ automatically reading the frequency from my HF (that’s high frequency) radio, making for one less thing that I have to enter into the logger as I work contacts. While trying to figure out how to get log4om to do the same thing, I stumbled on an open source project called hamlib.

You see, while most modern rigs provide some form of CAT (that’s computer aided transceiver) control via an RS-232 serial port, every manufacturer’s radio responds to a slightly different set of commands. The goal of the hamlib project is to create a common interface that any piece of software can use to talk to all kinds of radios without having to re-implement all of their individual peculiarities.

After downloading the release 3.3 and running the exe file, I added the C:\Users\Jonathan Fritz\AppData\Roaming\LogOM\hamlib directory to my PATH and opened Powershell, where the following command started an interactive terminal:

$ rigctl --m 127 --r COM3 --serial-speed=9600

Let’s break down the arguments to this command:

  • rigctl is the name of the program, pronounced “rig control”
  • --m 127 tells rigctl that my radio is a Yeasu FT-450D
  • --r COM3 says that my radio is connected to the COM3 port; and
  • --serial-speed=9600 tells it that my radio expects serial commands at a rate of 9600 baud.

It’s worth noting that your radio might appear on a different COM port when connected to your computer via a RS232 to USB cable, and that you may need to adjust the baud rate of the serial connection to match the settings in your rig’s config menu.

You can find out what COM port your radio is connected to in Windows > Control Panel > Device Manager

Once you’ve started rigctl, there are a few interesting commands that you can run.

Get the frequency of the radio:

Rig command: f
Frequency: 7301000

Get the mode that the radio is in:

Rig command: m
Mode: LSB
Passband: 3000

Ok, that’s a neat party trick, but what’s the point? Well, rigctl can also be used to change your radio’s settings, and can be run in a non-interactive mode where commands are read in from a file.

Non-interactive mode

I started by writing my commands out to a file:

$ echo M LSB 3000 \r\n F 7150000 > 40m.txt

Once again, let’s break this command down:

  • echo prints whatever comes after it to the terminal
  • M LSB 3000 tells the radio to set the mode to lower sideband with a passband of 3000Hz
  • \r\n is a line break in Windows, which separates two commands from one another
  • F 7150000 tells the radio to set the frequency to 7.150.00MHz, the middle of the 40M band
  • > pipes the output of the echo command (the string M LSB 3000 \r\n F 7150000) into a file on disk
  • 40m.txt is the name of the file to pipe the command into

The result is a file called 40m.txt containing two commands that will set the radio to LSB mode and set the frequency to 7.150.00MHz.

Now, we can execute those two commands by running this command:

$ rigctl --m 127 --r COM3 --serial-speed=9600 - < 40m.txt

The first four arguments here are the same ones that we used to open the interactive terminal above. The remaining arguments are:

  • - tells rigctl to read the remaining commands from stdin, letting us pipe them in from a file
  • < the opposite of >, pipes commands in from a file instead of out to a file
  • 40m.txt the name of the file containing the commands that we want to send to the radio

Running this command will set the radio’s mode and frequency, initializing it for operations on the 40m band.

The rigctl manual contains a bunch of other really interesting commands, including the ability to activate the rig’s PTT (push to talk) switch, which could be used to write a script that puts the radio into transmit mode before playing pre-recorded message. That sounds like a very useful feature for contesting.

Finally, if there’s something that your radio can do that rigctl can’t, you can always use the w command to sent CAT control strings directly to the rig. The control strings for most rigs can be found on the manufacturer’s website.

73 (that’s amateur radio speak for “best regards”), and enjoy your newfound power.

1 Comment

Filed under Amateur Radio, Software

Image Rotation and EXIF Data

If you’ve looked at some of my earlier posts in Firefox, you may have noticed something odd about them. Some of the photos will appear to be rotated in the wrong direction, but if you view the same post in Chrome or on an iPhone, they’ll appear to be rotated correctly.

When I’m working in my garage, I take all of my photos on my iPhone, and I’ve recently found out that when I take a picture in landscape orientation (with the home button held to the left or the right, rather than the top or bottom of the screen), iOS doesn’t rotate images to match the orientation of the phone when it writes them to storage. Instead, it writes the image data in the same orientation as the camera, and saves some time by writing the orientation information separately into the metadata that it attaches to the image. This lets the device take pictures faster, but means that some of the images have to be rotated before I post them.

Unfortunately, I didn’t notice this behaviour up until now, thanks to a series of unfortunate circumstances:

  1. As previously mentioned, Chrome looks at images’ EXIF data and re-orients them as necessary
  2. The file browser on my Ubuntu 14.04-LTS machine also auto-corrects for iOS’ sloppy behaviour
  3. Unlike the other parts of my toolchain, WordPress ignores EXIF orientation information when images are uploaded, taking the stance that it’s not interested in automatically modifying users’ images.

So what to do? Well, I could open each of the images in an image editing program like Pinta, apply the necessary rotation and resize the file. On the other hand, that sounds boring and time consuming, and I’d rather figure out how to do the job automatically.

Since we’re modifying images, the go-to tool in our kit will be an amazing suite of image editing tools called ImageMagick. It’s 100% free and open source, runs damned-near anywhere, and can do pretty much anything that you might want to do with an image file.

I’m working on an Ubuntu system, so I’ll install ImageMagick like this:

$ sudo apt-get install imagemagick

If you’re on some other platform, you can read up on how to download the tool here.

Now we just have to string together some command line switches to do what we want. Here’s what I came up with:

$ mogrify -auto-orient -resize 584x438 -strip -quality 85% *.jpg

The command that we’re running is called mogrify, and will modify your images in place, so you’re going to want to cd into a directory that contains a copy of your images before running it.

After the mogrify command, we specify a number of command line switches that change how it behaves:

  • -auto-orient fixes the iOS image orientation problem described above
  • -resize resizes the image to the size provided (specified as maximum width x height in pixels), and respects the source image aspect ratio if it can’t make the image exactly that size
  • -strip removes all EXIF data and other metadata from the image, including timestamps, GPS location data, and information about the device that took the image. It also removes the pesky orientation data that caused Chrome and Safari to automatically correct for iOS’ behaviour, which is important, because we’ve just rotated the image, so if we leave the metadata in place, the image might actually appeared double rotated after I post it
  • -quality specifies the compression level to use when resizing the image (specified as a percentage between 0 and 100)

The last thing in the command, after all of the command line switches, is *.jpg, which tells mogrify to do all of the previous steps on every file in the current directory that ends with .jpg.

Once I’ve run this command, I’m left with a directory full of properly oriented images that are the right size for posting on my website, with all of the private/identifying metadata stripped out of them.

Handy, right?

2 Comments

Filed under Software