Pragmatism in the real world

Migrating to Lightroom Classic from Apple Photos

I recently moved over to Adobe’s Lightroom Classic from Apple’s Photos in order to have more flexible non-destructive editing features – in particular local brushes. Rather confusingly, there’s also another Adobe product called Lightroom which is a new build for cloud-based storage which doesn’t (yet?) have all the features of Lightroom Classic. In particular, the lack of virtual copies and smart collection sets make Lightroom feel like a step backwards from Photos, while Lightroom Classic feels like a step forward in everything other than cloud storage and the Memories feature.

There are two main challenges for migrating from one photo management tool to another:

  • Transferring organisational folders and albums
  • Transferring photos edits

We need to do both for a successful migration.

Transferring folders and albums

The way that my photos are organised into folders and albums within Photos is stored in a database and as Lightroom Classic doesn’t have an “Import from Apple Photos” function, we’ll need to find a way to store which albums a photo is stored in within the photo itself and then recreate it in Lightroom Classic later. I chose to do this using keywords.

My organisational structure in Photos looks something like this:


Photos phpnw08 album

For each picture in an album, I chose to create a keyword that included the folders that that album was in using the “>” symbol as a separator. For example, the Published album in the screenshot above lives in the Conferences & Meetups -> 2008 -> 2008-11 PHPNW08 folder hierarchy, so each photo within it, such as the one of Johanna and Mike, needs a keyword of “PhotosExport>Conferences & Meetups>2008>2008-11 PHPNW08>Published”. (I added “PhotosExport>” so that these keywords are easy to identify.)

Obviously, I didn’t do this by hand as it would take ages and be prone to error, so I wrote a script to do it for me!

Update Note that this script doesn’t work for Catalina or Big Sur. For more recent versions of macOS, look at Rhet Turnbull’s osxphotos. As noted in this comment, you can use it to export images from Photos with the relevant export keywords by running this command in the Terminal:

osxphotos export /path/to/export --sidecar xmp \
  --keyword-template "PhotosExport>{folder_album(>)}" \
  --keyword-template "{edited?edited-before-lightroom,}" \
  --exiftool

See the comment for details and this one for how to re-run.

For pre-Catalina, the KeywordAlbum.applescript should be placed somewhere useful such as ~/Library/Scripts/Applications/Photos. I ran it by opening it in Script Editor and pressing the “play” toolbar button.

This is the key loop:

tell application "Photos"
    set allAlbums to albums
    repeat with theAlbum in allAlbums
            -- Get the hierarchy keyword 
            set theKeyword to my getAlbumKeyword(theAlbum)

            -- get all the photos in this album and add the keyword
            set thePhotos to every media item in theAlbum
            repeat with thePhoto in thePhotos
                my addKeywordsToItem(thePhoto, theKeyword)
            end repeat
    end repeat
end tell

We iterate over all albums and for each album we call getAlbumKeyword() to get the hierarchical keyword containing this album’s name and all its parents. Once we have the name, we can iterate over every photos in the album and add our keyword. See the gist for the full details.

After running the script, all my photos in Photos now have a keyword that starts with “PhotosExport>” for each album that they belong to.

When imported into Lightroom Classic, the “>” is treated as a nesting separator, so they appear in the Keywords panel like this:


Lr photosexport keywords

This image of Lightroom Classic’s Keyword List panel shows the Published keyword that maps to the Photos album from earlier within the PhotosExport -> Conferences & Meetups -> 2008 -> 2008-11 PHPNW08 hierarchy of keywords.

We now need to convert that keyword hierarchy into collections and collection sets which are Lightroom Classic’s terms for albums and folders.

Unfortunately, Lightroom Classic isn’t scriptable with AppleScript, but does supports plug-ins written in Lua, so I wrote a plug-in to do this work. My collection-creator plug-in creates collections within nested collection sets from a keyword hierarchy and as a result, I was able to get the same set of folders and albums in Lightroom Classic that I had in Photos.


Lr phpnow08 collection

With a solution found for transferring folder and albums, we now turn our attention to exporting the photos themselves.

Exporting the photos from Photos

It is trivial to export all your original photos from Photos along with the metadata (title, description, keywords) in an XML sidecar file. You can then import these into Lightroom Classic, but it will lose all your edits (including crops) and for JPEGs, you will also lose all your metadata as Lightroom Classic doesn’t read XML sidecar files for JPEGs.

This is not ideal and we can do better.

Exporting JPEG photos

I don’t use RAW+JPG when shooting RAW, so all JPEGs I have in Photos are originals from my phone or a small camera. For these, we need to export them all as new JPEGs out of Photos so that they contain any metadata (title, description, keywords) that may be set.

