Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
midzelis committed Mar 25, 2024
0 parents commit 1946ff8
Show file tree
Hide file tree
Showing 17 changed files with 2,326 additions and 0 deletions.
53 changes: 53 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Create Release

on:
workflow_dispatch:
inputs:
tag:
description: 'The next release tag to use'
required: false
type: string
push:
branches:
- 'main'
tags:
- 'v*'

# Ensures that only one deploy task per branch/environment will run at a time.
concurrency:
group: environment-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:

- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
# - name: Env
# run: |
# # ACTIONS_RUNTIME_TOKEN, ACTIONS_RUNTIME_URL should be exposed
# env|sort
- name: Check out the repository to the runner
uses: actions/checkout@v4
- name: Bump version and push tag
id: tag_version
uses: mathieudutour/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
custom_tag: ${{ inputs.tag }}
- name: Zip it up
run: zip -r 'mi.Immich.Publisher.lrplugin.zip' . -x '*.git*'
- name: Create a GitHub release
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.tag_version.outputs.new_tag }}
name: Release ${{ steps.tag_version.outputs.new_tag }}
body: ${{ steps.tag_version.outputs.changelog }}
removeArtifacts: true
artifacts: "mi.Immich.Publisher.lrplugin.zip"
allowUpdates: true
31 changes: 31 additions & 0 deletions Info.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--
-- Copyright (c) 2024 Min Idzelis
--
-- This file is part of LR-Immich.
--
-- Foobar is free software: you can redistribute it and/or modify it under the terms
-- of the GNU General Public License as published by the Free Software Foundation,
-- either version 3 of the License, or (at your option) any later version.
--
-- LR-Immich is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with Foobar.
-- If not, see <https://www.gnu.org/licenses/>.
--

return {
LrSdkVersion = 3.0,
LrSdkMinimumVersion = 1.3,
LrToolkitIdentifier = 'midzelis-LR-Immich',
LrPluginName = "[mi] Immich Publisher",
LrPluginInfoProvider = 'plugininfo.lua',
LrPluginInfoUrl = "https://github.com/midzelis/immich-publisher",
LrExportServiceProvider = {
title = "Immich",
file = "publisher.lua",
},
LrInitPlugin = "init.lua",
VERSION = { major = 1, minor = 0, revision = 0, build = "0", },
}
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Immich Publisher [im]

A plugin for Lightroom Classic to publish collections from Lightroom into Immich Albums.

This plugin never deletes images!

This plugin creates a relationship between local photos and collections and immich assets and albums. If the same photo is present in multiple collections, that relationship is maintained in immich - there are no additional copies created, just memberships.

Exports will upload photos to immich without adding them to an alubm. However, if you later create a published collection with those previously exported pictures, the photos will not be re-uploaded, only the memberships will be added.

Export and Publish functions work.

### Known issues
* Immich doesn't support overwriting an image for the time being. So, if you make edits to an image in Lightroom, and try to republish, it will not show up on immich
* I'll be enhancing Immich in the future to support this.
* If you remove a photo from a Lightroom collection, I haven't been able to figure out how to get notified.
* Workaround
* Mark a file for republishing. This will cause the plugin to run, and it will check that all the local files are synced to the remote.

### Advanced
Turn on Logging if you want to see more information about what is being send to the server. Verbose logging is very versbose, and normal logging is probably the most you want.

### Future
I plan on adding keywords, comments, likes, and additional enhancements in the future.

PRs Welcome!
145 changes: 145 additions & 0 deletions immich.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
--
-- Copyright (c) 2024 Min Idzelis
--
-- This file is part of LR-Immich.
--
-- Foobar is free software: you can redistribute it and/or modify it under the terms
-- of the GNU General Public License as published by the Free Software Foundation,
-- either version 3 of the License, or (at your option) any later version.
--
-- LR-Immich is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY
-- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with Foobar.
-- If not, see <https://www.gnu.org/licenses/>.
--

Immich = {}

function Immich:new(server, api_key)
local immich = {}
setmetatable(immich, {
__index = Immich,
})
if "/" == server:sub(-1) then
immich.server = server:sub(1, -2)
else
immich.server = server
end
immich.api_key = api_key
immich.deviceId = 'lightroom.immich'
return immich
end

