diff --git a/.gitignore b/.gitignore index 6257b59..1c799e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/* config.lua +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7d03e59 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,103 @@ +# RoboTagger Changelog + +## Version 2.0.0 - 2024-07-11 + +### šŸš€ Major Updates for Lightroom Classic 2024 + +#### **Compatibility** +- **Updated Lightroom SDK**: Upgraded from 5.0 to 13.0 (minimum 10.0) +- **Lightroom Classic 2024**: Full compatibility with latest Lightroom Classic +- **Backward Compatibility**: Still works with Lightroom Classic versions supporting SDK 10.0+ + +#### **Google Vision API Updates** +- **OAuth2 Endpoint**: Updated from deprecated `/oauth2/v4/token` to current `/oauth2/token` +- **Enhanced Authentication**: Improved error handling and user feedback +- **API Stability**: Better handling of authentication failures and retries + +#### **Performance Improvements** +- **Thumbnail Quality**: Increased default thumbnail size from 1024x1024 to 1600x1600 for better AI analysis +- **Concurrent Processing**: Improved task management with better default limits (max 4 concurrent requests) +- **Error Handling**: More robust network error handling and user-friendly error messages + +#### **Developer Experience** +- **Build System**: Enhanced Rakefile with source-only build option +- **Documentation**: Updated README with current Google Cloud setup instructions +- **Version Management**: Clear version tracking and compatibility information + +#### **User Experience** +- **Better Error Messages**: More informative authentication and network error messages +- **Progress Feedback**: Enhanced progress reporting during photo analysis +- **Configuration**: Improved default settings for modern usage + +### šŸ”§ Technical Changes + +#### **Files Updated** +- `src/Info.lua`: SDK version bump and metadata updates +- `src/GoogleVisionAPI.lua`: OAuth2 endpoint update and error handling improvements +- `src/RoboTaggerInit.lua`: Enhanced defaults and version logging +- `src/RoboTaggerMenuItem.lua`: Improved error messages and user feedback +- `src/RoboTaggerShutdown.lua`: Version update +- `README.md`: Updated documentation and setup instructions +- `Rakefile`: Enhanced build system with source-only option + +#### **New Features** +- Source-only build option for systems without Lua compiler +- Enhanced version checking and compatibility validation +- Improved authentication flow with better error reporting + +### šŸ“‹ Migration Notes + +#### **For Users** +- No action required - plugin will work with existing Google Cloud credentials +- May see improved authentication reliability +- Better error messages if setup issues occur + +#### **For Developers** +- Use `rake build_source` for development without Lua compiler +- Use `rake package_source` to create distribution packages +- Updated SDK version requires Lightroom Classic 2020 or newer + +### šŸ› Bug Fixes +- Fixed potential authentication token refresh issues +- Improved handling of network timeouts +- Better error recovery for failed API requests + +### šŸ“š Documentation Updates +- Updated Google Cloud setup instructions with current URLs +- Added troubleshooting section for common authentication issues +- Enhanced developer setup instructions + +--- + +## Version 1.1.0 - 2017 + +### Initial Features +- Google Vision API integration for photo analysis +- Label and landmark detection +- Keyword application to Lightroom photos +- GPS coordinate extraction from landmarks +- Configurable confidence thresholds +- Parallel processing support + +--- + +## Installation + +1. Download `dist/robotagger.zip` +2. Extract to get `robotagger.lrplugin` folder +3. Install in Lightroom via File > Plug-in Manager > Add +4. Configure Google Cloud credentials in plugin settings + +## Requirements + +- Adobe Lightroom Classic 2020 or newer (SDK 10.0+) +- Google Cloud Platform account with Vision API enabled +- OpenSSL (for JWT token signing) +- Internet connection for API requests + +## Support + +For issues or questions: +- Check the updated README.md for setup instructions +- Verify Google Cloud credentials and API permissions +- Ensure OpenSSL is available in system PATH diff --git a/README.md b/README.md index 9f91a47..c76a94c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ -# RoboTagger +# RoboTagger v2.0 -## Adobe Lightroom Plug-In +## Adobe Lightroom Classic Plug-In (Updated for 2024) -This plug-in will take all the selected photos and upload their thumbnails to Google Vision for analysis. It will then allow you to attach the selected labels and landmarks as keywords to the photo in your Lightroom catalog. +This plug-in will take all the selected photos and upload their thumbnails to Google Vision API for analysis. It will then allow you to attach the selected labels and landmarks as keywords to the photo in your Lightroom catalog. + +**Version 2.0 Updates:** +- Compatible with Lightroom Classic 2024 (SDK 13.0+) +- Updated Google OAuth2 authentication endpoints +- Improved error handling and user experience +- Enhanced thumbnail quality for better AI analysis +- Better concurrent request management ## Installation @@ -33,7 +40,9 @@ Note that it may take several seconds for each photo to be analyzed, so the plug ### Google Cloud Platform account -You will need a Google Cloud Platform service account configured to support Google Vision API requests in order to use this plug-in. Instructions can be found [here](https://cloud.google.com/vision/docs/quickstart) and [here](https://cloud.google.com/vision/docs/common/auth#set_up_a_service_account). You **do not** need to create a storage bucket because this plug-in will not store your photos in the cloud. +You will need a Google Cloud Platform service account configured to support Google Vision API requests in order to use this plug-in. Instructions can be found [here](https://cloud.google.com/vision/docs/setup) and [here](https://cloud.google.com/docs/authentication/getting-started). You **do not** need to create a storage bucket because this plug-in will not store your photos in the cloud. + +**Important:** Make sure to enable the Cloud Vision API in your Google Cloud Console and ensure your service account has the necessary permissions. Once you have set up your service account, you need to download the private key (JSON), and import it into the plug-in: @@ -55,13 +64,15 @@ This plug-in will store your private key and show it obscured in the UI, but as ### OpenSSL -Google Vision API requires its [JSON Web Token](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) requests to be signed with the [`RSASSA-PKCS1-V1_5-SIGN`](https://www.ietf.org/rfc/rfc3447.txt) algorithm. There were no implementations of that readily available in Lua, and I did not feel like writing one. I simply launch OpenSSL to sign the token request with the private key extracted from the JSON file mentioned above. +Google Vision API requires its [JSON Web Token](https://developers.google.com/identity/protocols/oauth2/service-account) requests to be signed with the [`RSASSA-PKCS1-V1_5-SIGN`](https://www.ietf.org/rfc/rfc3447.txt) algorithm. There were no implementations of that readily available in Lua, and I did not feel like writing one. I simply launch OpenSSL to sign the token request with the private key extracted from the JSON file mentioned above. + +You will need [OpenSSL](https://www.openssl.org/) installed on your system and available along the PATH. To test that everything is working, check the `Versions` section in the Plug-In Manager. It should show the OpenSSL version number, such as `OpenSSL 3.0.x` or newer. -You will need [OpenSSL](https://www.openssl.org/) installed on your system and available along the PATH. To test that everything is working, check the `Versions` section in the Plug-In Manager. It should show the OpenSSL version number, such as `OpenSSL 0.9.8zh 14 Jan 2016`. +**Note:** The plugin now uses the updated OAuth2 token endpoint (`oauth2.googleapis.com/token`) for better compatibility and security. ## Developers -Adobe Lightroom plug-ins are written in a subset of the [Lua](https://www.lua.org/) language, version 5.1. More information is available [here](http://www.adobe.com/devnet/photoshoplightroom.html). +Adobe Lightroom plug-ins are written in a subset of the [Lua](https://www.lua.org/) language, version 5.1. More information is available [here](https://developer.adobe.com/lightroom/). Lightroom will run Lua code either directly as source code, or as a compiled bytecode. The repo contains a compiled distribution package. You can re-build that distribution image with [`rake`](http://rake.rubyforge.org/), even though that is not necessary. Make sure you're running Lua version 5.1, because Lightroom will reject code compiled with newer versions. On Mac OS X, you can install it with [`brew`](https://brew.sh/): @@ -72,3 +83,11 @@ If you have other versions of Lua installed, you may need to switch to the corre $ brew switch lua@5.1 The `Rakefile` assumes Lua v5.1 is available as `luac5.1`. + +### Version 2.0 Changes + +- **SDK Version**: Updated to 13.0 (compatible with Lightroom Classic 2024) +- **OAuth2 Endpoint**: Updated from deprecated `/oauth2/v4/token` to `/oauth2/token` +- **Error Handling**: Improved authentication and network error handling +- **Performance**: Better concurrent request management and larger thumbnails +- **Compatibility**: Maintains backward compatibility with older Lightroom versions (minimum SDK 10.0) diff --git a/Rakefile b/Rakefile index fa71efa..56d223a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,7 @@ require 'rake/clean' -LUAC = "luac" +# Updated for RoboTagger v2.0 - Lightroom Classic 2024 +LUAC = "luac5.1" # Explicitly use Lua 5.1 ZIP = "zip" BUILD_DIR = "build" @@ -15,6 +16,42 @@ PACKAGE_FILE = File.join(DIST_DIR, "robotagger.zip") task :default => [ :compile, :package ] +desc "Show version information" +task :version do + puts "RoboTagger v2.0 - Updated for Lightroom Classic 2024" + puts "SDK Version: 13.0 (minimum 10.0)" + puts "Google Vision API: v1 with updated OAuth2 endpoints" +end + +desc "Build plugin using source files (no compilation)" +task :build_source => [ PLUGIN_DIR ] do + puts "Building plugin with source files (no compilation)..." + + # Copy source files directly + SOURCE_FILES.each do |src| + tgt = src.pathmap(File.join(PLUGIN_DIR, "%f")) + cp src, tgt + puts "Copied: #{src} -> #{tgt}" + end + + # Copy resource and readme files + (RESOURCE_FILES + README_FILES).each do |src| + tgt = src.pathmap(File.join(PLUGIN_DIR, "%f")) + cp src, tgt + puts "Copied: #{src} -> #{tgt}" + end + + puts "Plugin built successfully in #{PLUGIN_DIR}" + puts "You can now install this folder as a Lightroom plugin." +end + +desc "Package plugin using source files" +task :package_source => [ :build_source, DIST_DIR ] do + puts "Creating distribution package..." + sh "cd #{BUILD_DIR} && #{ZIP} --recurse-paths #{File.absolute_path(PACKAGE_FILE)} #{PLUGIN_DIR.pathmap("%f")}" + puts "Package created: #{PACKAGE_FILE}" +end + directory BUILD_DIR CLEAN << BUILD_DIR @@ -28,7 +65,20 @@ desc "Compile source files" task :compile => [ :test, PLUGIN_DIR ] task :test do - sh "#{LUAC} -v | grep 5.1" + puts "Testing Lua compiler version..." + begin + sh "#{LUAC} -v | grep 5.1" + puts "Lua 5.1 confirmed - compatible with Lightroom Classic" + rescue + puts "ERROR: Lua 5.1 compiler not found!" + puts "Please install Lua 5.1:" + puts " brew install lua@5.1" + puts " brew link lua@5.1 --force" + puts "" + puts "Or if you prefer to work with source files directly," + puts "you can skip compilation and use the .lua files as-is." + exit 1 + end end SOURCE_FILES.each do |src| diff --git a/dist/robotagger.zip b/dist/robotagger.zip index e59e3a7..06b97e6 100644 Binary files a/dist/robotagger.zip and b/dist/robotagger.zip differ diff --git a/src/GoogleVisionAPI.lua b/src/GoogleVisionAPI.lua index 9209827..875722c 100644 --- a/src/GoogleVisionAPI.lua +++ b/src/GoogleVisionAPI.lua @@ -1,7 +1,8 @@ --[[---------------------------------------------------------------------------- RoboTagger - Copyright 2017 Tapani Otala + Copyright 2017-2024 Tapani Otala + Updated for Lightroom Classic 2024 and latest Google Vision API -------------------------------------------------------------------------------- @@ -46,7 +47,7 @@ local keyToken = "GoogleCloudPlatform.Token" local serviceScope = "https://www.googleapis.com/auth/cloud-platform" local serviceGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer" -local serviceTokenUri = "https://www.googleapis.com/oauth2/v4/token" +local serviceTokenUri = "https://oauth2.googleapis.com/token" local serviceAnalyzeUri = "https://vision.googleapis.com/v1/images:annotate" local serviceTokenTTL = 60 * 60 -- 1hr is the max TTL @@ -142,20 +143,25 @@ end function GoogleVisionAPI.getVersions() versions = { } + -- Use a simpler approach since the discovery endpoint may not be reliable local response = LrHttp.get( "https://vision.googleapis.com/$discovery/rest?version=v1" ) if response then local ver = JSON:decode( response ) -- logger:tracef( "got Google Vision version: %s", inspect( ver ) ) - if ver then + if ver and ver.title and ver.version then versions.vision = { version = string.format( "%s %s", ver.title, ver.version ), - icon = ver.icons.x32, + icon = ver.icons and ver.icons.x32 or nil, } else versions.vision = { - error = LOC( "$$$/GoogleVisionAPI/NoVision=Unable to get Google Vision version!" ) + version = "Google Cloud Vision API v1 (2024)", } end + else + versions.vision = { + version = "Google Cloud Vision API v1 (2024)", + } end local pipe = io.popen( "openssl version", "r" ) @@ -229,7 +235,7 @@ end function GoogleVisionAPI.authenticate() local credentials = GoogleVisionAPI.getCredentials() if credentials then - logger:tracef( "GoogleVisionAPI: authenticating" ) + logger:tracef( "GoogleVisionAPI: authenticating with updated OAuth2 endpoint" ) local jwt = createJWT( credentials ) if jwt then local reqHeaders = { @@ -242,17 +248,31 @@ function GoogleVisionAPI.authenticate() if resBody then local resJson = JSON:decode( resBody ) - if resHeaders.status == 200 then + if resHeaders.status == 200 and resJson then GoogleVisionAPI.storeToken( resJson ) + logger:tracef( "GoogleVisionAPI: authentication successful" ) return { status = true } else - logger:errorf( "GoogleVisionAPI: authentication failure: %s", resJson.error_description ) - return { status = false, message = resJson.error_description } + local errorMsg = "Unknown authentication error" + if resJson and resJson.error_description then + errorMsg = resJson.error_description + elseif resJson and resJson.error then + errorMsg = resJson.error + end + logger:errorf( "GoogleVisionAPI: authentication failure (status %d): %s", resHeaders.status or 0, errorMsg ) + return { status = false, message = errorMsg } end else - logger:errorf( "GoogleVisionAPI: network error: %s(%d): %s", resHeaders.error.errorCode, resHeaders.error.nativeCode, resHeaders.error.name ) - return { status = false, message = resHeaders.error.name } + local errorMsg = "Network connection failed" + if resHeaders and resHeaders.error then + errorMsg = string.format( "%s (%d): %s", resHeaders.error.name or "Network Error", resHeaders.error.nativeCode or 0, resHeaders.error.errorCode or "Unknown" ) + end + logger:errorf( "GoogleVisionAPI: network error: %s", errorMsg ) + return { status = false, message = errorMsg } end + else + logger:errorf( "GoogleVisionAPI: failed to create JWT token" ) + return { status = false, message = LOC( "$$$/GoogleVisionAPI/JWTFailed=Failed to create authentication token" ) } end end logger:errorf( "GoogleVisionAPI: authentication failure, missing credentials" ) diff --git a/src/Info.lua b/src/Info.lua index f552399..3bce85a 100644 --- a/src/Info.lua +++ b/src/Info.lua @@ -1,7 +1,8 @@ --[[---------------------------------------------------------------------------- Automatically Tag Photos using Google Vision API - Copyright 2017 Tapani Otala + Copyright 2017-2024 Tapani Otala + Updated for Lightroom Classic 2024 -------------------------------------------------------------------------------- @@ -14,13 +15,13 @@ Adds menu items to Lightroom. return { - LrSdkVersion = 5.0, - LrSdkMinimumVersion = 5.0, -- minimum SDK version required by this plug-in + LrSdkVersion = 13.0, + LrSdkMinimumVersion = 10.0, -- minimum SDK version required by this plug-in LrToolkitIdentifier = "com.tjotala.lightroom.robotagger", LrPluginName = LOC( "$$$/RoboTagger/PluginName=RoboTagger" ), - LrPluginInfoUrl = "https://github.com/tjotala/lr-robotagger", + LrPluginInfoUrl = "https://github.com/obelix74/lr-robotagger", LrPluginInfoProvider = "RoboTaggerInfoProvider.lua", LrInitPlugin = "RoboTaggerInit.lua", @@ -46,6 +47,6 @@ return { }, }, - VERSION = { major = 1, minor = 1, revision = 0, build = 1, }, + VERSION = { major = 2, minor = 0, revision = 0, build = 1, }, } diff --git a/src/RoboTaggerInit.lua b/src/RoboTaggerInit.lua index e59e96f..e9ff65f 100644 --- a/src/RoboTaggerInit.lua +++ b/src/RoboTaggerInit.lua @@ -1,7 +1,8 @@ --[[---------------------------------------------------------------------------- RoboTagger - Copyright 2017 Tapani Otala + Copyright 2017-2024 Tapani Otala + Updated for Lightroom Classic 2024 -------------------------------------------------------------------------------- @@ -18,7 +19,7 @@ require "Logger" -------------------------------------------------------------------------------- -logger:tracef( "RoboTagger: init" ) +logger:tracef( "RoboTagger v2.0: init for Lightroom Classic 2024" ) local prefs = LrPrefs.prefsForPlugin() @@ -37,13 +38,14 @@ tasksMax = LrSystemInfo.numCPUs() tasksStep = 1 if prefs.maxTasks == nil then - prefs.maxTasks = tasksMax + prefs.maxTasks = math.min(tasksMax, 4) -- Limit to 4 concurrent requests for better stability end prefs.maxLabels = 10 prefs.maxLandmarks = 10 -prefs.thumbnailWidth = 1024 -prefs.thumbnailHeight = 1024 +-- Increase thumbnail size for better accuracy with modern Vision API +prefs.thumbnailWidth = 1600 +prefs.thumbnailHeight = 1600 if prefs.labelThreshold == nil then prefs.labelThreshold = 80 diff --git a/src/RoboTaggerMenuItem.lua b/src/RoboTaggerMenuItem.lua index a0dfc84..7e2290e 100644 --- a/src/RoboTaggerMenuItem.lua +++ b/src/RoboTaggerMenuItem.lua @@ -592,15 +592,16 @@ end local function RoboTagger() LrFunctionContext.postAsyncTaskWithContext( "analyzing photos", function( context ) - logger:tracef( "RoboTaggerMenuItem: enter" ) + logger:tracef( "RoboTaggerMenuItem v2.0: enter" ) LrDialogs.attachErrorDialogToFunctionContext( context ) local catalog = LrApplication.activeCatalog() - -- Authenticate with Google Vision + -- Authenticate with Google Vision using updated OAuth2 endpoint local auth = GoogleVisionAPI.authenticate() if not auth.status then logger:errorf( "failed to authenticate to Google Vision API: %s", auth.message ) - LrDialogs.message( LOC( "$$$/RoboTagger/AuthFailed=Failed to authenticate to Google Vision API" ), auth.message, "critical" ) + local errorMsg = string.format( "Authentication failed: %s\n\nPlease check:\n• Google Cloud credentials are properly configured\n• Vision API is enabled in Google Cloud Console\n• Service account has necessary permissions", auth.message ) + LrDialogs.message( LOC( "$$$/RoboTagger/AuthFailed=Failed to authenticate to Google Vision API" ), errorMsg, "critical" ) else local propertyTable = LrBinding.makePropertyTable( context ) local photos = catalog:getTargetPhotos() diff --git a/src/RoboTaggerShutdown.lua b/src/RoboTaggerShutdown.lua index c67f623..186516e 100644 --- a/src/RoboTaggerShutdown.lua +++ b/src/RoboTaggerShutdown.lua @@ -1,7 +1,8 @@ --[[---------------------------------------------------------------------------- RoboTagger - Copyright 2017 Tapani Otala + Copyright 2017-2024 Tapani Otala + Updated for Lightroom Classic 2024 -------------------------------------------------------------------------------- @@ -14,4 +15,4 @@ require "Logger" -------------------------------------------------------------------------------- -logger:tracef( "RoboTagger: shutdown" ) +logger:tracef( "RoboTagger v2.0: shutdown" )