The easiest way to do this is to create a smart album called “All Non-RAWs”:


Photos sa non raws

As we can’t select just JPEGs we select for all non-RAW files which will include any PNGs etc that may be in our library.

To export, we select all photos in the smart album, and choose the File -> Export X Items… menu item (where X is the number of photos you have selected). This option exports the photos as processed by Photos.

This dialog will be displayed:


Photos export processed

Expand the Photos section and select JPEG, with maximum quality, most compatible colour profile at full size. Check every box in the Include section and then press Export. Choose a folder to save to and it will create a set JPEG files with the metadata embedded within each one.

These can be imported into Lightroom Classic.

Exporting RAW photos

Exporting RAWs is more complicated as the edits I have made in Photos cannot be interpreted by Lightroom Classic.

We have two choices:

  1. Import all original RAWs into Lightroom Classic and also import a set of JPEGs for the edited RAWs
  2. Import all unedited original RAWsLightroom Classicand also import a set of JPEGs for the edited RAWs. Archive the edited original RAWs separately.

Option 1 is the belt-and-braces solution, but means that for every edited RAW photo, you have two separate, unconnected image files in Lightroom Classic as it doesn’t handle RAW+JPG as I would hope and ignores the JPEG completely unless unless the “Treat JPEG files next to raw files as separate photos” option in Preferences is checked. (i.e. if you go this route, ensure that preference is checked!)

I didn’t want an additonal 13,800 images in my new Lightroom Classic catalog, so I went with option 2.

The approach I took was to assume that I don’t want to re-edit any photo that I previously edited in Photos so I want to import the processed JPEG for these RAW files and will not have the original RAW in Lightroom Classic.

I will however archive the original RAW files, so that if I ever did decide that I would like to re-edit a previously edited photo, I can extract the original file from my archive, import it into Lightroom and then re-edit.

To achieve this, I did these exports from Photos for importing into Lightroom Classic:

  1. All unedited RAWs as originals with XML sidecars
  2. All edited RAWs as processed JPEG

This gives me all RAW files that have not been edited with their metadata and JPEGs of all the RAWs that I have edited with their metadata.

Export 1: The smart album in Photos for unedited RAWs is:


Photos sa unedited raws

To export as originals with XML side car files, select all photos in the smart album and choose File -> Export -> Export Unmodified Original of X Photos… (where X is the number of photos you have selected). This dialog is then displayed:


Photos export originals

Ensure that you select Export IPTC as XMP from the next dialog and then press Export, choose a folder and it will then create a set of RAW files where each one has an XML sidecar file with the same name.

Export 2: For the edited RAWs that we want as JPEGs, the smart album criteria is very similar:


Photos sa edited raws

I then selected all the photos in the smart album and added a new keyword edited-before-lightroom to them. This took a while, but I’ll need it so that I’ll know if there’s a RAW file in the archive that I should use if I ever want to re-edit in Lightroom Classic

To export select all photos in the smart album and, as with exporting all our JPEGs, choose the File -> Export X Items… to create a set of maximum quality, full size JPEG files with embedded metadata. This creates a folder full of JPEG files with the metadata embedded within each one.

Export 3: I created a smart album for all non-RAW files:


Photos sa non raws

As with export 2, I exported using File -> Export X Items… to create a set of JPEG files with embedded metadata.

The photos from all both of theses exports are then imported into Lightroom Classic.

Finally, I exported the photos in “Edited RAWs” smart album again, but this time using the File -> Export Unmodified Original of X Photos… menu item in order to create an archive of the original RAW files for the photos that were edited in Photos and are now JPEGs in Lightroom Classic. These are stored on my NAS for safe keeping.

And I’m done!

To conclude

Changing database backed photo management tools is quite a lot of work if you want to keep your album organisation and also the photo edits you did. It would be a lot easier if there was a common standard for keeping folder/album information in a photo’s metadata. It would be even better if there was a common way to store non-destructive edits in a cross-application manner, but I can’t see that ever happening!

I should also point out that if the new Lightroom (not Lightroom Classic) is what you want, then you’re good to go as Adobe provide a migration button for you. If you want to use Lightroom Classic, then this is the process I used.

As a final thought, although, I’ve talked about migrating from Photos to Lightroom Classic, the basic principles hold true for migrating to and from any photo management app. It’s much more problematic if one of the applications isn’t scriptable though!

