Removing JavaScript

14 May 2019

My website has always been both a way for me to show my work & hobby for me to tinker with. Over the last year I’ve moved to self-hosting a staticly generated site and customized various themes within an inch of their lives.

Today I’m changing things up again. This time to remove JavaScript.

Inspired by a post by Brent Simmons talking about a web browser that disables JavaScript by default:

If it‘s the opposite — if I have to blacklist instead of whitelist — then I’d be constantly blacklisting. And, the first time I go to a site, it gets to run code before I decide to allow it.

It got me thinking about my website with its fancy masonry galleries, responsive navigation bar, and syntax highlighting and whether I needed JavaScript. As it happens: I don’t.

Unfortunately I am not a web developer, so I had to rely on cleverer people. After a lot of research and false starts1 I found some projects I was able to use:

  1. Masonry Gallery: Driveway
  2. Responsive Navigation: Luxbar
  3. Syntax Highlighting: Built into Hugo

With these components in place I also had a mostly fresh start and got to clean up the look of my site a little. The galleries are now first class layouts and all of the little overrides I’d made are integrated in the Hugo theme directly.

There are a few limitations to removing JavaScript. The main one for me is it reflows my gallery order. It’s not a huge issue, and it’s something that could be fixed with a small script, but I didn’t set out to remove almost all JS. I can always backpedal a bit, but it’s nice to not be reliant on it just to make my site work at all.

Blog Updates

I’ve also moved away from pagination on my blog. The main blog page will show a list of recent posts and the rest are at the new archive page.

  1. Shockingly, not everything posted on Stack Overflow works ¯\(ツ)[return]

Adapting a Digital Back to a 6x9 Field Camera

29 Apr 2019

Sometime last year I was up at my favorite camera shop, Seawood Photo in San Rafael, and saw a little wooden field camera, partially hidden, sitting up on the top shelf behind the counter. I asked to see it and instantly knew I had bring it home.

This little wooden 6x9 field camera is a Tachihara Shirom. While the movements are a bit limited both standards can be moved together to provide good support for wide angle lenses. The ground glass, which conveniently flips out of the way, is designed to take a Graflok roll film back, which is great if you want to shoot film. However, digital backs won’t fit the 2x3 Graflok back due to the where the film sits relative to the locking mechanism.

Luckily the whole ground glass assembly is removable! All one needs is an adapter to hold the digital back, easy, right?

Designing an Adapter

The initial version of the adapter was designed to use the original ground glass. I borrowed the Mamiya mount from an Arca adapter and got to work in CAD designing the adapter, which lead to the first problem: registration.

It’s difficult to find specs on the registration of Graflok film backs and nigh impossible to find measurements for digital backs. This left me with a pair of calipers and a lot of guess-work. After several attempts I did eventually (somehow) find the correct registration between the ground glass and the sensor.

The prototype I printed was a little bit on the thin side (I never got around to printing a reinforced version), but, with a few coats of black spray paint, completely worked. Taking a photo was simple:

  1. Compose & focus on the ground glass
  2. Remove the ground glass
  3. Attach the digital back adapter
  4. Attach the digital back
  5. Attach the sync cable
  6. Power on the back, if needed
  7. Take the photo
  8. Decide whether it’s worth reversing and repeating the process or guess about a composition change.

In summary, shooting this way was obnoxious. I ended up not using the camera much because it was just too much of a hassle.

The sensible approach is to get a sliding back. More on that later.

Adding a Counter to URL in Swift

02 Apr 2019

I recently ran into a bug in a project when attempting to move a file:

“The File.eip” couldn’t be moved to “.archive” because an item with the same name already exists.

This little error propogated all the way to the top and terminating my Launch Agent1. Being a background task I couldn’t ask the user what they’d like to do about this error, so the method moving the file had to handle it.

The obvious solution is to add a file counter to the end, so File.eip becomes File 1.eip. I figured it would also make sense to make this incrementing method an extenion of URL, similar to .appendPathComponent(:)

Adding a Counter

The whole method looks like this:

