Pragmatism in the real world

Creating collections with the Lightroom Classic SDK

I currently organise my photo library in Lightroom Classic with one folder per day for my images and then I extensively use collection sets, collections and smart collections to manage the photos that represent an “event”, regardless of whether the project is a single day, such as a family BBQ, a multi-day event such as a conference or a multi-month thing such as my Project 365 or mainline railway photos. The nice thing about using collections is that I can have the same photo in multiple logical folders while only having a single copy of the file on disk.

I generally organise each project as a collection set containing a collection for the photos and then at least one smart collection for my picks and then additional collections and smart collections within the set as necessary.

The typical project looks like this:

Collection sets and Collections in Lightroom

The collection set has a date based name as Lightroom doesn’t allow manual sorting of the collections list (which is infuriating!). I then create the collection with the same name appending “masters” (2019-10-13 Worcester photo walk masters in this case). This is where all the photos for the project live. This name is a consequence of the way smart collection rules work: to select a source collection you have to string match, so you need a unique string. The primary smart collection is 2019-10-13 Worcester photo walk <!>. The criteria for this collection is:

Lr smart collection

The rules are simple: Must have at least one star and be in the project’s masters collection. The <!> is appended because I use Jeffrey Friedl’s Smart Collection Sync plugin to allow me to easily share it with Lightroom Mobile. I also sometimes create a To Publish or Published collection to track with images are published to Facebook/Instagram/Flickr.

This takes a bit of time to set up and it crossed my mind after my previous post that I could automate this, so I created a new plugin to do so.

Create Collection Set plugin

Unimaginatively, I’ve called my new plugin Create Collection Set – naming is hard. The idea is that it will create a collection set, the masters collection and the “picks” smart collection for me.

There’s three things I need to handle:

  1. If a collection set is selected, then my new collection set will be a child of it.
  2. I need a dialog box where I can enter the name of the new collection set. The other names will be derived from this.
  3. I need to create the collection set and child collections.

Let’s look at all each one in turn.

Obtaining the selected collection set

In the Lightroom SDK, the catalog has a getActiveSources() method which will provide a list of the selected sidebar items such as folders, collections & collection sets.

The code looks like this:

  local parentCollectionSet = nil
  local parentCollectionSetName = "== Top level =="

  local sources = catalog:getActiveSources()
  local count = 0
  for _ in pairs(sources) do count = count + 1 end
  if count == 1 then
    local activeSource = sources[1]
    if (type(activeSource) == "table") and (activeSource:type() == "LrCollectionSet") then
      parentCollectionSet = activeSource
      parentCollectionSetName = activeSource:getName()
    end
  end

As you can select multiple items in the sidebar, we have to count the returned sources table and ensure that it contains just one item. If multiple items are selected, we will create our collection set at the top level. We also pick up the name of the collection set so that we can display it in the dialog where we ask for the name of the new collection set.

Receiving user input

The easiest way to receive user input is to use a dialog box. The Lightroom SDK has a fairly rich set of features for creating dialog boxes with a comprehensive set of controls. I just need an input box and a label or two.

Dialog boxes in the SDK are created and displayed within a function context’s callback:

