14 Replies Latest reply: Mar 23, 2015 12:00 PM by Maxim3 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