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

Plugin code help for writing metadata

Explorer ,
Mar 19, 2025 Mar 19, 2025

Hi I'm trying to write a lightroom classic plugin, and I am having a hard  time simply saving metadata to a photo.  In my test plugin i am simply trying to set a rating to a photo, but the saving keeps failing.  Of course i know you can do this in lightroom, but this is just a test for something later.  thank you for any advice someone can give.

 

the full plugin code is here https://github.com/radialmonster/lr-image-rater

 
 
 
local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrTasks = import 'LrTasks'
local LrView = import 'LrView'
local LrLogger = import 'LrLogger'

-- Initialize logger with default Lightroom log location
local logger = LrLogger('ImageRaterLogger')
logger:enable("logfile")
logger:info("Logger initialized.")
LrDialogs.message("Logger initialized.") -- Debug message

-- Function to set a rating
local function setRating(rating)
    LrDialogs.message("setRating function called with rating: " .. (rating or "nil")) -- Debug message
    local catalog = LrApplication.activeCatalog()
    local photo = catalog:getTargetPhoto()

    if photo then
        LrDialogs.message("Photo selected. Attempting to set rating: " .. (rating or "nil")) -- Debug message

        LrFunctionContext.callWithContext("Set Rating Context", function(context)
            local success, err = pcall(function()
                catalog:withWriteAccessDo("Set Rating", function()
                    photo:setRawMetadata('rating', rating)
                end, {
                    timeout = 1, -- Wait for up to 1 second for write access
                    callback = function()
                        LrDialogs.message("Write access timeout. Could not set rating.") -- Debug message
                    end
                })
            end)

            if success then
                LrDialogs.message("Rating set successfully.") -- Debug message
            else
                LrDialogs.message("Failed to set rating: " .. (err or "Unknown error")) -- Debug message
            end

            local message = rating and ("Rated " .. rating .. " stars") or "Rating cleared"
            LrDialogs.showBezel(message)
        end)
    else
        LrDialogs.message("No photo selected. Cannot set rating.") -- Debug message
    end
end

