Copy link to clipboard
Copied
I've been trying to create some Lightroom plugins and inevitably, I've been studying the Lightroom SDK (the SDK manual, the SDK API reference, the sample plugins) and learning Lua at the same time.
At this time, I am working on my second plugin that seeks to add a post-process action which has as goal to create a watermark externally (probably with ImageMagick), without using Lightroom's watermarking presets or engine.
To do this, I am reading through Chapter 3 of the SDK Manual (Creating Export and Publish services) and I find it challenging to:
What makes this particularly challenging is that the bits of sample code in the manual and even the sample plugins seems to be random and inconsistent from one to another.
For one, the SDK manual does not offer clear directions (to me, as a beginner, anyway) on how an export service and export post-process action are different, and what they need to return and how. On page 37, there is a sample code as example of a table returned by a service definition script:
return {
startDialog = function( propertyTable ) ... end,
endDialog = function( propertyTable, why ) ... end,
exportPresetFields = { { key = 'myPluginSetting', default = 'Initial value' } },
showSections = { 'fileNaming', 'imageSettings' },
sectionsForBottomOfDialog = function( viewFactory, propertyTable ) ... end,
processRenderedPhotos = function( functionContext, exportContext ) ... end
}
However, no such example is given for a post-process "action definition script". Initially, I followed the same plugin structure as my first plugin, where sectionsForTopOfDialog () was declared in PluginManager.lua which was required by PluginInfoProvider.lua, which in turn was referred to by Info.lua. This was based on one of either the manual or one of the sample plugins I followed at the time.
Following the same approach for sectionForFilterInDialog() and postProcessRenderedPhotos() of the post-process action did not seem to work, as they expected to be returned as part of the table returned by the action definition script. This wasn't obvious at first, nor was it obvious to build the correct syntax.I first tried:
return {
sectionForFilterInDialog = sectionForFilterInDialog,
postProcessRenderedPhotos = MyExportFilterProvider.postProcessRenderedPhotos
}
But that gave errors after adding/inserting the post-process action during Export, that simply stated "The plugin is missing.". That wasn't because any plugins or files were actually misisng but because the expected table/values were probably incorrectly returned.
Things came to light a bit after looking at the sample plugin CreatorFilter and seeing how everything's organized and returned there, which brings me to a "for two" point.
For two, the sample code for post-process action functions provided on page 48 of the SDK manual created for me all kind of confusion:
function SimpleExternalToolFilterProvider.postProcessRenderedPhotos( functionContext, filterContext )
The first confusion was the "SimpleExternalToolFilterProvider" term which I couldn't find a reference to anywhere else. The example seen three pages prior, on page 45, mentioned "My Action" as title and "myAction.lua" as file, right after mentioning "Filter Name" and "yExportFilterProvider.lua" only a few lines higher:
... (from page 45)
LrExportFilterProvider = {
title = "Filter Name", -- this display string appears in the dialog
file = "MyExportFilterProvider.lua", -- the action definition script
id = "myFilter", -- a unique identifier for the action
requiresFilter = "mainFilter", -- optional
supportsVideo = "true" -- optional
},
... (lower on page 45)
LrExportFilterProvider = {
{
title = "MyAction",
file = "MyAction.lua",
id = "main",
},
},
...
So I wondered if SimpleExternalToolFilterProvider was some kind of required namespace or else and poked around the api reference for it. Obviously, I found nothing.
Everything would have been much clearer much faster had the example function on page 48 would have been simlpy named:
function MyExportFilterProvider.postProcessRenderedPhotos( functionContext, filterContext )
or even
function myAction.postProcessRenderedPhotos( functionContext, filterContext )
Actually, it would have been even clearer if both code examples on pages 45 and 48 were even more specific when offering code samples for post-processing actions:
... (page 45)
LrExportFilterProvider = {
{
title = "My Export Post-Process Action",
file = "MyExportPostProcessActionProvider.lua",
id = "main",
},
},
... (page 48)
function MyExportPostProcessActionProvider.postProcessRenderedPhotos( functionContext, filterContext )
...
(After all this I am still unsure of whether an export filter is the same as a post-process action and the other way around. Is a post-process action part of an export filter provider? Does an export filter provider provide more than just post-process actions? On page 42 of the SDK manual alone, the two terms are mentioned several times and I can't tell if they are used interchangeably.)
But that's not all. The SimpleExternalToolFilterProvider.postProcessRenderedPhotos sample function on page 48 brings a whole set of questions to someone who is not yet familiar with all the API namespaces related to LrExport* and their methods/properties, and also the various post-process action execution steps/stages.
Here's the beginning of that function:
function SimpleExternalToolFilterProvider.postProcessRenderedPhotos( functionContext, filterContext )
-- Optional: If you want to change the render settings for each photo
-- before Lightroom renders it, write something like the following.
-- If not, omit the renditionOptions definition, and also omit
-- renditionOptions from the call to filterContext:rendition()
local renditionOptions = {
filterSettings =
function( renditionToSatisfy, exportSettings )
exportSettings.LR_format = 'TIFF'
return os.tmpname()
-- ... if you wanted Lightroom to generate TIFF files
-- and override the configured filename when providing
-- input images to this post-process action.
-- By doing so, you assume responsibility for creating
-- the file type that was originally requested and placing it
-- in the location that was originally requested in your
-- filter loop below.
end,
} --renditionOptions
for sourceRendition, renditionToSatisfy in filterContext:renditions(renditionOptions ) do
-- Wait for the upstream task to finish its work on this photo.
local success, pathOrMessage = sourceRendition:waitForRender()
...
That set of questions is:
I looked through the sample plugins hoping for some clarity, but they only contributed further confusion. More specifically, I looked at the CreatorFilter.lrdevplugin/CreatorExternalToolFilterProvider.lua file which has an example of a postProcessRenderedPhotos() function.
In that function, there is a new line (compared to the manual):
renditionsToSatisfy = filterContext.renditionsToSatisfy,
So that offered some confirmation that filterContext may provide certain properties and methods. How do I find out which?
With that confirmation came a new confusion because I noticed renditionsToSatisfay ~= renditionToSatisfy - I am referring to the plural form of what I thought was the same renditionToSatisfy variable I was investigating.
Although renditionsToSatisfay is set to the same value provided by filterContext, is not used anywhere else further in the plugin. Nor is renditionOptions{} returned anywhere, so I really still don't understand everything here. Is setting renditionsToSatisfay required, and if not, why even have it as part of the sample code when it isn't used anywhere?
Moving on, after much poking around through the SDK reference, I found that filterContext:renditions(renditionOptions) used in the for loop seems to refer to the exportSession:renditionsForFilter( params ) method of the LrExportSession class.
This leads me to believe that filterContext is basically an LrExportSesson object. If that is true, why not simply refer to it as exportSession instead of filterContext, to keep things more clear to those reading the sample code and sdk manual?
To sum up all those points, it feels much would be far simpler if the manual offered a complete example of the contents of an "action definition script", which for simplicity I suggested above it could be referred to as MyExportPostProcessActionProvider.lua:
... -- add this in Info.lua
LrExportFilterProvider = {
title = "My Export Post-Process Action", -- this display string appears in the dialog
file = "MyExportPostProcessActionProvider.lua", -- the action definition script
-- id = "myFilter", -- a unique identifier for the action
-- requiresFilter = "mainFilter", -- optional
-- supportsVideo = "true" -- optional
},
...
... -- contents of MyExportPostProcessActionProvider.lua
local LrTasks = import "LrTasks"
local MyExportPostProcessActionProvider = {}
function MyExportPostProcessActionProvider.postProcessRenderedPhotos( functionContext, exportSession )
-- Optional: If you want to change the render settings for each photo
-- before Lightroom renders it, write something like the following.
-- If not, omit the renditionOptions definition, and also omit
-- renditionOptions from the call to exportSession:rendition()
local renditionOptions = {
renditionsToSatisfy = exportSession.renditionsToSatisfy,
filterSettings =
function( renditionToSatisfy, exportSettings )
exportSettings.LR_format = 'TIFF'
return os.tmpname()
-- ... if you wanted Lightroom to generate TIFF files
-- and override the configured filename when providing
-- input images to this post-process action.
-- By doing so, you assume responsibility for creating
-- the file type that was originally requested and placing it
-- in the location that was originally requested in your
-- filter loop below.
end,
} --renditionOptions
for sourceRendition, renditionToSatisfy in exportSession:renditions(
renditionOptions ) do
-- Wait for the upstream task to finish its work on this photo.
local success, pathOrMessage = sourceRendition:waitForRender()
if success then
-- Now that the photo is completed and available to this filter,
-- you can do your work on the photo here.
-- It would look somethinglike this:
local status = LrTasks.execute( 'mytool "' .. pathOrMessage .. '"' )
-- (This tool is hypothetical.)
-- You may need to use escapes in the file name so that
-- special characters are not interpreted by the OS shell
-- (cmd.exe in Windows or bash in Mac OS).
-- In Windows, enclose the whole command in double quotes.
-- If your tool cannot process the photo as intended, use
-- something like this to signal a failure for this rendition only:
if status ~= ("mystatus") then
renditionToSatisfy:renditionIsDone( false, "error message" )
end
-- (Replace "error message" with a user-readable string explaining why
-- the photo failed to render.)
-- It is neither necessary nor harmful to call renditionIsDone if the
-- rendition has completed successfully.
-- The iterator for exportSession:renditions calls it
-- automatically if you have not already done so.
end -- if success
end -- for sourcerendition
end -- postProcessRenderedPhotos
--[[
return {
postProcessRenderedPhotos = MyExportPostProcessActionProvider.postProcessRenderedPhotos
}
]]
-- This function will create the section displayed on the export dialog
-- when this filter is added to the export session.
function MyExportPostProcessActionProvider.sectionForFilterInDialog( viewFactory, _ )
return {
title = "My Export Post-Process Action Test",
viewFactory:row {
spacing = viewFactory:control_spacing(),
viewFactorystatic_text {
title = "Some sample static text",
fill_horizontal = 1,
},
-- add additional elements here for the export dialog section
}
}
end
return MyExportPostProcessActionProvider
Finally, for myself at this stage, some questions remain to be figured out:
I understand all these challenges may not seem as challenges to an experience Lightroom SDK developer who is fully familiar with Lightroom SDK's ways, but I am sharing this as my experience as a beginner at both the SDK and Lua as a language who is not a stranger to other programming languages and reading documentation in general.
The intention of this post is not to be a rant, but an invitation for Adobe's technical writers to maybe review the SDK documentation and see if it can be streamlined.
At the same time, I wanted to share a bit of my experience to see if anybody else who may have tried to tackle the SDK and create plugins encountered similar challenges.
If you read this far, thank you for your time.
Have something to add?
Find more inspiration, events, and resources on the new Adobe Community
Explore Now