-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAddaxKeywords.lua
More file actions
142 lines (124 loc) · 5.88 KB
/
AddaxKeywords.lua
File metadata and controls
142 lines (124 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
local LrPathUtils = import 'LrPathUtils'
local LrFileUtils = import 'LrFileUtils'
local AddaxKeywords = {}
--- Helper to log messages (reused from main process logic)
local function log(msg)
local desktop = LrPathUtils.getStandardFilePath('desktop')
local logPath = LrPathUtils.child(desktop, "Addax_DebugLog.txt")
local f = io.open(logPath, "a")
if f then
f:write(os.date("%H:%M:%S") .. " - [Keywords] " .. tostring(msg) .. "\n")
f:close()
end
end
--- Strips leading and trailing whitespace.
local function trim(s)
if not s then return "" end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
--- Atomic Get-or-Create for keywords using the latest Lightroom SDK pattern.
-- @param catalog The Lightroom catalog object.
-- @param name The name of the keyword.
-- @param parent The parent keyword object (optional).
-- @return The keyword object or nil if it could not be created.
function AddaxKeywords.getOrCreate(catalog, name, parent)
name = trim(name)
if not name or name == "" then return nil end
-- Using returnExisting=true is the most robust way to handle this in a single call.
-- We avoid pcall here because catalog operations may yield, which is not allowed in pcall.
local keyword = catalog:createKeyword(name, {}, true, parent, true)
if keyword then
return keyword
else
log("ERROR: Could not create/find keyword: " .. name)
return nil
end
end
--- Synchronizes classification results with the catalog.
-- @param catalog The active catalog.
-- @param kwFile Absolute path to the keywords.txt generated by Python.
-- @param pathMap Table mapping exported JPEG names to original photo paths.
-- @param excludes Table of excluded class names (lowercase keys).
-- @param threshold The minimum confidence threshold (0.0 to 1.0).
-- @return The number of photos successfully updated.
function AddaxKeywords.synchronize(catalog, kwFile, pathMap, excludes, threshold)
local processedCount = 0
local content = LrFileUtils.readFile(kwFile)
if not content then
log("ABORT: Keywords data file missing.")
return 0
end
-- 1. Parse the keyword exchange data
-- Format: FILENAME|FAMILY|SPECIES|COMMON|CONF;...
local imageResults = {}
for line in content:gmatch("[^\r\n]+") do
local filename, kwString = line:match("([^|]+)|(.+)")
if filename and kwString then
local speciesList = {}
for kwBlock in kwString:gmatch("([^;]+)") do
local f, s, c, confStr = kwBlock:match("([^|]*)|([^|]*)|([^|]*)|([^|]*)")
local conf = tonumber(confStr) or 1.0
if f and s and c and conf >= threshold then
table.insert(speciesList, { family = trim(f), species = trim(s), common = trim(c), confidence = conf })
end
end
if #speciesList > 0 then
imageResults[trim(filename)] = speciesList
end
end
end
-- 2. Establish Root Hierarchy
local kwRoot = AddaxKeywords.getOrCreate(catalog, "Addax-AI", nil)
if not kwRoot then return 0 end
local kwTax = AddaxKeywords.getOrCreate(catalog, "Taxonomy", kwRoot)
local kwCom = AddaxKeywords.getOrCreate(catalog, "Common Names", kwRoot)
-- 3. Apply results to photos
for exportedName, originalPath in pairs(pathMap) do
local photo = catalog:findPhotoByPath(originalPath)
if photo then
local res = imageResults[trim(exportedName)]
if res then
processedCount = processedCount + 1
log("Applying metadata for: " .. exportedName)
for _, item in ipairs(res) do
local fLower = item.family:lower()
local sLower = item.species:lower()
local cLower = item.common:lower()
-- Filter based on user-defined Excluded Classes
if not excludes[fLower] and not excludes[sLower] and not excludes[cLower] then
-- Branch A: Scientific Taxonomy (Prevent redundant Aves > Aves)
local kwParent = kwTax
if item.family ~= "" then
kwParent = AddaxKeywords.getOrCreate(catalog, item.family, kwParent)
end
if item.species ~= "" and sLower ~= fLower then
local kwSpecies = AddaxKeywords.getOrCreate(catalog, item.species, kwParent)
if kwSpecies then
photo:addKeyword(kwSpecies)
log(" Linked Taxonomy: " .. item.family .. " > " .. item.species)
end
else
-- If species is same as family, just tag the family
if kwParent ~= kwTax then
photo:addKeyword(kwParent)
log(" Linked Taxonomy (Level 1): " .. item.family)
end
end
-- Branch B: Common Names (Only if different from scientific names)
if item.common ~= "" and cLower ~= sLower and cLower ~= fLower then
local kwC = AddaxKeywords.getOrCreate(catalog, item.common, kwCom)
if kwC then
photo:addKeyword(kwC)
log(" Linked Common Name: " .. item.common)
end
end
else
log(" Skipping excluded species: " .. item.species)
end
end
end
end
end
return processedCount
end
return AddaxKeywords