19 Replies Latest reply: Jun 15, 2009 6:00 PM by Seán McCormack RSS

    4000. + a question re external files.

    Seán McCormack CommunityMVP

      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.

        • 1. Re: 4000. + a question re external files.
          DFBurns Community Member

          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

          • 2. Re: 4000. + a question re external files.
            Seán McCormack CommunityMVP

            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' },

            }

            • 3. Re: 4000. + a question re external files.
              john beardsworth Community Member

              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
              • 4. Re: 4000. + a question re external files.
                DFBurns Community Member

                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.

                • 5. Re: 4000. + a question re external files.
                  john beardsworth Community Member

                  No offence taken, Dave. I'm now picking up speed with this Lua stuff and am very confident I'm doing some things in a crude and inelegant manner!

                   

                  To get the code looking OK here, I switched to HTML view and then use the pre HTML tag.

                   

                  John

                  • 6. Re: 4000. + a question re external files.
                    Seán McCormack CommunityMVP

                    There's even more answer to questions I hadn't asked Dave, cheers.

                    e.g. I've been sending logs to the Console already.. which is how I was able to figure out that TweetPhoto was having server hiccups. I wasn't sure how to fit text in with the log message though.

                    • 7. Re: 4000. + a question re external files.
                      DFBurns Community Member

                      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. :-)

                      • 8. Re: 4000. + a question re external files.
                        Seán McCormack CommunityMVP

                        Will do.

                         

                        I may just try this as a post process action after all.

                        • 9. Re: 4000. + a question re external files.
                          john beardsworth Community Member

                          FYI The reason for that Mac/Win code was originally because of these shell differences. On PC, I could use LrShell to launch a PDF directly - just sending its name launched it in Acrobat Reader. That default app approach failed on Mac and I was forced to point LrTasks at Preview.

                          John

                          • 10. Re: 4000. + a question re external files.
                            Seán McCormack CommunityMVP

                            I've got to the point where I'm feeding the params in, but.....

                            It's processing on a per photo basis. What's the trick to force it wait until all photos have rendered before executing?

                            • 11. Re: 4000. + a question re external files.
                              DFBurns Community Member

                              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.

                              • 12. Re: 4000. + a question re external files.
                                Matt Dawson Community Member

                                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

                                • 13. Re: 4000. + a question re external files.
                                  Seán McCormack CommunityMVP

                                  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!

                                  • 14. Re: 4000. + a question re external files.
                                    DFBurns Community Member

                                    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:

                                     

                                    http://www.xsi-blog.com/archives/103

                                    • 15. Re: 4000. + a question re external files.
                                      DFBurns Community Member

                                      Matt - It really keeps things sane when you have 18 command line options to pass to ImageMagick like my code does. When/if you need to reorder options is cleaner with this technique as well. Just cut the line and paste it in the new location. Cheers,

                                       

                                      Dave

                                      • 16. Re: 4000. + a question re external files.
                                        Seán McCormack CommunityMVP

                                        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
                                        
                                        
                                        

                                        • 17. Re: 4000. + a question re external files.
                                          Seán McCormack CommunityMVP

                                          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

                                          • 18. Re: 4000. + a question re external files.
                                            Mark J M Wilson

                                            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.

                                            • 19. Re: 4000. + a question re external files.
                                              Seán McCormack CommunityMVP

                                              True, and I was thinking this myself today.