Copy link to clipboard
Copied
--- 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
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
Excellent. How did you learn about this? (It's good to learn how to fish, rather than be just given fish.)
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
Excellent tip, thanks.
Copy link to clipboard
Copied
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}
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
I did some digging, and it looks like LR4 beta has new view component, viewFactory:catalog_photo. API seems to work just like LrPhotoPictureView.
Copy link to clipboard
Copied
Neat, thanks.
Copy link to clipboard
Copied
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!!
Copy link to clipboard
Copied
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.
Rob
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
The result codes I can find in a Google Search only go to 101: Result Codes
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
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).
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
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
Copy link to clipboard
Copied
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)
Copy link to clipboard
Copied
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".
Find more inspiration, events, and resources on the new Adobe Community
Explore Now