22 Replies Latest reply: Apr 7, 2015 12:12 PM by johnrellis RSS

    Here is a function to read preview images from active catalog

    areohbee Community Member

      --- Get a preview image corresponding to specified photo, at the specified level, if possible.

      --

      --  @param photo (LrPhoto or table of param, required)     specified photo or table of named parameters same as below including photo=lr-photo:

      --  @param photoPath (string, optional)     photo-path if available, otherwise will be pulled from raw-metadata.

      --  @param previewFile (string, default=unique-temp-path)     target path to store jpeg - if non-vil value passed and file is pre-existing, it will be overwritten.

      --  @param level (number, required)      appx sizes + intended use:

      --      <br>     1 - 80x60     small thumb

      --      <br>     2 - 160x120   medium thumb

      --      <br>     3 - 320x240   large thumb

      --      <br>     4 - 640x480   small image

      --      <br>     5 - 1280x960  medium image

      --      <br>     6 - 2560x1920 large image

      --      <br>     7 - 1:1       full-res

      --  @param minLevel (number, default=1) minimum acceptable level.

      --

      --  @usage file, errm, level = cat:getPreview{ photo=catalog:getTargetPhoto(), level=5 }

      --  @usage file, errm, level = cat:getPreview( catalog:getTargetPhoto(), nil, nil, 5 )

      --

      --  @return file (string, or nil) path to file containing requested preview (may be the same as preview-file passed in).

      --  @return errm (string, or nil) error message if unable to obtain requested preview (includes path(s)).

      --  @return level (number, or nil) actual level read, which may be different than requested level if min-level passed in.

      --

      function Catalog:getPreview( photo, photoPath, previewFile, level, minLevel )

          if photo == nil then

              app:callingError( "no photo" )

          end

          if not photo.catalog then -- not lr-photo

              photoPath = photo.photoPath

              previewFile = photo.previewFile

              -- assert( photo.level, "no level in param table" )

              level = photo.level

              minLevel = photo.minLevel

              photo = photo.photo

              -- assert( photo and photo.catalog, "no lr-photo in param table" )

          end

          if level == nil then

              app:callingError( "no level" )

          end

          if level > 7 then

              app:logWarning( "Max level is 7" )

              level = 7

          end

          if photoPath == nil then

              photoPath = photo:getRawMetadata( 'path' )

          end

          local photoFilename = LrPathUtils.leafName( photoPath )

          local _previewFile

          if previewFile == nil then

              _previewFile = LrPathUtils.child( LrPathUtils.getStandardFilePath( 'temp' ), str:fmt( "^1.lrPreview.jpg", photoFilename ) ) -- include extension, since there are separate previews for each file-type.

          else

              if fso:existsAsFile( previewFile ) then

                  app:logVerbose( "preview path passed is to existing file to be overwritten" )

              end

              _previewFile = previewFile

          end

       

          local imageId

          local s = tostring( photo ) -- THIS IS WHAT ALLOWS IT TO WORK DESPITE LOCKED DATABASE (id is output by to-string method).

          local p1, p2 = s:find( 'id "' )

          if p1 then

              s = s:sub( p2 + 1 )

              p1, p2 = s:find( '" )' )

              if p1 then

                  imageId = s:sub( 1, p1-1 )

              end

          end

          if imageId == nil then

              return nil, "bad id"

          end

       

          local cp = catalog:getPath()

          local fn = LrPathUtils.leafName( cp )

          local n = LrPathUtils.removeExtension( fn )

          local cd = LrPathUtils.parent( cp )

          local pn = n .. " Previews.lrdata"

          local d = LrPathUtils.child( cd, pn )

          local pdb = LrPathUtils.child( d, 'previews.db' )

          assert( fso:existsAsFile( pdb ), "nope" )

          --Debug.pause( pdb )

          local exe = app:getPref( 'sqlite3' )

          if not str:is( exe ) then

              if WIN_ENV then

                  exe = LrPathUtils.child( _PLUGIN.path, "sqlite3.exe" )

              else

                  exe = LrPathUtils.child( _PLUGIN.path, "sqlite3" )

              end

              app:logVerbose( "Using sqlite executable included with plugin: ^1", exe )

          else

              app:logVerbose( "Using custom sqlite executable: ^1", exe )

          end

          local param = '"' .. pdb .. '"'

          local targ = str:fmt( "select uuid, digest from ImageCacheEntry where imageId=^1", imageId )

          local r1, r2, r3 = app:executeCommand( exe, param, { targ }, nil, 'del' )

          local uuid -- of preview

          local digest -- of preview

          if r1 then

              if r3 then

                  local c = str:split( r3, '|' )

                  if #c >= 2 then

                      -- good

                      uuid = c[1]

                      digest = c[2]

                  else

                      return nil, "bad split"

                  end

              else

                  return nil, "no content"

              end

          else

              return nil, r2

          end

       

          local previewSubdir = str:getFirstChar( uuid )

          local pDir = LrPathUtils.child( d, previewSubdir )

          if fso:existsAsDir( pDir ) then

              -- good

          else

              return nil, "preview letter dir does not exist: " .. pDir

          end

          previewSubdir = uuid:sub( 1, 4 )

          pDir = LrPathUtils.child( pDir, previewSubdir )

          if fso:existsAsDir( pDir ) then

              -- good

          else

              return nil, "preview 4-some dir does not exist: " .. pDir

          end

          local previewFilename = uuid .. '-' .. digest .. ".lrprev"

       

          local previewPath = LrPathUtils.child( pDir, previewFilename )

          if fso:existsAsFile( previewPath ) then

              app:logVerbose( "Found preview file at ^1", previewPath )

          else

              return nil, str:fmt( "No preview file corresponding to ^1 at ^2", photo:getRawMetadata( 'photoPath' ), previewPath )

          end

       

          -- this could be modified to return image data instead of file if need be.

          local content

          local function getImageFile()

              local p1, p2 = content:find( "level_" .. str:to( level ) )

              if p1 then

                  local start = p2 + 2 -- jump over level_n\0

                  local p3 = content:find( "AgHg", start )

                  local stop

                  if p3 then

                      stop = start + p3 - 1

                  else

                      stop = content:len() - 1

                  end

                  local data = content:sub( start, stop )

                  if previewFile ~= nil then -- user passed file

                      app:logVerbose( "Writing preview into user file: ^1", _previewFile )

                  else

                      -- rename file to include level.

                      local base = LrPathUtils.removeExtension( _previewFile ) .. '_' .. level

                      _previewFile = base .. ".jpg"

                      app:logVerbose( "Writing preview into default-named file: ^1", _previewFile )

                  end

                  local s, m = fso:writeFile( _previewFile, data )

                  if s then

                      app:logVerbose( "Wrote preview file: ^1", _previewFile )

                      return _previewFile

                  else

                      return nil, m

                  end

              else

                  return nil -- no real error, just no preview at that level.

              end

          end   

       

          minLevel = minLevel or 1

       

          local status

          status, content = LrTasks.pcall( LrFileUtils.readFile, previewPath )

          if status and content then

              repeat

                  local file, errm = getImageFile() -- at level

                  if file then

                      return file, nil, level

                  elseif errm then

                      return nil, errm

                  elseif level > minLevel then

                      level = level - 1

                  else

                      return nil, str:fmt( "No preview for ^1 at any acceptable level", photoPath )

                  end

              until level <= 0

              return nil, str:fmt( "Unable to obtain preview for ^1", photoPath )

          else

              return nil, str:fmt( "Unable to read preview source file at ^1, error message: ^2", previewPath, content )

          end   

      end

       

      This function is working great so far, but as of 2011-09-29 it has not been rigorously tested, so it may have a bug or two...

      It is based on the elare plugin framework available here (including source code): https://www.assembla.com/spaces/lrdevplugin/

       

      You will need sqlite3 executable from here: http://www.sqlite.org/sqlite.html

      - put it in lr(dev)plugin dir

       

      Note: view-factory's picture component will accept a path as resource id, so to use:

       

      local pictureFile, errm = cat:getPreview( photo, nil, nil, 4 )

      if pictureFile then

         items[#items + 1] = vf:picture {

             value = pictureFile,

          }

      end

       

      Note: the above code is intended for "sample/example" only -

       

      MUST DO:

      - Handle portrait orientation properly...

       

      MAYBE DO:

      - Handle AdobeRGB profile assignment - not needed for thumbs, but maybe for the biggies...

      - Optimize for multi-photo use.

      - Change detection for sake of caching for repeated access (like scrolling thumbnails).

       

      @2011-10-04, the code at Assembla.com (see link above) takes care of all these things, and then some...;-)

       

      Rob

        • 1. Re: Here is a function to read preview images from active catalog
          jarnoh Community Member

          There's also undocumented view control:

           

          LrPhotoPictureView.makePhotoPictureView({

                    width=400,

                    height=400,

                    photo=catalog:getTargetPhoto()

          })

           

          Should give you a nice view of the currently selected photo if you add this to your view

           

          Jarno

          • 2. Re: Here is a function to read preview images from active catalog
            johnrellis CommunityMVP

            Excellent.  How did you learn about this?  (It's good to learn how to fish, rather than be just given fish.)

            • 3. Re: Here is a function to read preview images from active catalog
              jarnoh Community Member

              In Mac version there's a file called Contents/PlugIns/MultipleMonitor.lrmodule/Contents/Resources/LrPhotoPictureView.lua

               

              Simple strings command on this gives a lot of clues and the rest is just trying it out.

               

              Jarno

              • 5. Re: Here is a function to read preview images from active catalog
                johnrellis CommunityMVP

                Using the technique of grepping for suspect strings, I found the following additional importable modules. LrRemoteCommunication might be of particular interest to Rob, if he can figure out how to use it:

                 

                LrPhotoPictureView

                {--table: 1

                    makePhotoPictureView = function: 0000000015836630}

                 

                LrRemoteCommunication

                {--table: 1

                    closeNamedConnection = function: 00000000114B3570,

                    pollForMessage = function: 00000000159251F0,

                    spawnTaskAndConnect = function: 0000000013EF8610,

                    sendMessageToServer = function: 00000000114B3660,

                    launchApplicationWithPath = function: 0000000015A592D0}

                 

                LrTableUtils

                {--table: 1

                    debugDumpTable = function: 0000000000589430}

                 

                LrUUID
                {--table: 1
                    generateUUID = function: 0000000005B75140}

                • 6. Re: Here is a function to read preview images from active catalog
                  clvrmnky Community Member

                  jarnoh wrote:

                   

                  There's also undocumented view control:

                   

                  LrPhotoPictureView.makePhotoPictureView({

                            width=400,

                            height=400,

                            photo=catalog:getTargetPhoto()

                  })

                   

                  Should give you a nice view of the currently selected photo if you add this to your view

                   

                  Cool. Alas, it appears that width, height cannot be bound to an observable table. I suppose this sort of makes sense, as you wouldn't want the photo jumping around in the container.

                   

                  But as a trick for making invisible objects not take up any space, setting this to 0 when a view is first brought up would be nice.

                   

                  Duh. I should learn how to use  LrView.conditionalItem() shouldn't I?

                   

                  Message was edited by: clvrmnky

                  • 7. Re: Here is a function to read preview images from active catalog
                    jarnoh Community Member

                    I did some digging, and it looks like LR4 beta has new view component, viewFactory:catalog_photo.  API seems to work just like LrPhotoPictureView.

                    • 9. Re: Here is a function to read preview images from active catalog
                      Judas Gutenberg Community Member

                      hey rob -- i'm using this function to generate thumbnails, and it mostly works for most of platforms.  but on some macs i get an "r2"  from this line:

                       

                      local r1, r2, r3 = app:executeCommand( exe, param, { targ }, nil, 'del' )

                       

                      and the thumbnail either doesn't get generated or it doesn't get to the right place.

                      what could be causing that?  thanks!!

                      • 10. Re: Here is a function to read preview images from active catalog
                        areohbee Community Member

                        Hi Judas,

                         

                        I dunno, but first place to look is verbose log file, and debug log.

                         

                        I can be contacted outside the forum too, if your issues relate more to code in Elare Plugin Framework than SDK in general.

                         

                        robcole.com - Contact Me

                         

                        Rob

                        • 11. Re: Here is a function to read preview images from active catalog
                          Judas Gutenberg Community Member

                          thanks rob,

                           

                          i logged the errors from your function and this what i'm seeing for failures of thumbnail creation:

                           

                          Invalid exit code - expected 0, but 32256 was returned by command: "/Users/XXXXX/Library/Application Support/Adobe/Lightroom/KeywordPerfect3.lrdevplugin/sqlite3" "/Users/Photography/Lightroom 5 Catalog/Lightroom 5 Catalog Previews.lrdata/previews.db" "select uuid, digest from ImageCacheEntry where imageId=3547190" > "/Users/XXXXX/Library/Application

                           

                          what would exit code 32256 for sqlite mean? 

                          • 12. Re: Here is a function to read preview images from active catalog
                            Judas Gutenberg Community Member

                            The result codes I can find in a Google Search only go to 101:  Result Codes

                            • 13. Re: Here is a function to read preview images from active catalog
                              areohbee Community Member

                              Path to output file was truncated in your post?

                               

                              "/Users/XXXXX/Library/Application

                               

                              Perhaps the exit code was from OS not sqlite3 ? (just guessing).

                               

                              R

                              • 14. Re: Here is a function to read preview images from active catalog
                                Maxim3 Community Member

                                Thank for so interesting and undocumented feature. Where you get it?

                                [quote]In Mac version there's a file called Contents/PlugIns/MultipleMonitor.lrmodule/Contents/Resources/LrPhotoPictureView.lua[/quot e]

                                On my version of Lr3 this lua script is compiled. Did you puzzle out decompiled code?

                                 

                                Unfortunatelly, there are some issues with this way of getting preview:

                                1) sqlite doesnt work with localized paths like i,e c:\users\Администратор... I tried to solve it with using utf8 or uri formats for path but useless. I solved it with usind short form of path (in windows. On mac I didnt yet tested it). To get a short form of path I use a simple script launched in cmd shell, some like "echo %~s1" and it take a shorf form of path i.e.

                                C:\Users\836D~1\...

                                     instead of

                                C:\Users\Администратор\

                                 

                                2) The main problem is what if I work in "Develop" mode and if I change an image setting then previews will not be refreshed until I dont switch into "Library".

                                 

                                Maybe somebody know a way how to push Lightroom to refresh previews db into "Develop" mode?

                                 

                                Of course, all it is actually for Lr3 and Lr4 only. In Lr5 there is another API for get smart preview. But I would like to support and Lr3-4 audience also

                                • 15. Re: Here is a function to read preview images from active catalog
                                  Maxim3 Community Member


                                  I was too hasty when I said what this task is easily in Lr5

                                   

                                  Indeed, smart preview function gets preview in dng format and into maximal possible resolution. But for following manual processing I would like to have preview in i.e. jpg format and in small resolution.

                                   

                                  I put some expectations on function photo:requireJpegThumbnail but as I can read in forum(s) this function also doesnt work in "Develop" mode and it needs to switch to "Library" anyway.

                                  And it is not clear how to use the result of this funtion? What data and in what format it return? There is a way how to transform it into a normal jpg file?

                                  • 16. Re: Here is a function to read preview images from active catalog
                                    Maxim3 Community Member

                                    No many answers in this theme I can see unfortunately

                                     

                                    It was worth to try. Indeed Lr's 5 requireJpegThumbnail function works fine. Except what it always return jpg into maximal possible resolution even if I ask to make thumbnail 200x200

                                     

                                    The another problem is what if I change a setting inside of plug-in with using photo:applyDevelopPreset then Lr doesnt refresh preview DB even if I switch into "Library" module. And it is problem.

                                     

                                    Somebody know, where is some Lr API functions which should be call togather with  photo:applyDevelopPreset for to push LR to refresh preview db?

                                     

                                     


                                    • 17. Re: Here is a function to read preview images from active catalog
                                      johnrellis CommunityMVP

                                      Unfortunately, Rob Cole (the original poster), formerly very active, hasn't participated in these forums for over two months.  He's had a lot of experience with the issues you raise.

                                       

                                      The only suggestion I have for the refresh issue is to have your plugin wait for a while after applying photo:applyDevelopPreset(), before requesting the thumbnail. I recall Rob making comments at one point that he had observed it could sometimes take a while for the new develop settings to "take".  You might try sleeping for a long time, e.g. 120 seconds, to see if it could make a difference.  If it does, then you could do a smarter wait by repeatedly polling photo:getDevelopSettings() until you notice the new settings take effect (with a suitable sleep of a quarter second or so each time through the loop).

                                      • 18. Re: Here is a function to read preview images from active catalog
                                        Maxim3 Community Member

                                        Thank Johnrellis for your suggestion.

                                        I think, it is not so easily.

                                        If I call applyDevelopPreset during a modal dialog is shown (into an observer funtion). Then everything works fine. Presets are appling and preview db is refreshing.

                                        But, if I call the applyDevelopPreset after the dialog was closed (I need to rollback if I press "Cancel") then preview db never refresh, even if I switch to Library mode by hand and wait. (In any way, It is not acceptable to wait 120 sec (2hours!) until preview will be refreshed)

                                         

                                        What is difference in cases if I call applyDevelopPreset during a modal dialog is shown and in case if I call this when a modal dialog has been closed?

                                        • 19. Re: Here is a function to read preview images from active catalog
                                          johnrellis CommunityMVP
                                          It is not acceptable to wait 120 sec (2hours!)

                                          Agreed.  I only suggested a large maximum wait time as an experiment to establish definitively whether the plugin needs to wait "a little" before the change takes effect.  According to your experiment, it doesn't.

                                          But, if I call the applyDevelopPreset after the dialog was closed (I need to rollback if I press "Cancel") then preview db never refresh

                                          When you say the "preview db never refreshes", does the thumbnail in Library mode get properly updated?  Or it just the preview returned by the getPreview() function above that fails to return the proper preview?

                                          • 20. Re: Here is a function to read preview images from active catalog
                                            Maxim3 Community Member

                                            It means what the preview file into Lr's previews subfolder (i.e AC279813-FDBB-484E-97A5-387B3B09D495-07cc63f155500a902b21fef7be6585b5.lrprev)

                                            always has the fileModificationDate older then 'lastEditTime' picture's metadata. And Lr's preview db always returns a reference on this dated preview file.

                                             

                                            As I said, if I call applyDevelopPreset as a reaction on a slider changing when a modal dialog is shown. And if I close the dialog and call the plug-in again then it checks, is preview freshest or not. If not then the plug-in switches Lr into "Library" mode and back and preview db is refreshed.

                                             

                                            But if I call the applyDevelopPreset after the dialog is closed (in case if user pressed "cancel") then Lr never refresh previews db even if I switch to "Library"  mode several times and wait few seconds

                                             

                                             

                                             


                                            • 21. Re: Here is a function to read preview images from active catalog
                                              Maxim3 Community Member


                                              I solved this problem

                                               

                                              Just everytime when I press "cancel" for dialog I delete the latest preview file and as result it pushes Lr to calculate a freshest preview when I switch Develop\Library modes.

                                               

                                              But this way looks as a trick and I suspect what many new issues are possible during deeply plug-in testing and usage

                                               

                                              These issues with preview there are into Lr3 (both, Mac and Win). In Lr5 I use requestJpegThumbnail function and this works moreless good. (except what it always returns jpg into maximal possible resolution)

                                              • 22. Re: Here is a function to read preview images from active catalog
                                                johnrellis CommunityMVP
                                                But this way looks as a trick and I suspect what many new issues are possible during deeply plug-in testing and usage

                                                Beware that getPreview()'s use of the preview database is entirely unsupported, whether or not you use this "trick", so there may be other issues that arise independent of the "trick".