function Immich:_authHeaders(additional)
local headers = {
{ field = 'x-api-key', value = self.api_key },
{ field = 'Accept', value = 'application/json' }
}
for key, val in pairs(additional or {}) do
table.insert(headers, { field = key, value = val })
end
return headers
end

function _invoke(func)
local result = func()
if not result then
LrErrors.throwUserError("error")
end
log:trace("HTTP Response: ", result)
return json.decode(result)
end

function Immich:_get(url)
log:trace('GET ' .. url)
return _invoke(function() return LrHttp.get(url, self:_authHeaders()) end)
end

function Immich:_send(url, body, method)
local headers = self:_authHeaders({ ['Content-Type'] = 'application/json' })
local encoded = json.encode(body)
log:trace((method or 'POST') .. ' ' .. url, encoded)
return _invoke(function() return LrHttp.post(url, encoded, headers, method) end)
end

function Immich:_postMultipart(url, formdata)
log:trace('POST (multipart) ' .. url)
return _invoke(function() return LrHttp.postMultipart(url, formdata, self:_authHeaders()) end)
end

function Immich:_url(path)
return self.server .. path
end

function Immich:asset_exists(identifiers)
local url = self:_url('/api/asset/exist')
local body = {
deviceAssetIds = identifiers,
deviceId = self.deviceId,
}
local response = self:_send(url, body)
local set = Set:new(response.existingIds)
return set.items
end

function Immich:asset_upload(photo, rendered)
local url = self:_url('/api/asset/upload')
local name = LrPathUtils.leafName(rendered)
local date = photo:getRawMetadata("dateTimeOriginalISO8601")

local formdata = {
{ name = 'assetData', filePath = rendered, fileName = name },
{ name = 'deviceAssetId', value = photo.localIdentifier },
{ name = 'deviceId', value = self.deviceId },
{ name = 'fileCreatedAt', value = date },
{ name = 'fileModifiedAt', value = date },
}

return self:_postMultipart(url, formdata)
end

function Immich:album_info(id)
local url = self:_url('/api/album/' .. id)
return self:_get(url)
end

function Immich:create_album(albumName, description, assetIds)
local url = self:_url('/api/album')
local body = {
albumName = albumName,
description = description,
assetIds = assetIds,
}
return self:_send(url, body)
end

function Immich:add_album_assets(albumId, assetIds)
log:trace('add_album_assets', albumId, assetIds)
local url = self:_url('/api/album/' .. albumId .. '/assets')
local body = {
ids = assetIds,
}
return self:_send(url, body, 'PUT')
end

function Immich:remove_album_assets(albumId, assetIds)
log:trace('remove_album_assets', albumId, assetIds)
local url = self:_url('/api/album/' .. albumId .. '/assets')
local body = {
ids = assetIds,
}
return self:_send(url, body, 'DELETE')
end

function Immich:search_metadata(body)
local url = self:_url('/api/search/metadata')
return self:_send(url, body)
end

function Immich:albumWebUrl(albumId)
return self:_url('/albums/' .. albumId)
end

return Immich
41 changes: 41 additions & 0 deletions init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--
-- Copyright (c) 2024 Min Idzelis
--
-- This file is part of LR-Immich.
--
-- Foobar is free software: you can redistribute it and/or modify it under the terms
-- of the GNU General Public License as published by the Free Software Foundation,
-- either version 3 of the License, or (at your option) any later version.
--
-- LR-Immich is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-- See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with Foobar.
-- If not, see <https://www.gnu.org/licenses/>.
--

local Log = require "log"

_G.log = Log:new()

_G.LrHttp = import 'LrHttp'
_G.LrDate = import 'LrDate'
_G.LrPathUtils = import 'LrPathUtils'
_G.LrErrors = import "LrErrors"
_G.LrTasks = import "LrTasks"

_G.json = require "json"
_G.serialize = require "serialize"
_G.Set = require "set"
_G.utils = require "utils"

_G.LrView = import 'LrView'
_G.bind = LrView.bind
_G.LrBinding = import 'LrBinding'
_G.LrDialogs = import 'LrDialogs'

_G.LrPrefs = import 'LrPrefs'
_G.prefs = _G.LrPrefs.prefsForPlugin()

_G.LrErrors = import "LrErrors"
Loading

0 comments on commit 1946ff8

Please sign in to comment.