Copy link to clipboard
Copied
I'm writing a Lightroom plugin using the Lightroom SDK/API in the Lua language. I'm new to Lua. I've found a situation where my script only works if a Lightroom dialog box (LrDialogs.message("random message")) is present in one function. If the first dialog isn't included then the program flow never steps into the for line in io.lines(outputFilePath)
block, despite outputFilePath being populated with the correct path string. Any ideas what's going on?
Here's the code:
------ read output file for exif and write to LR metadata ------
function parseOutput(outputFilePath)
LrDialogs.message("random message")
local tblOutput = {} --to hold the output exif (1 column table, i.e. an array)
local tblImages = {} --to hold the images and their relevant metadata
for line in io.lines(outputFilePath) do
line = removeWhitespaces(line)
table.insert(tblOutput, line)
end
local str = table.remove(tblOutput) --remove last line in table/file (it's log info, not exif)
tblImages = extractExif(tblOutput) --pick out the exif key/value pairs and add to Image objects
end
[Moved out of the general (and dead) Coding Corner and into a product-specific SDK forum - moderator]
The call to parseOutput() must occur only after Exiftool has finished. So they should be executed sequentially in the same task.
Copy link to clipboard
Copied
I don't see anything obviously wrong with the code fragment you posted. But because LR often (usually) doesn't report errors that occur in all but the main task, it's easy to get fooled about what's really going with your plugin. I strongly recommend you use a debugger. Another thread in this forum describes how to connect LR to a particular IDE; alternatively, you can use by Debugging Toolkit, which provides a more limited but lighter-weight debugger specifically designed for LR's Lua environment. Either one will require an investment of an hour or two, but it will quickly pay off.
Copy link to clipboard
Copied
Thanks John. I'm using ZeroBraneStudio IDE which allows me to debug. I'm not sure how to dig deeper than just stepping through the program and watching the relevant variable. outputFilePath is populated with the correct value, but io.lines(outputFilePath) doesn't trigger.
Copy link to clipboard
Copied
io.lines(outputFilePath) doesn't trigger.
Do you mean that as you single step, the debugger never reaches that line? But when you insert the call to message() and single-step, the debugger does reach that line?
Copy link to clipboard
Copied
Some thoughts:
- Perhaps io.lines() is raising an error and ZeroBrane isn't trapping the error, or perhaps you've got an enclosing pcall() or error handling in some caller that is silently catching the error. Try adding this line before the "for":
local success, value = LrTasks.pcall (io.lines, outputFilePath)
if not success then
LrDialogs.message ("io.lines error: " .. value)
end
- When I revised my debugging toolkit last winter, I looked at the module tha ZeroBrane pretty closely. I wasn't convinced that it properly handles LR's Lua execution environment. In particular, I wonder if it always catches errors in tasks other than the main task and in callbacks from LR API calls, and I wonder if it properly handles single-stepping and breakpoints in LR's tasks (which I'm pretty sure is not the standard Lua tasking / coroutines). If my suspicions are right (and I'm not confident they are), try using logging ("print statements") to trace the execution.
Copy link to clipboard
Copied
Thanks John. I added that code and it returned success = true. So it didn't invoke the error LrDialog.message.
The code reaches the for io.lines... block, but it never steps into the block (unless I add that random LrDialog before it).
Additional info: The parseOutput() function is called from a LrTasks.startAsyncTask() function with a LrTasks.execute() call in it.
Copy link to clipboard
Copied
Here's the code of the function that calls parseOutput().
exifToolAPI = {}
local LrMobdebug = import 'LrMobdebug'
local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrTasks = import 'LrTasks'
local LrPathUtils = import 'LrPathUtils'
local LrFileUtils = import 'LrFileUtils'
local LrDate = import 'LrDate'
local strErrorLogPath = ""
if WIN_ENV then
strErrorLogPath = "C:\Users\Foo\Bar\FUJI_LR_error.log"
elseif MAC_ENV then
strErrorLogPath = "/Users/foo/bar/FUJI_LR_error.log"
end
local arrImage = {} --array of Image objects holding requested exif data
function exifToolAPI.open()
LrMobdebug.start()
local photos = {} -- array to hold LrPhoto objects
local cat = LrApplication.activeCatalog() --active catalogue object
local objET = {} --handle object to hold various ExifTool related properties
local tmpdir = LrPathUtils.getStandardFilePath("temp") --path to system temp directory
--create path to a unique txt file to hold commands to pass to ExifTool
objET.commandFilePath = LrPathUtils.child(tmpdir, "ExiftoolCmds-" .. tostring(LrDate.currentTime()) .. ".txt")
objET.outputFilePath = LrPathUtils.child(tmpdir, "ExiftoolOutput-" .. tostring(LrDate.currentTime()) .. ".txt")
objET.errorLogFilePath = strErrorLogPath
--create empty file
local objFile = io.open(objET.outputFilePath, "w")
objFile:close()
local objFile = io.open(objET.errorLogFilePath, "w")
objFile:close()
--get image list
photos = cat:getMultipleSelectedOrAllPhotos()
LrTasks.startAsyncTask (
function() -- Start exiftool in listen mode
LrMobdebug.on()
strPath = nil
strCommand = cmdlineQuote() .. "/usr/local/bin/exiftool -iso -FilmMode -DynamicRangeSetting -AutoDynamicRange -DevelopmentDynamicRange -@ " .. objET.commandFilePath
strParameters = ""
for i,v in ipairs(photos) do
strPath = photos:getRawMetadata("path") .. "\n" --don't need quotes when using -@ file
strParameters = strParameters .. strPath
end
objFile = io.open(objET.commandFilePath, "w")
objFile:write(strParameters)
objFile:close()
strCommand = strCommand .. " > " .. objET.outputFilePath .. " 2> " .. objET.errorLogFilePath .. cmdlineQuote()
local exitStatus = LrTasks.execute(strCommand)
LrFileUtils.delete(objET.commandFilePath)
end
)
parseOutput(objET.outputFilePath)
LrFileUtils.delete(objET.outputFilePath)
end
Copy link to clipboard
Copied
One issue is that the task running Exiftool may not run at all before parseOutput() is called, or it may only run partially. If that happens, then the file whose name is in objET.outputFilePath hasn't been created yet. But when you insert the call to message() in parseOutput(), the dialog will pause its calling task, allowing the Exiftool task to run to completion, creating the output file before parseOutput() attempts to read it.
Why are you calling Exiftool in a task separate from the one calling parseOutput()?
Copy link to clipboard
Copied
No reason other than just separating code. Should it be within the async call?
Copy link to clipboard
Copied
The call to parseOutput() must occur only after Exiftool has finished. So they should be executed sequentially in the same task.
Copy link to clipboard
Copied
That's fixed it. I moved the call to parseOutput() into the async task block and it runs correctly. Thanks for your help with this, John. I'm now trying to write the fuji exif data to LR metadata and have encountered another problem. I'll start a new thread for that issue. Cheers.