LrFunctionContext.callWithContext( "showCreateCollectionSetDialog", function(context)
  -- dialog here
end

To create a dialog we use LrView's osFactory to build the content and then present it. We also need to get data into and out of the dialog and this is done by binding elements in a property table to the view. The system will then pull the data from the table elements when displaying the dialog and put the user entry into the table when the “OK” button is clicked.

To do this, firstly we create the properties tables that will hold the data:

    local props = LrBinding.makePropertyTable(context)
    props.name = ""

We have created a table called props with a single name value which will hold the name of the collection set to be created.

We can then create the dialog box’s contents:

    local c = f:column {
      bind_to_object = props,
      spacing = f:control_spacing(),

      f:row {
        f:static_text {
          title = "Parent:"
        },
        f:static_text {
          title = parentCollectionName
        },
      },
      f:row {
        f:static_text {
          title = "Name: "
        },
        f:edit_field {
          width_in_chars = "30",
          enabled = true,
          value = LrView.bind("name")
        }
      }
    }

This creates a single column with two rows. The first row is a read-only label showing the name of the parent collection that will be used and then the following row holds an input box for the user to enter the name of the collection set to be created.

We can then display the dialog box:

    local result = LrDialogs.presentModalDialog {
      title = "Create Collection Set",
      contents = c
    }

The dialog box looks like this:

Create Collection Set Dialog Box

Creating collections and smart collections

When the name is filled in we can create the collection set, collection and smart collection.

    if result == "ok" then
      catalog:withWriteAccessDo("Create collection set", function()
        -- create collection set
        collectionSet = catalog:createCollectionSet(props.name, parentCollectionSet, true)
      end)

      catalog:withWriteAccessDo("Create collections inside collection set", function()
        -- create "Masters" collection within our new set
        local mastersCollectionName = string.format("%s Masters", props.name)
        catalog:createCollection(mastersCollectionName, collectionSet)

        -- Create smart collection within our new set
        local smartCollectionName = string.format("%s <!>", props.name)
        local searchDesc = {
          combine = "intersect",
          {
            criteria = "rating",
            operation = ">=",
            value = 1
          },
          {
            criteria = "collection",
            operation = "all",
            value = mastersCollectionName,
          },
        }
        catalog:createSmartCollection(smartCollectionName, searchDesc, collectionSet)
      end)

Creating the collection set is done via catalog:createCollectionSet(). As we want to use the resultant object, we put this call within its own catalog:withWriteAccessDo() so that the collection set is committed to the database before we try and use it as a parent.

Then, catalog:createCollection() creates a collection. We append ” Masters” to the set name, pass in the collectionSet that we have just created as its parent and all is well.

To create the smart collection we need to define the rules. In Lightroom-speak, this is known as the searchDesc and the documentation on it is in the API Reference. For my case, I need both rules to be true, so the combine is set to intersect. Each rule has a criteria, operation and value. For rules that need a second value (e.g. created between two dates), then value2 is used.

As the rating criteria is a number we use the >= operation and the value of 1. For the collection that the photo must also belong to, we use the all operation as this is a string and we want all words to be included in the match.

Having set the searchDesc up, we create the smart collection and we’re done.

All done

Conceptually, this is a very simple plugin, but saves me a bit of hassle and so makes me smile when I use it!

As it’s very specific to the way I organise my photos, I don’t suppose it’s much use to anyone else, but if you want to use it as a base for your own automation of Lightroom collections and sets, then I’ve uploaded it as new-collection-set-lrplugin on GitHub.

4 thoughts on “Creating collections with the Lightroom Classic SDK

  1. Rob,

    Downloaded the plugin to test and use as base for creating my own smart collections. Just started on Lightroom plugin development.

    When running your plugin in Adobe Lightroom 10.1 it gives the following error message:

    "An internal error has occurred: ?:0: attempt to compare number with string"

    Can you fix this?

  2. This is EXACTALLY what I've been looking for! Brilliant – THANK YOU!!!!
    I dabble in .net/c# development so hopefully I can work this out and get it tweaked for my needs.

    My goal is to create a simple "Collection Set Generator" that will take in 2 variables/user inputs (clientName, sessionName) and then create a CollectionSet structure like this…

    Clients (existing/root Collection Set)
    -ClientName (CollectionSet – from 'clientName')
    –SessionName (CollectionSet – from 'sessionName')
    —'all images' (Collection – static, always the same for each session)
    —'done/deliever' (Collection – static, always the same for each session)

    Seems pretty straight forward (fingers crossed) :)

  3. Rob,
    Thank you for sharing this. I was struggling to find a way to create Collections via a Script (not a Plugin) .
    But a script always gives me this error message : "An internal error has occurred: LrBinding.makePropertyTable: missing fundi on Context". You made realise it can only be done with an actual Plugin.
    I am going to use your example to do what I need.
    By the way, I found that if you change:
    – line 66: put — at the beginning of the line (– Create the contents for the dialog.)
    – and line 84: "30" becomes just 30
    it works better ;-)
    Again, thank you.

Comments are closed.