func incrementingCounter(starting count: Int = 1, by increment: Int = 1, format: String = "%01d", delimiter: String = " ") -> URL {
    // Break apart the URL
    let ext = self.pathExtension
    var fileName = self.deletingPathExtension().lastPathComponent
    let tempDest = self.deletingLastPathComponent()
    var counter = count
    // Find any suffix digits
    fileCounter: if let digitRange = fileName.range(of: "\(delimiter)\\d+$", options: .regularExpression) {
        let delimiterSet = CharacterSet(charactersIn: delimiter)
        // Extract the digits
        let subString = String(fileName[digitRange]).trimmingCharacters(in: delimiterSet)
        guard let fileCounter = Int(subString) else { break fileCounter}
        // Increment the counter
        counter = fileCounter + increment
        // Remove the existing counter
    // Append the counter with a space
    let formattedCounter = String(format: format, counter)
    let newName = fileName.appending("\(delimiter)\(formattedCounter)")
    return tempDest.appendingPathComponent(newName).appendingPathExtension(ext)

and comes in two flavors:

The first constructs a new URL and the second adds the counter in place. The parameters allow the user to customize how the incrementing takes place. The user can specify the inital number, how much to imcrement by, as well as the string format of the counter and the delimiter to seprate the counter from the file name.

Let’s look at what each part does.

To find the counter we first break the URL apart so we can deal with just the file name.

let ext = self.pathExtension
var fileName = self.deletingPathExtension().lastPathComponent

let tempDest = self.deletingLastPathComponent()

Once we have the file name we need to check if it already has a counter. To do this I’m using a small regular expression, "\(delimiter)\\d+$", which matches our delimiter followed by number. The $ anchors the expression to the end of the string so we don’t match files that contain numbers in their file names.

Specifying the delimiter provides for more flexibility. By default it’s a space, but that may not always be what we need. I regularly work with files that use _v\d{4} as a counter.

fileName.range(of: "\(delimiter)\\d+$", options: .regularExpression)

This method returns an optional range for the matching characters. If there is a range we attempt to convert the range into an integer.

let delimiterSet = CharacterSet(charactersIn: delimiter)

let subString = String(fileName[digitRange]).trimmingCharacters(in: delimiterSet)
guard let fileCounter = Int(subString) else { break fileCounter}

The break here is important: it uses a labeled break which lets us exit the current scope (in the case our if for the range). I’m using the labeled break to handle the case where a number can’t be made into an integer2.

In this case it will just append a new counter to the end of the file name.

Next we increment the counter, and importantly, remove the existing counter.

 // Increment the counter
counter = fileCounter + increment

// Remove the existing counter

The last step in our method is to format the counter and append it to the file name.

// Append the counter with a space
let formattedCounter = String(format: format, counter)
let newName = fileName.appending(" \(formattedCounter)")
return tempDest.appendingPathComponent(newName).appendingPathExtension(ext)

The whole file is available over on GitHub

  1. Now I have tests that try to copy a duplicate file. [return]
  2. I haven’t figured out how to write a test for this [return]

Laydown Test with Victoria Lau

10 Feb 2019

At the end of last year I partnered with stylist Victoria Lau to shoot another test, this time based around autumn and winter clothing.

Visit Autumn Laydowns

Scripting Collections in Capture One 12

21 Dec 2018

A few weeks ago Phase One released Capture One 12 with a slick new interface, support for plug-ins, and a fix for one of my pet bugs (I call it Ralph). There are also a handful of new properties that allow workflows to be automated.

With the release I’ve updated all of my scripts for Capture One 12.

User Property

Capture One collections include a new user property to help differentiate the session’s collections from a user’s favorites.

With this new property finding favorites is fairly simple:

tell front document of application "Capture One 12"
    set theFolder to captures
    set captureCollection to item 1 of (collections whose folder is theFolder and user is true)
end tell

This is also the key to making my capture folder navigation scripts work again.


Another new feature is the sort order property, which allows a script to sort a collection by over a dozen different keys. The main use I’ve found for this is easy, automated batch renaming.

tell front document of application "Capture One 12"
    --  Sort by date
    set sorting order of current collection to by date
    set sorting reversed of current collection to false
end tell

Previously batch renaming was a multi-step manual process:

  1. Sort by the desired key
  2. Select all
  3. Reset the renaming counter
  4. Batch rename

All of these steps could be mapped to keyboard shortcuts, but who has time for that? A short script will do all of those steps in a single action. Paired with a few more lines of code, the script could move between all favorites and rename an entire session with in a single go.

Bonus: Progress Reporting

Anyone who has run a larger script that handles a lot of files has certainly run into the fact that Capture One blocks user input while a script is running. While this isn’t solved in 12, scripts can at least indicate they’re still running with the progress properties.

The progress is displayed in the same view as other Capture One progress bars.

tell application "Capture One 12"
    -- progress set up
    set progress total units to 10
    set progress completed units to 0
    set progress text to "Doing important things"

    -- for each task
    set progress completed units to progress completed units + 1
    set progress additional text to "Details about the important thing"
end tell