17 thoughts on “Migrating to Lightroom Classic from Apple Photos

  1. Thank you for sharing this information, Rob! I ran your KeywordAlbum.applescript on my Photos library consisting of 23 819 images across 307 albums. It worked like a charm, even with Norwegian letters and special characters in file- and album names. Zero errors. I have a mixture of RAW + JPEG and PNGs from different cameras, including Nikon, Canon and Leica. You really saved me a lot of hassle.

  2. Your fine work here will save me countless hours as I recover two corrupted very large (220 gigs & 410 gigs) old iPhoto libraries from a Synology NAS I used to archive my brothers iMacs data before one of them had an unrecoverable hard drive failure. The iMacs were both acting badly a couple months before the ultimate failure, which prompted me to buy his family a small NAS and do an archive backup during a family visit. My brother is a fine photographer, but rather busy as a primary care doc attending to his patients on the frontlines of CV-19 pandemic in Boston, so I wanted to take on this task for him.

    After much work I have recovered and converted his old iPhoto libraries to newer Photos versions and with your guide and tools will now work to migrate them into Lightroom Classic. Everything is now backed up onto multiple drives on my collection of NAS's. Will be using a vanilla install i9-9900 cpu Hackintosh I built to process the Photos –> Lightroom migration, otherwise my old mid-2010 MBP would find the task a bit daunting.

    Thank-you again for developing this workflow. Best regards.

    — stay safe.
    — mark in Seattle

  3. Your KeywordAlbum.applescript worked pretty well on my first test of an Apple PhotosLibrary. It added the expected keywords: PhotosExport>some-album-name, to the jpeg files. It ignored photos and albums inside PhotoLibrary folders, so I repeated the process with a fresh copy of the test PhotosLibrary, this time moving all the Albums into the same root level of Photos –> Albums.

    One question if I may: when I leveled the Album structure, and even before doing so, multiple Albums can point to the same original jpeg photo file. So not unexpectedly KeywordAlbum.applescript iterating over all the Albums sometimes created multiple keyword entries inside a jpeg file:

    a JPEG photo file:
    Keywords:
    PhotosExport>some-album-name
    PhotosExport>another-album-name

    Was wondering how the Adobe Lightroom Classic plugin code will deal with multiple "PhotosExport>" keywords. If it stores a copy of the JPEG file inside a hard drive folder with the same name as the original Apple Photos Album name then that would be workable, even better … maybe if it stores the JPEG in the hard drive folder created first and any other "PhotosExport>" keywords associated with the JPEG file get a symbolic reference … or the Lightroom Classic equivalent placed inside the second Lightroom folder.

    Took some effort to prevent Apple Photos from processing all the previous Photo Libraries it knew about, when I really only wanted it the KeywordAlbum.applescript to process only my test Photo Library. No harm was done, because I stopped the script before it could do that. My assumption was the script would only process the Apple Photos "system library" which my test PhotosLibrary was designated as. Not clear how Apple intends a user to remove PhotoLibrarys from it's list of available libraries, so I disconnected the internal hard drive holding the non-test libraries, leaving only the test Library visible to the system.

    Thank-you once again for providing your applescript and LR plugin.

  4. Many thanks for your tutorial. Have been diligently following your instructions but got stuck when running the plugin. The message says "No Key Words under "_Keywords To Become Collections" to be converted into collections"

    Presumably the message above is what you referenced in your note (which I have copied below).

    QUOTE
    If the "_KeywordsToBecomeCollections" keyword has been created by the plug-in or is empty, then a message will be displayed and the process will stop. You now need to place the keyword hierarchy that you wish to be converted to collections within collection setsinside this keyword and Create Collections run again.
    UNQUOTE

    Would appreciate if you could explain what you mean by "You now need to place the keyword hierarchy ….. and Create Collections run again".

    1. Winston,

      The Apple script creates keywords that start with PhotosExport>, so when they end up in Lightroom Classic, you see a hierarchy of keywords under PhotosExport.

      The Creation-Creator Plugin doesn't look there though. It looks for keywords that are children of the top level _KeywordsToBecomeCollections keyword.

      Therefore, you need to drag the keywords under the PhotosExport keyword into the _KeywordsToBecomeCollections keyword and then it will create collections for you when you run the "Create Collection" menu command.

  5. Hi Rob! I've just come across your script here. I've left it running over night and at the end (in the results section) it says 'Result: false'. Not sure if that is a problem or expected?

    I also noticed that not all photos within albums have been tagged with keywords by the script. Initially i thought it was because the albums are in more deeply nested folders and the settings you gave the script tell it to not look so deeply but I'm not sure now as 'Mark Early' seemed to have a similar problem too. Is it a problem with the apple Photos app and not your script? and is there a workaround/adjustment to the script you could offer me to make it keyword every photo within an album no matter how deeply nested it is please? by the way im not good at all with all of this programming so if there is a change in the script you could post in your reply so i can copy and paste instead telling me pointers on how to modify the script myself I would be really grateful

    PS – I live about 30 minutes away from you! lol

      1. literally every folder and album that is not at the root level. I'm using catalina if that makes a difference? i read somewhere that photos app changes how it operates within the newest versions (4 and 5?) and you have to tell it to look within folders instead of albums i think. if i find the web page i saw it on ill link it to you. i have also come across another script on github (https://github.com/RhetTbull/osxphotos) but it may as well be in another language as i am terrible at programming so don't know what I'm doing with it lol

        1. Also not a coder so this could be off but I'm pretty sure the problem is line 32 "set allAlbums to albums" is only capturing top level albums.

          I think this post explains what is going on (https://developer.apple.com/forums/thread/653467) but because my programing level is probably worse than Steve's I haven't yet figure out how to get allAlbums to include all the nested album names. It looks like you need to build all the recursive loops.

        2. Hi. I'm the author of the osxphotos app you mentioned (https://github.com/RhetTbull/osxphotos). You can get pretty close to your use case with this one command:

          osxphotos export /path/to/export --sidecar xmp --keyword-template "PhotosExport>{folder_album(>)}" --keyword-template "{edited?edited-before-lightroom,}" --exiftool

          Breakdown of the command:
          export /path/to/export = where to send the files

          --sidecar xmp = export XMP sidecar files with associated metadata (will also include faces/persons which Photos won't export)

          --keyword-template "PhotosExport>{folder_album(>)}" = add the keyword you wanted with folder/album path. {folder_album(>)} is a command in osxphotos' own template language which means "add a keyword based on folder and album path and use > as the path separator"

          --keyword-template "{edited?edited-before-lightroom,}" = add a keyword of 'edited-before-lightroom' if the photo has been edited otherwise don't add a keyword

          --exiftool = run exiftool to export all metadata to the exported files (requires exiftool be installed: https://exiftool.org/)

          If you shot RAW+JPEG, this would give you both the RAW and JPEG images as there's currently no way to filter just the RAW of the RAW+JPEG pair (though you can filter out just the JPEG part). I'll open a new issue for this as it would be useful.

        3. Also, I forgot to mention that with osxphotos you can re-run this command in the future by adding the –update option which will export only those images which have changed or been added in Photos or changed in the destination.

          osxphotos export ~/Desktop/export --sidecar xmp --keyword-template "PhotosExport>{folder_album(>)}" --keyword-template "{edited?edited-before-lightroom,}" --exiftool --skip-original-if-edited --update

  6. First, Rob. I didn't thank you in your last post for this article. It made me think long and hard before the migration process. So thank you.

    Back to this. Jamming the code from the apple link you posted into the script does successfully capture all the albums. But in the whack a mole world of software, now down in lines 103-107 for some reason attempting to set targetObject to the parent folder results in an error which stops the repeat loop.

    So the script will now process all albums but only one nested folder layer down.

    I tried several different ways to assign a folder object to any variable and it seems like it always results in an error. I'll keep looking around a bit but it might be that Apple has moved on from supporting this.

    Thanks for posting your work though,
    -Russell

    1. I ended up brute forcing the problem. Here's the ugly code that only works for 4 or fewer nested levels of folders. I know – that's not the way to write software but it worked for me and I was too tired to solve it correctly.

      on getAlbumKeyword(targetObject)	
      	-- start with the album name
      	set theKeword to the name of targetObject
      	
      	tell application "Photos"
      		try
      			if class of parent of targetObject is folder then
      				set theKeword to name of parent of targetObject & ">" & theKeword
      				if class of parent of parent of targetObject is folder then
      					set the theKeword to name of parent of parent of targetObject & ">" & theKeword
      					if class of parent of parent of parent of targetObject is folder then
      						set the theKeword to name of parent of parent of parent of targetObject & ">" & theKeword
      						if class of parent of parent of parent of parent of targetObject is folder then
      							set theKeword to name of parent of parent of parent of parent of targetObject & ">" & the Keword
      							my logThis("You were WRONG there are Albums nested 4 deep")
      						end if
      					end if
      				end if
      			end if
      		end try
      	end tell
      	set theKeword to "ExistingAlbum>" & theKeword
      	return theKeword
      end getAlbumKeyword
      
  7. Hi Rob,
    Did you ever manage to 'repair' your script so that it operated on all levels of folders (recursively). I'm about to finally export my whole library to my NAS and I am willing to depend on your script as opposed to having to figure out how to utilise Rhets osxphotos script

Comments are closed.