Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Here is a function to read preview images from active catalog

LEGEND ,
Oct 01, 2011 Oct 01, 2011

--- 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

TOPICS
SDK
7.7K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 05, 2011 Oct 05, 2011

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Oct 05, 2011 Oct 05, 2011

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Oct 05, 2011 Oct 05, 2011

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Oct 05, 2011 Oct 05, 2011

Excellent tip, thanks.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Oct 05, 2011 Oct 05, 2011

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}

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Oct 05, 2011 Oct 05, 2011

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Jan 14, 2012 Jan 14, 2012

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Jan 14, 2012 Jan 14, 2012

Neat, thanks.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Oct 30, 2014 Oct 30, 2014

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!!

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Oct 30, 2014 Oct 30, 2014

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Nov 02, 2014 Nov 02, 2014

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? 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Nov 02, 2014 Nov 02, 2014

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Nov 02, 2014 Nov 02, 2014

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Mar 23, 2015 Mar 23, 2015

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

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

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Mar 31, 2015 Mar 31, 2015


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?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Apr 03, 2015 Apr 03, 2015

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?


Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Apr 03, 2015 Apr 03, 2015

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

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Apr 06, 2015 Apr 06, 2015

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?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Apr 06, 2015 Apr 06, 2015
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?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Apr 07, 2015 Apr 07, 2015

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


Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Apr 07, 2015 Apr 07, 2015


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)

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
LEGEND ,
Apr 07, 2015 Apr 07, 2015
LATEST
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".

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines