My First Go Project
Last winter I wanted to tackle something that has been on my mind for quite a while, years in fact. Back in the MS Windows days, circa 2006 or so, I was using Photoshop Elements as my main image processing driver. It allowed the user to decide the file naming scheme of the photos when uploading if the camera’s default wasn’t satisfactory. So I starting using a scheme based on the date the image was taken with a counter (picture number) at the end.
About the same time I was creating HDR images with my photos using a piece of software called Photomatix. HDR stands for High Dynamic Range which brings out details that would otherwise be lost in the shadows or highlights of an image. I would upload my images using Photoshop Elements, then do the first processing step with Photomatix to create the HDR image, then go back to PE and do the final processing such as sharping, contrast, white balance, etc.
Is was about 2012 when I started getting disenchanted with Windows and having to shell out $100 or more for a Photoshop upgrade. I was also looking at Windows 8 and how awful it was and decided that the if I was going to “upgrade” the OS it was going to be with Linux. One day I bought a used Laptop with Windows 7 on it at Goodwill. I also bought an extra hard disk with more capacity. When I got home I swapped out the hard disks and installed Linux Mint and never look back. Good bye Microsoft Windows.
Which left me with a problem. The amount of pictures I can take would leave me with several days of renaming and filing, something Photoshop automatically did for me. So for several years I just gritted my teeth and went to work after each upload. The vacation of 2015 was especially painful.
So when the Pandemic of 2020 hit and everyone had to hunker down I decided to do something about my filing problem. I was going to write a program myself using Python, which seems to be the darling of the programming languages of late. After studying it for a while I decided against it in favor of a static language, which Python wasn’t being a dynamic language. Eventually I decided on Go (Golang), a new language created by the good people of Google. Go is similar to C or C++ without all the headaches. I really like it and will use it again on other projects.
I’m quite proud of my application, which I named “pup” (Photo Upload Processing". After uploading my photos I open up a terminal and run “pup”. It’so fast I can actually hear it scream. It renames all my images based on the date embedded in the exif data, creates all the folders in my Pictures folder, if they don’t already exist, and moves them to their appropriate place on my hard drive. What used to take me days to do is now accomplished in a blink.
It’s my first Go program so it probably can be done better. I will probably tinker with it to streamline it. But here it is and anyone is welcome to use if they find it useful. I will eventually go on my github account
This is the code:
// pup.go (Photo Upload Processing)
// By Paul Chamberalin
package main
import (
"fmt"
"github.com/rwcarlsen/goexif/exif"
"github.com/tidwall/gjson"
"log"
"os"
"path/filepath"
"strconv"
)
func main() {
// Get the entire list of files in the current folder
var files []string
root := "."
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
files = append(files, path)
return nil
})
if err != nil {
panic(err)
}
var pictureCounter int
var previousBaseName string
var previousXposDate string
var xposDate string
var xposYearLong string
var folderDate string
for i := 1; i <= len(files)-1; i++ {
//fmt.Println("-----------------------")
// parse out the file name & its extension
var fileName = files[i]
var extension = filepath.Ext(fileName)
var baseName = fileName[0 : len(fileName)-len(extension)]
// open read file's EXIF data just to read the date the image was taken
var err error
var imgFile *os.File
var metaData *exif.Exif
var jsonByte []byte
var jsonString string
imgFile, err = os.Open(fileName)
if err != nil {
log.Fatal(err.Error())
}
metaData, err = exif.Decode(imgFile)
if err != nil {
log.Fatal(err.Error())
}
jsonByte, err = metaData.MarshalJSON()
if err != nil {
log.Fatal(err.Error())
}
jsonString = string(jsonByte)
imgDate := gjson.Get(jsonString, "DateTime").String()
xposDate = imgDate[2:4] + " " + imgDate[5:7] + " " + imgDate[8:10] + " "
folderDate = imgDate[2:4] + "-" + imgDate[5:7] + "-" + imgDate[8:10]
xposYearLong = imgDate[0:4]
// Makes sure the picture number, calculated by pictureCounter, (1) either
// stays the same based on same shot/different file extension and (2) increments
// up or reseets back to 1 based on date taken.
//fmt.Println(baseName + " | " + previousBaseName)
//fmt.Println(xposDate + " | " + previousXposDate)
if baseName != previousBaseName {
if xposDate == previousXposDate {
pictureCounter++
} else {
pictureCounter = 1
}
}
pictureNumber := strconv.Itoa(pictureCounter)
switch len(pictureNumber) {
case 1:
pictureNumber = "00" + pictureNumber
case 2:
pictureNumber = "0" + pictureNumber
default:
pictureNumber = "" + pictureNumber
}
newFileName := xposDate + pictureNumber + extension
fmt.Println("Old: " + fileName + " New:" + newFileName)
//Create a directory tree based on date taken
home, err := os.UserHomeDir()
homeFolder := home + "/Pictures"
yearLongFolder := "/" + xposYearLong
yearLongPath := homeFolder + yearLongFolder
folderDateFolder := "/" + folderDate
folderDatePath := yearLongPath + folderDateFolder
pictureNumberFolder := "/" + pictureNumber
pictureNumberPath := folderDatePath + pictureNumberFolder
err = os.Mkdir(homeFolder, 0755)
if err != nil {
//log.Fatal(err)
}
err = os.Mkdir(yearLongPath, 0755)
if err != nil {
//log.Fatal(err)
}
err = os.Mkdir(folderDatePath, 0755)
if err != nil {
//log.Fatal(err)
}
err = os.Mkdir(pictureNumberPath, 0755)
if err != nil {
//log.Fatal(err)
}
// Rename all image files based on date taken and move them to their
// place in the directory tree
newJPGPath := folderDatePath + "/" + newFileName
newNEFPath := pictureNumberPath + "/" + newFileName
switch extension {
case ".JPG":
err = os.Rename(fileName, newJPGPath)
if err != nil {
log.Fatal(err)
}
case ".NEF":
err = os.Rename(fileName, newNEFPath)
if err != nil {
log.Fatal(err)
}
default:
fmt.Println("Invalid Extension")
}
previousBaseName = baseName
previousXposDate = xposDate
}
}