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(:)

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 fileName.removeSubrange(digitRange) } // 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: • incrementingCounter(starting:by:format:delimiter:) • incrementCounter(starting:by:format:delimiter:) 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
fileName.removeSubrange(digitRange)


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]

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

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.

### Sorting

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"

set progress completed units to progress completed units + 1