Hey folks, seeing as I hang here most, I figure it's the best place for my 4000th post on Adobe Forums (get a life!).
Does anyone have a little tutorial on passing the rendered photos to an external file? I want it inside a normal plugin, not as a post process action.
It's for an entirely personal project, passing images to ffmpeg to have my timelapse movies created with an export from Lightroom.
Sean - Are you looking for sample code that sets up a command line and then launches an external app? I have this for ImageMagick's convert which I can post here along with comments. My code does this once per exported image though rather than build a list of the images and pass them all at once which is what it sounds like you're trying to do. Not that adding that extra bit should be hard. Let me know and I'll share if relevant.
Dave
Sounds close Dave..
I'd need to pass 3 variables: frame rate, data rate and output filename.
I assume it'll be of the type local status = LrTasks.execute( 'mytool "' .. pathOrMessage .. '"' )
But I'm not sure how to add multiple variables. I'm not sure what the correct mix of '"..xxx.."' is!
A sample call would be ffmpeg -r 10 -b 1800 -i %03d.jpg test1800.mp4
-r=frame rate -b=bit rate -i image name as a sequence(which is done with the renditions) then output.
These variables will be handled by prefs (because once I'm happy with the settings, I'll probably stick with them)
UploadTask.exportPresetFields = {
{ key = 'fr', default = '24' },
{ key = 'dr', default = '1800' },
{ key = 'moviename', default = '/movie.mp4' },
{ key = 'LR_export_destinationType', default = 'tempFolder' },
}
Does this snippet from my exiftool plugin help? It's sending mulitple variables to the selected thumbnail (effectively extending Ctrl/Cmd S) .
John
if WIN_ENV == true then command = '"' .. LrPathUtils.child(LrPathUtils.child( _PLUGIN.path, "win"), "exiftool.exe") .. '" ' .. newUrgency .. ' "' .. phoPath .. '" ' .. newOverwrite quotedCommand = '"' .. command .. '"'
else command = '"' .. LrPathUtils.child(LrPathUtils.child(_PLUGIN.path, "mac"), "exiftool") .. '" ' .. newUrgency .. ' "' .. phoPath .. '" ' .. newOverwrite quotedCommand = command
end
John's reply shows what to do in essential form. With no offense intended to John, I think this can be improved upon in two ways. I find that all of the concatenation operators make things hard to read. I also find that keeping entirely separate versions for Windows vs. Mac is potentially error prone when the only thing that actually changes is the name of the executable.
What I prefer to do is make a table for the parameters, insert them one by one, then concatenate the table contents at the end. Here is an abridged version of the code I use with edits for your app (this formatting stinks - John, how did you get your code to look like that in the embedded window?):
local params = {}
if WIN_ENV == true then
table.insert( params, '"ffmpeg.exe"')
else
table.insert( params, '"ffmpeg"')
end
table.insert( params, "-r " .. exportPresetFields.fr )
table.insert( params, "-b " .. exportPresetFields.br )
table.insert( params, "-i " .. exportPresetFields.moviename )
-- combine params into one string with a space inbetween
local execString = table.concat( params, " " )
logmsg ( "Running app with shell command: " .. execString )
local result = LrTasks.execute( execString )
logmsg( "The result is: " .. result );
if ( result ~= 0 ) then
logmsg( "ERROR: " .. result )
end
The logmsg() function is just my wrapper around a LR logging function. This is all a bit simplified from my actual code for clarity. For instance, to collect the output of the application into a file, I actually have this as the very last thing inserted into the table:
if ( debugMode ) then
table.insert( params, ">> debugLog.txt 2>&1" )
end
This redirects the normal and error output into debugLog.txt. If I recall, you're on a Mac. I'm on Windows and I can offer up another trick or two about managing the console window that appears (or not), etc. but maybe not important since this is a plugin for yourself.
Sean - I have no access to a Mac so please let me know if the code works as expected there. Especially the redirect to a file. It comes down to how Adobe has chosen to launch other apps from LR on the Mac. If they're launching it via the default command shell (which I believe is bash on a Mac) then that redirect should work. This is useful for me as well since when it comes time to make a plugin available to the community, it's one less reason for me to go buy a Mac just to test it. :-)
In your processRenderedPhotos(), you have a loop where you go through all of the exportContext:renditions one by one (standard stuff for almost all export plugins). You're probably launching the app within that loop. Move it after the loop so it launches a single time once all photos are rendered.
DFBurns wrote:
...local params = {}
if WIN_ENV == true then
table.insert( params, '"ffmpeg.exe"')
else
table.insert( params, '"ffmpeg"')
end
table.insert( params, "-r " .. exportPresetFields.fr )
...
Thanks for sharing! This approach is much more elegant than what I had been using up until now.
Matt
Almost there.. It's all passing correctly now.. Thanks again Dave.
Changes I had to make: filterContext.propertyTable.fr for exportPresetFields.fr, etc
I added in ffmpegPath=LrPathUtils.parent(pathOrMessage) to allow me to tell ffmpeg where the files were, as it requires a sequence as input.
I also used that path as a prefix for the move location output folder.
Now I just need to get an acceptable quality movie as output, because currently the quality SUCKS!
That's probably a matter of choosing the right options for ffmpeg. I've never used it myself but I've heard that it's pretty decent. When I've done time-lapse movies, I did it manually using Quicktime Pro. A quick Google around shows that there is a scripting interface for QT at least on Windows using COM. You can write that script (sort of like VBScript) to a file then launch that script to do the work. A kludge but it'd probably work. :-) See here for info:
Joy.
It works...
The movie sucked because 2 images were at 1080X720, with the rest at 1280x720 causing a bit of shoehorning. All fine now...
Here's the (probably sucky) code: (please note the comment in the middle, if anyone knows the answer to that, please pipe up!)
function ffmpegFilterProvider.postProcessRenderedPhotos( functionContext, filterContext )
local renditionOptions = {
plugin = _PLUGIN,
renditionsToSatisfy = filterContext.renditionsToSatisfy,
filterSettings = function( renditionToSatisfy, exportSettings )
-- This hook function gives you the opportunity to change the render
-- settings for each photo before Lightroom renders it.
end,
}
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()
if success then
-- take the file path and image size
-- because this is for me, I know the files have to have the same crop, so taking it from the final
-- rendered file is okay. If someone wants to suggest how we know the file is the final one to render, then
-- I could simply call that one, rather than calling LrPathUtils and LrPhotoInfo each render
ffmpegPath=LrPathUtils.parent(pathOrMessage)
movieSize=LrPhotoInfo.fileAttributes(pathOrMessage)
end
end
local params = {}
local movie={}
table.insert( movie, ffmpegPath)
table.insert( movie, "/")
table.insert( movie, filterContext.propertyTable.moviename)
local movieString = table.concat( movie, "" )
if WIN_ENV == true then
table.insert( params, '"ffmpeg.exe"')
else
table.insert( params, '"ffmpeg"')
end
table.insert( params, "-r " .. filterContext.propertyTable.fr ) --Frame Rate
table.insert( params, "-b " .. filterContext.propertyTable.dr ) --Bit Rate
table.insert( params, "-i " ..ffmpegPath.."/%03d.jpg" ) --Type of Sequence, in this case in the form 001
table.insert( params, "-s " ..movieSize.width.."x"..movieSize.height ) --Movie Size
table.insert( params, "" .. movieString ) --Movie path and filename
-- combine params into one string with a space inbetween
local execString = table.concat( params, " " )
log:trace(execString)
local result = LrTasks.execute( execString )
log:trace( "The result is: " .. result );
if ( result ~= 0 ) then
log:trace( "ERROR: " .. result )
end
end
Hi Dave,
My post above was being made as you posted.. so we crossed over.
The point was that I didn't actually want to have to have another program open to run this.. And of course I'm on a mac. I did look into Applescript and Automator, but couldn't find how to call Open Image Sequence from them.. Anyhow all working.
I just need to add an Overwrite Movie checkbox and maybe change the frame rate and data rate to popup menus... and I'm done.
Again this works for me because I have ffmpeg compiled and living in my /usr/bin folder..
I suspect I'd need to make it ./ffmpeg to run locally inside the plugin (haven't tried)..
And so you get the 10 points too ![]()
Good advice to build your argument string using a table and then concat the table at the end - as well as tidier code, this is faster than building strings using 'x .. y'. You can make that faster still by using:
myTable[#myTable + 1] = "some text"
rather than:
table.insert(myTable, "some text")
An inline expression is generally faster than calling a function.
Sean, on your question of finding the last photo from filterContext:renditions - if you know all the files are the same size, can't you get your values once and not call the two functions again - i.e. get the first photo's data rather than try to get the last.
North America
Europe, Middle East and Africa
Asia Pacific