-- Show the rating dialog
local function showRater()
    local catalog = LrApplication.activeCatalog()
    if not catalog then
        logger:info("No active catalog found.")
        LrDialogs.message("No active catalog found.") -- Debug message
        return
    end

    local photo = catalog:getTargetPhoto()
    if not photo then
        logger:info("No photo selected in catalog.")
        LrDialogs.message("No photo selected in catalog.") -- Debug message
        LrDialogs.message("Please select a photo", "No photo selected", "info")
        return
    end

    logger:info("Photo selected. Showing rating dialog.")
    LrDialogs.message("Photo selected. Showing rating dialog.") -- Debug message

    local f = LrView.osFactory()

    local contents = f:column {
        spacing = f:control_spacing(),

        f:row {
            f:push_button {
                title = "Rate 1",
                action = function()
                    LrDialogs.message("Rate 1 button clicked") -- Debug message
                    setRating(1)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Rate 2",
                action = function()
                    LrDialogs.message("Rate 2 button clicked") -- Debug message
                    setRating(2)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Rate 3",
                action = function()
                    LrDialogs.message("Rate 3 button clicked") -- Debug message
                    setRating(3)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
        },

        f:row {
            f:push_button {
                title = "Rate 4",
                action = function()
                    LrDialogs.message("Rate 4 button clicked") -- Debug message
                    setRating(4)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Rate 5",
                action = function()
                    LrDialogs.message("Rate 5 button clicked") -- Debug message
                    setRating(5)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Clear Rating",
                action = function()
                    LrDialogs.message("Clear Rating button clicked") -- Debug message
                    setRating(nil)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
        },
    }

    LrDialogs.presentModalDialog({
        title = "Rate Image",
        contents = contents,
    })
end

-- Start the task
LrTasks.startAsyncTask(showRater)
TOPICS
SDK
180
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

correct answers 1 Correct answer

LEGEND , Mar 20, 2025 Mar 20, 2025

Here's your previous script modified so the Rate 1 button works. Changes:

 

1. Integrated with the debugging toolkit.  Debug.showErrors() wraps every function passed back into the SDK API. That allows errors to be displayed rather than silently discarded.

 

2. The action for the "Rate 1" button calls setRating() in an async task.

 

3. In setRating(), LrTasks.pcall() is used instead of the Lua built-in pcall().  The built-in pcall() interferes with LR's tasking and the called function is always invoked

...
Translate
LEGEND ,
Mar 19, 2025 Mar 19, 2025

[This post contains formatting and embedded images that don't appear in email. View the post in your Web browser.]

 

There are a couple of errors:

 

1. The variable "LrFunctionContext" isn't defined -- it needs to be imported:

 

johnrellis_0-1742438850141.png

 

2. catalog:withWriteAccessDo() needs to be called from an async task. It's invoked by the action callback passed to f:push_button(), and such callbacks are always invoked from LR's main task:

 

johnrellis_1-1742438953027.png

 

You're not seeing these errors because by default, plugin tasks don't have any error handlers -- LR just silently throws away the error and terminates the task!

 

I strongly recommend that you use either the Zerobrane IDE or my very lightweight Lightroom Debugging Toolkit.  Zerobrane is a full-fledged IDE, but it doesn't know about LR's task architecture, whereas the Toolkit does. Both require the same level of annotating your code to support error handling, break points, and stepping.

 

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
Explorer ,
Mar 19, 2025 Mar 19, 2025

I know this may be very simple however I am limited to a little bit of knowledge and help with AI.  I am spinning wheels here.  I have given your directions and still spinning.  I have whittled it down a simple test to just set a 3 star rating or cancel buttons.  but  i keep getting messages about either the

An internal error has occurred: LrCatalog:withWriteAccessDo: could not execute action 'Set Rating'. It was blocked by another write access call, and no timeout parameters were provided.

Or if I get past that I get a message about it failing to yield or something like that.


local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrTasks = import 'LrTasks'
local LrView = import 'LrView'
local LrFunctionContext = import 'LrFunctionContext'
local LrLogger = import 'LrLogger'

-- Create a logger to help with debugging
local myLogger = LrLogger('ImageRaterLogger')
myLogger:enable('logfile')

-- Main function to show the rating dialog
local function showRatingDialog()
    -- Run everything in a function context for proper cleanup
    LrFunctionContext.callWithContext("ImageRater", function(context)
        -- Create UI factory
        local f = LrView.osFactory()
       
        -- Get the catalog and target photo
        local catalog = LrApplication.activeCatalog()
        if not catalog then
            LrDialogs.message("No active catalog found.", "Error", "critical")
            return
        end

        local photo = catalog:getTargetPhoto()
        if not photo then
            LrDialogs.message("Please select a photo", "No photo selected", "info")
            return
        end
       
        -- Get current rating for display
        local currentRating = photo:getRawMetadata("rating") or 0
       
        -- Create simplified dialog with just two buttons
        local contents = f:column {
            spacing = f:control_spacing(),
            margin = 10,

            f:static_text {
                title = "Current rating: " .. currentRating .. " stars",
                font = "<system/bold>",
                alignment = "center",
            },
           
            f:static_text {
                title = "Would you like to rate this image 3 stars?",
                alignment = "center",
            },

            f:row {
                spacing = f:control_spacing(),
               
                f:push_button {
                    title = "Rate 3 Stars",
                    action = function()
                        myLogger:info("Rate 3 Stars button clicked")
                        -- First close the dialog immediately
                        LrDialogs.stopModalWithResult(contents, "ok")
                       
                        -- Then, start a completely separate async task
                        LrTasks.startAsyncTask(function()
                            myLogger:info("Starting async task for rating...")
                           
                            -- Try the simplest possible method
                            LrApplication.activeCatalog():setSelectedPhotosRating(3)
                           
                            myLogger:info("Rating applied")
                            LrDialogs.showBezel("Rated 3 stars")
                        end)
                    end,
                },
                f:push_button {
                    title = "Cancel",
                    action = function()
                        myLogger:info("Cancel button clicked")
                        LrDialogs.stopModalWithResult(contents, "cancel")
                    end,
                },
            },
        }

        -- Present dialog
        LrDialogs.presentModalDialog({
            title = "Rate Image",
            contents = contents,
        })
    end)
end

-- Start the task, but run in a clean async context
LrTasks.startAsyncTask(function()
    -- Add a small delay to ensure we're in a clean state
    LrTasks.sleep(0.1)
    showRatingDialog()
end)



I have previously tried to integrate your toolkit also and it really messed up the code i had trying to integrate that.  I had the rest of my plugin features working like i wanted, but it would not save the data.  I will literally pay you to help me.  I just want a simple plugin to select 1 photo, a box pops up and shows buttons for Rate 1, Rate 2, Rate 3, Rate 4, Rate 5, Clear Rating, Cancel.  and the user clicks their choice and it does that and closes the box. the catalog should immedietly show the new rating on the photo.  you can do a new one or modify the existing one.  dm me a quote if you would be open to do that.   thank you so much

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 ,
Mar 20, 2025 Mar 20, 2025

Here's your previous script modified so the Rate 1 button works. Changes:

 

1. Integrated with the debugging toolkit.  Debug.showErrors() wraps every function passed back into the SDK API. That allows errors to be displayed rather than silently discarded.

 

2. The action for the "Rate 1" button calls setRating() in an async task.

 

3. In setRating(), LrTasks.pcall() is used instead of the Lua built-in pcall().  The built-in pcall() interferes with LR's tasking and the called function is always invoked in the main task. The documentation for this is buried in the API reference for LrTasks.

 

4. The action for "Rate 1" was calling LrDialogs.stopModalWithResult (contents, ...).  But "contents" wasn't yet defined:

local contents = ... stopModalWithResult (contents, ...) ...

(The local variable "contents" isn't defined until after the "local" statement.)

 

Here's the modified script:

local Require = require 'Require'.path ("../common")
local Debug = require 'Debug'.init ()
require 'strict'

local LrApplication = import 'LrApplication'
local LrDialogs = import 'LrDialogs'
local LrFunctionContext = import 'LrFunctionContext'
local LrTasks = import 'LrTasks'
local LrView = import 'LrView'
local LrLogger = import 'LrLogger'

local showErrors = Debug.showErrors


-- Initialize logger with default Lightroom log location
local logger = LrLogger('ImageRaterLogger')
logger:enable("logfile")
logger:info("Logger initialized.")
LrDialogs.message("Logger initialized.") -- Debug message

-- Function to set a rating
local function setRating(rating)
    LrDialogs.message("setRating function called with rating: " .. (rating or "nil")) -- Debug message
    local catalog = LrApplication.activeCatalog()
    local photo = catalog:getTargetPhoto()

    if photo then
        LrDialogs.message("Photo selected. Attempting to set rating: " .. (rating or "nil")) -- Debug message
        LrFunctionContext.callWithContext("Set Rating Context 1", showErrors (function(context)
            end))

        LrFunctionContext.callWithContext("Set Rating Context", showErrors (function(context)
            local success, err = LrTasks.pcall(showErrors (function()
                catalog:withWriteAccessDo("Set Rating", function()
                    photo:setRawMetadata('rating', rating)
                end, {
                    timeout = 1, -- Wait for up to 1 second for write access
                    callback = function()
                        LrDialogs.message("Write access timeout. Could not set rating.") -- Debug message
                    end
                })
            end))


            if success then
                LrDialogs.message("Rating set successfully.") -- Debug message
            else
                LrDialogs.message("Failed to set rating: " .. (err or "Unknown error")) -- Debug message
            end

            local message = rating and ("Rated " .. rating .. " stars") or "Rating cleared"
            LrDialogs.showBezel(message)
        end))
    else
        LrDialogs.message("No photo selected. Cannot set rating.") -- Debug message
    end
end

-- Show the rating dialog
local function showRater()
    local catalog = LrApplication.activeCatalog()
    if not catalog then
        logger:info("No active catalog found.")
        LrDialogs.message("No active catalog found.") -- Debug message
        return
    end

    local photo = catalog:getTargetPhoto()
    if not photo then
        logger:info("No photo selected in catalog.")
        LrDialogs.message("No photo selected in catalog.") -- Debug message
        LrDialogs.message("Please select a photo", "No photo selected", "info")
        return
    end

    logger:info("Photo selected. Showing rating dialog.")
    LrDialogs.message("Photo selected. Showing rating dialog.") -- Debug message

    local f = LrView.osFactory()

    local contents
    contents = f:column {
        spacing = f:control_spacing(),

        f:row {
            f:push_button {
                title = "Rate 1",
                action = showErrors (function()
                    LrTasks.startAsyncTask (showErrors (function ()
                        LrDialogs.message("Rate 1 button clicked") -- Debug message
                        setRating(1)
                        LrDialogs.stopModalWithResult(contents, "ok")
                        end))
                end),
            },
            f:push_button {
                title = "Rate 2",
                action = function()
                    LrDialogs.message("Rate 2 button clicked") -- Debug message
                    setRating(2)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Rate 3",
                action = function()
                    LrDialogs.message("Rate 3 button clicked") -- Debug message
                    setRating(3)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
        },

        f:row {
            f:push_button {
                title = "Rate 4",
                action = function()
                    LrDialogs.message("Rate 4 button clicked") -- Debug message
                    setRating(4)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Rate 5",
                action = function()
                    LrDialogs.message("Rate 5 button clicked") -- Debug message
                    setRating(5)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
            f:push_button {
                title = "Clear Rating",
                action = function()
                    LrDialogs.message("Clear Rating button clicked") -- Debug message
                    setRating(nil)
                    LrDialogs.stopModalWithResult(contents, "ok")
                end,
            },
        },
    }

    LrDialogs.presentModalDialog({
        title = "Rate Image",
        contents = contents,
    })
end

-- Start the task
LrTasks.startAsyncTask(showErrors (showRater))

 

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
Explorer ,
Mar 20, 2025 Mar 20, 2025

This works!  Let me play with this a bit to test out thxxxxxxxxxxxxxx

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
Explorer ,
Mar 20, 2025 Mar 20, 2025

I'm in a very good point I just need to finish some more items and I'll share my plugin to you.  Bedtime now though thxxxx

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
Explorer ,
Mar 21, 2025 Mar 21, 2025
LATEST

OK I have finished (for now) my plugin!  I would be honored if you wanted to check it out

https://github.com/radialmonster/lr-image-rater

 

I don't like Lightroom's Compare feature. So I made my own. You compare images, and it will assign ratings to them.  If you try it and have any issues or suggestions please let me know.  thxxx

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