Script to Find variants or Images not in a User Collection
Users with large Catalogs may have a problem with Images or Variants not in any user collection. Once an Image or Variant is no longer in a Project or Album, it essentially disappears from view and is "lost".
I have published several scripts to address this issue, but only for images. With recent improvements in Capture One this problem is relevant for Variants as well as Images.
This version is also simpler, smaller and faster than the previous versions. On my Mac Studio this script will analyse a 16000 image catalog in about 15 seconds.
At the end of the script, all of the "missing" variants are selected, and the user can then drag them to an Album or Project created for this purpose, if so desired. Its probably a good idea to choose such an Album in a project named "ScriptSearchResults", so that the results are ingnored by further searches.
At the end of the script, an alert is generated reporting the results, the same is on a the clip board. If this report is very lengthy, there is only a summary on the Alert, but the full version is on the clipboard. It remains for the user to paste the results into his or her preferred document editor.
There are a small number of user settings at the top of the script that allow the user to spedify if Images or variants shall be searched, if User Smart Albums shall be excluded, and if other user collections shall be excluded.
Further details at the top of the script.
Feel free to use this script for any purposes, although it would be kind to acknowledge my contribution.
Any feedback, bug reports and suggestions for improvement are welcome
Minor edit to the script at 2023/01/20 7:08 to comment out a redundant Tell command at about line 172
(**** Description
A Script to search a Capture One Catalog for Variants not in any user collection
Version 16.12, January 2024 !! Best effort support !!
Copyright 2024 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
This script is suitable to use as an application in Capture One Pro's Script Menu
This script does not write or delete any information in the COP Catalog or Session or the image file
*** Setup
Start Script Editor, open a new (blank) file, copy and paste the this Applescript code into a new blank Script Editor Document, compile (click the hammer symbol) and save.
Best if you save to a "Scripts" folder somewhere in your Documents Folder
This script is suitable to use as an application in Capture One Pro's Script Menu
*** Operation in Script Editor
Open the compiled and saved script
Open the Script Editor log window, and select the messages tab
In Capture One Library Tool select the All Images Collection or any Catalog Folder
If a User Collection is is the current (selected) collection, the script will change the current Collection to "All Images"
Upon running the script, it will deselect all variants, and then as each missing variant is found it will be selected
Seaching is done by searching for Variant's IDs, as ID's are faster to retrieve, and are unique
The Name and Position of the missing Variants are retrieved at the end of the analaysis
After completing the analysis, an Alert will show the results
The missing Variants are sorted in Name Order
After the user closed the Alert, the results are copied to the clipboard
The user may then choose to paste the results to some document, e.g. a Text Edit documenent
If the results are very lengthy, only a summary of the results is shown in the Alert, but the full results will be on the clipboard.
*** User Settings
The following default settings can be changed in the "User Settings" part of the script
These user collections should be excluded to avoid every Variant from always being "not missing"
any collection named ScriptSearchResults
any collection whose name contains "SearchResults"
any album whose name begins with "Images missing in"
There is a setting to exclude Smart Albums from the search, as Smart Albums can include images that are not in any Album or Project
The normal approach is to not search a Project's collections, as all the variants and images in those collections are in those collections are also in the Project
If collectProjectIds to false, Variant IDs will only be collected from a Projects collections and not from the Project. This takes longer but goves the same result.
Various debug settings are available to check operation if needed.
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
true -- change this line to trigger a compile of this script. Do this after a Library Script has been updated, but no other changes are required to this script
global searchTypeIsVariant, excludedCollectionsNameL, excludeUserSmartAlbums, excludedCollectionsNameKindL, collectProjectIds
global debugIDLogEnable, debugLogCollEnable, debugLogNKparsing, debugLogExcludedColl, debugLogFound, debugLogMissing, debugMissingAlert
#### User Settings
set searchTypeIsVariant to true -- if False, the serach is for Images
set excludeUserSmartAlbums to true -- if TRUE the script will ignore user smart albums, which may contain variants not in any other user collection
## Configure these below to exclude user collections created by other scripts and Capture One functions
set excludedCollectionsNameL to {"ScriptSearchResults"} -- Names of user collections to be excluded from the search
set excludedCollectionsNameKindL to {"Images missing in*/Album", "*SearchResults*"} -- Name and Kind of collections to be excluded from the search
-- "*" at the beginning or end denotes a wild card match
-- an (optional) "/" is followed by the kind of user collection {Project,Album,Group,Smart Album}
-- eg "Images missing in*/Album" excludes all Albums with names beginning with "Images missing in"
-- eg "*SearchResults*" excludes all collections with names containing "SearchResults"
#### Debug Settings and settings not typically changed by users
set collectProjectIds to false -- if TRUE variant/Image IDs ARE collected from Projects, but NOT from a project's collections
-- if FALSE variant/Image IDs are NOT collected from Projects, but ARE collected from project's collections
-- I think TRUE and FALSE modes give the same results for variants, but FALSE mode takes significantly longer
-- This setting allows one to compare results
-- For Images, TRUE and FALSE modes definitely give the same result
set debugIDLogEnable to false -- log the variant/Image ID
set debugLogCollEnable to false -- log the user collections which are searched
set debugLogNKparsing to false -- log the results of parsing the excludedCollectionsNameKindL list
set debugLogExcludedColl to false -- log the user collections which are excluded from searching
set debugLogFound to false -- log the variant/Image IDs which are found in a user collection
set debugLogMissing to false -- log the variant/Image IDs which are not found in a user collection
set debugMissingAlert to false -- generate an alert showing the variant/Image IDs which are not found in a user collection
#### End of User Settings
if not doSetup() then return
set userIDList to getUserCollectionIDs()
set sourceIDlist to getSourceCollectionIDs()
set {notfoundIDList, sourceIDCount} to findMissingID(sourceIDlist, userIDList)
set {userIDList, sourceIDlist} to {{}, {}}
reportMissingVariants(notfoundIDList, sourceIDCount)
###################################################################
on doSetup()
## Eric Valk 2024
## Script setup
global searchTypeIsVariant
global coDocName, coDdockindR, sourceCollectionName, sourceCollectionKind_R, sourceCollectionRef, COkind
global skippedCollectionID_L, excludedCollectionID_L
global itemLabel_LC, itemLabelCap, itemLabelCapSingular, theExcludeNKDelim
global coProcessName, coAppName, wasFrontMostProcess
local coFileName
tell application "Capture One 16.3.4" to set coFileName to its name & ".app"
tell application "System Events"
set wasFrontMostProcess to name of first application process whose frontmost is true
set coProcessName to get name of first application process whose (its background only is false) and (its file's name is coFileName)
set frontmost of process coProcessName to true
end tell
makeCaptureOneEnums()
set theExcludeNKDelim to "/"
if searchTypeIsVariant then
set itemLabel_LC to "variants"
else
set itemLabel_LC to "images"
end if
set itemLabelCap to (change_case(itemLabel_LC, "proper"))
set itemLabelCapSingular to itemLabelCap's text 1 thru -2
local sourceCollectionIsUser, sourceCollectionID, sourceCollectionIDCount
tell application "Capture One 16.3.4"
set {coDocName, coDdockindR} to get {name, kind} of current document
tell document coDocName to ¬
if (get user of current collection) then set current collection to first collection
tell document coDocName to tell current collection
set {sourceCollectionRef, sourceCollectionIsUser, sourceCollectionKind_R, sourceCollectionName, sourceCollectionID} to ¬
get {it, its user, its kind, its name, its id}
if searchTypeIsVariant then
set sourceCollectionIDCount to (count of its variants)
else
set sourceCollectionIDCount to (count of its images)
end if
end tell
end tell
if sourceCollectionIDCount = 0 then
display alert ("Selected Collection \"" & sourceCollectionName & "\" contains no " & itemLabelCap & return & "Script Halted")
return false
end if
parseNameKindList()
set excludedCollectionID_L to {sourceCollectionID}
set skippedCollectionID_L to {}
return true
end doSetup
on getSourceCollectionIDs()
## Eric Valk 2024
## Get the IDs in the source collection - the variant's/images to be checked, and sort them
global coDocName, sourceCollectionRef, sourceCollectionName
global searchTypeIsVariant, itemLabel_LC, itemLabelCap, itemLabelCapSingular
local sourceIDlist
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to {"Collecting " & itemLabelCap & " in " & sourceCollectionName, 0, 2, "Getting " & itemLabelCapSingular & " List"}
--tell application "Capture One 16.3.4" to tell document coDocName to tell sourceCollectionRef to set sourceIDlist to its id of every variant
if searchTypeIsVariant then
tell application "Capture One 16.3.4" to tell document coDocName to tell sourceCollectionRef to set sourceIDlist to its id of every variant
else
tell application "Capture One 16.3.4" to tell document coDocName to tell sourceCollectionRef to set sourceIDlist to its id of every image
end if
tell application "Capture One 16.3.4" to set {progress completed units, progress additional text} to {1, (get "Sorting " & (count of sourceIDlist) & " " & itemLabel_LC)}
return SortList_F(sourceIDlist)
end getSourceCollectionIDs
on getUserCollectionIDs()
## Eric Valk 2024
## Get the IDs of the all user collections which are not excluded, and sort them
global coDocName, sourceCollectionRef, userCollectionCount, excludedUserCollectionsL
global itemLabel_LC, itemLabelCap, itemLabelCapSingular
global debugLogCollEnable
local TopCollsRefL, TopCollsNameL, TopCollsKindRL, TopCollsID_L, countTopColls, subColl_IDList, ptrTopColl
tell application "Capture One 16.3.4" to tell document coDocName to tell (every collection whose user of it is true) to ¬
set {TopCollsRefL, TopCollsNameL, TopCollsKindRL, TopCollsID_L} to {(get it), (get name of it), (get kind of it), (get id of it)}
set countTopColls to (count of TopCollsNameL)
set subColl_IDList to {}
set userCollectionCount to 0
set excludedUserCollectionsL to {}
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to {"Collecting " & itemLabelCap & " in User Collections", 0, 17, ""}
repeat with ptrTopColl from 1 to countTopColls
set subColl_IDList to subColl_IDList & collectUserCollectionIDs((get TopCollsRefL's item ptrTopColl), (get TopCollsKindRL's item ptrTopColl), (get TopCollsNameL's item ptrTopColl), (get TopCollsID_L's item ptrTopColl), true)
tell application "Capture One 16.3.4"
set progress completed units to (get contents of ptrTopColl) as integer
set progress additional text to ("Found " & (get count of subColl_IDList) & itemLabel_LC & " in " & userCollectionCount & " user collections")
end tell
end repeat
if debugLogCollEnable then log "Searched " & userCollectionCount & " user collections"
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to {"Sorting User " & itemLabelCap, 0, 1, (get "" & (count of subColl_IDList) & " " & itemLabel_LC)}
return SortList_F(subColl_IDList)
end getUserCollectionIDs
on parseNameKindList()
## Eric Valk 2024
## Parse the excludedCollectionsNameKindL List for faster searching
global excludedCollectionsNameKindL, excludedNameKindColl_R, theExcludeNKDelim, debugLogNKparsing, exParseResult
local exNamePart_L, exMatchType_L, exKindPart_L, exNoKind_L, countExNameKind
local NameAndKind, NamePart, nameMatchType, KindPart, hasNoKind, delimPtr, logString
set {exNamePart_L, exMatchType_L, exKindPart_L, exNoKind_L, exParseResult} to {{}, {}, {}, {}, {}}
repeat with NameAndKind in excludedCollectionsNameKindL
set {NamePart, KindPart, hasNoKind, nameMatchType} to {missing value, missing value, missing value, missing value}
if debugLogNKparsing then
set logString to "Name and Kind term: \"" & NamePart & "\""
log logString
end if
if NameAndKind does not contain theExcludeNKDelim then
set hasNoKind to true
set NamePart to (contents of NameAndKind)
else
repeat with delimPtr from 2 to ((length of NameAndKind) - 1)
if theExcludeNKDelim = (NameAndKind's text delimPtr) then
set NamePart to NameAndKind's text 1 thru (delimPtr - 1)
set KindPart to NameAndKind's text (delimPtr + 1) thru -1
set hasNoKind to false
exit repeat
end if
end repeat
if {"Project", "Album", "Group", "Smart Album"} does not contain KindPart then
set hasNoKind to true
set KindPart to "\"" & KindPart & "\" rejected"
end if
end if
if debugLogNKparsing then
set logString to "Collection Kind Match: \"" & KindPart & "\" Match Kind: " & (not hasNoKind)
log logString
end if
if (NamePart begins with "*") and (NamePart ends with "*") then
set {NamePart1, nameMatchType} to {(NamePart's text 2 thru -2), 3}
else if NamePart begins with "*" then
set {NamePart1, nameMatchType} to {(NamePart's text 2 thru -1), 2}
else if NamePart ends with "*" then
set {NamePart1, nameMatchType} to {(NamePart's text 1 thru -2), 1}
else
set {NamePart1, nameMatchType} to {NamePart, 0}
end if
if (NamePart1 contains "*") or (NamePart1 contains theExcludeNKDelim) then error "Unsupported collection match term: \"" & NamePart & "\""
set nameString to NamePart
if (not hasNoKind) then set nameString to nameString & "\" + \"" & KindPart
copy nameString to end of exParseResult
copy NamePart1 to end of exNamePart_L
copy KindPart to end of exKindPart_L
copy hasNoKind to end of exNoKind_L
copy nameMatchType to end of exMatchType_L
if debugLogNKparsing then
set logString to "Collection Name Match: \"" & NamePart & "\" MatchType: \"" & (item (nameMatchType + 1) of {"is", "begins with", "ends with", "contains"}) & "\""
log logString
end if
end repeat
set excludedNameKindColl_R to {exNamePart_L:exNamePart_L, exMatchType_L:exMatchType_L, exKindPart_L:exKindPart_L, exNoKind_L:exNoKind_L, countExNameKind:(count of exNamePart_L)}
return
end parseNameKindList
on collectUserCollectionIDs(thisCollectionRef, thisCollectionKind_R, thisCollectionName, thisCollectionID, thisCollectionIsTop)
## Eric Valk 2024
## Get the IDs of one user collection which is not excluded, and its collections
global coDocName, userCollectionCount, COkind
global debugIDLogEnable, debugLogFound, debugLogMissing, debugMissingAlert, debugLogCollEnable, debugLogExcludedColl
global excludedCollectionsNameL, excludedNameKindColl_R, excludedCollectionID_L, skippedCollectionID_L, collectProjectIds, excludeUserSmartAlbums, excludedUserCollectionsL
global searchTypeIsVariant, itemLabel_LC, itemLabelCap, itemLabelCapSingular
local exNamePart_L, exMatchType_L, exKindPart_L, exNoKind_L, countExNameKind
local isTopS, skipOnDetectedName, NKctr, NamePart, nameMatchType, thisCollectionKind_S
set isTopS to " "
if thisCollectionIsTop then set isTopS to " top "
if (excludeUserSmartAlbums and (thisCollectionKind_R = COkind's Coll_Smart_Album)) then -- prevent results flooding and for efficiency
if debugLogCollEnable or debugLogExcludedColl then log "Exclude" & isTopS & "Smart Album " & thisCollectionName
return {}
end if
set thisCollectionKind_S to convertKindList(thisCollectionKind_R)
set {exNamePart_L, exMatchType_L, exKindPart_L, exNoKind_L, countExNameKind} to ¬
{excludedNameKindColl_R's exNamePart_L, excludedNameKindColl_R's exMatchType_L, excludedNameKindColl_R's exKindPart_L, excludedNameKindColl_R's exNoKind_L, excludedNameKindColl_R's countExNameKind}
set skipOnDetectedName to false
if countExNameKind > 0 then
repeat with NKctr from 1 to countExNameKind
set NamePart to (exNamePart_L's item NKctr)
set nameMatchType to (exMatchType_L's item NKctr)
if (exNoKind_L's item NKctr) or (thisCollectionKind_S = (exKindPart_L's item NKctr)) then
if nameMatchType = 3 then
if thisCollectionName contains NamePart then set skipOnDetectedName to true
else if nameMatchType = 2 then
if thisCollectionName ends with NamePart then set skipOnDetectedName to true
else if nameMatchType = 1 then
if thisCollectionName begins with NamePart then set skipOnDetectedName to true
else if nameMatchType = 0 then
if thisCollectionName is NamePart then set skipOnDetectedName to true
else
error "Invalid Exclude Name Match Type"
end if
if skipOnDetectedName then exit repeat
end if
end repeat
end if
if skipOnDetectedName or ¬
(excludedCollectionsNameL contains thisCollectionName) or ¬
(excludedCollectionID_L contains thisCollectionID) ¬
then
if debugLogCollEnable or debugLogExcludedColl then
log "Exclude" & isTopS & thisCollectionKind_S & " " & thisCollectionName
end if
set excludedUserCollectionsL to excludedUserCollectionsL & (thisCollectionName & "(" & thisCollectionKind_S & ")")
return {}
end if
local collectedIDList, refSubCollsL, nameSubCollsL, kindSubCollsRL, idSubCollsL, countSubColls, ptrSubColl, thisCount, skipThisProject
set userCollectionCount to userCollectionCount + 1
set skipThisProject to ((thisCollectionKind_R is COkind's Coll_Project) and ((not collectProjectIds) or (skippedCollectionID_L contains thisCollectionID)))
## Do not collect image/variant ID's from a Project if collectProjectIds is False or skippedCollectionID_L contains this collection's ID
if skipThisProject then
set collectedIDList to {}
if debugLogCollEnable or debugLogExcludedColl then
set thisCount to ("" & (get count of collectedIDList) & " " & itemLabel_LC)
log "Skipped " & isTopS & thisCollectionKind_S & " " & thisCollectionName & " (" & thisCount & ")"
end if
else
if searchTypeIsVariant then
tell application "Capture One 16.3.4" to tell document coDocName to tell thisCollectionRef to set collectedIDList to its id of every variant
else
tell application "Capture One 16.3.4" to tell document coDocName to tell thisCollectionRef to set collectedIDList to its id of every image
end if
if debugLogCollEnable then
set thisCount to ("" & (get count of collectedIDList) & " " & itemLabel_LC)
log "Collected " & itemLabelCapSingular & " ID's in " & isTopS & thisCollectionKind_S & " " & thisCollectionName & " (" & thisCount & ")"
end if
end if
if (thisCollectionKind_R is COkind's Coll_Group) or skipThisProject then
## If image/variant ID's are not collected from a Project they will be collected from the Project's collections
## Only Groups and Projects have collections
tell application "Capture One 16.3.4" to tell document coDocName to tell thisCollectionRef to tell every collection of it
set {refSubCollsL, nameSubCollsL, kindSubCollsRL, idSubCollsL} to {(get it), (get name of it), (get kind of it), (get id of it)}
end tell
set countSubColls to (count of nameSubCollsL)
if (0 < countSubColls) then
repeat with ptrSubColl from 1 to countSubColls
set collectedIDList to collectedIDList & ¬
collectUserCollectionIDs((get refSubCollsL's item ptrSubColl), ¬
(get kindSubCollsRL's item ptrSubColl), ¬
(get nameSubCollsL's item ptrSubColl), ¬
(get idSubCollsL's item ptrSubColl), ¬
false)
end repeat
end if
end if
return collectedIDList
end collectUserCollectionIDs
on findMissingID(sourceIDlist, userIDList)
## Eric Valk 2024
## Find the Variant/Image IDs which are in the source but not in (other) user collections
global debugLogFound, debugLogMissing, debugMissingAlert, itemLabel_LC, itemLabelCap, itemLabelCapSingular, coDocName, sourceCollectionRef, searchTypeIsVariant
local userIDCount, sourceIDCount, notFoundIdCount, userIDptr, sourceIDptr
local searchID, userID, selectVariants
local Mark_A, Mark_B, Mark_C, intervalCountEst, timeIntervalTarget, estCounter, selectVariants
tell application "Capture One 16.3.4" to tell document coDocName to deselect variants (sourceCollectionRef's variants whose selected is true)
set userIDptr to 1
set userID to userIDList's item 1
set notFoundIdCount to 0
set userIDCount to count of userIDList
set sourceIDCount to count of sourceIDlist
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to {("Finding " & itemLabelCap & " missing from the User Collections"), 0, sourceIDCount, ""}
set Mark_A to GetTick_Now()
set Mark_B to Mark_A
set intervalCountEst to 20
set timeIntervalTarget to 250
set estCounter to 0
repeat with sourceIDptr from 1 to count of sourceIDlist
if sourceIDptr mod intervalCountEst = 0 then
set Mark_C to GetTick_Now()
if estCounter < 4 then
set intervalCountEst to (timeIntervalTarget * sourceIDptr / (MSduration(Mark_A, Mark_C))) as integer
if intervalCountEst ≥ sourceIDCount then set intervalCountEst to sourceIDCount div 4
set estCounter to estCounter + 1
end if
if MSduration(Mark_B, Mark_C) > 1500 then
tell application "Capture One 16.3.4" to set {progress completed units, progress additional text} to {sourceIDptr, ("" & notFoundIdCount & " missing of " & sourceIDptr & " checked")}
set Mark_B to Mark_C
end if
end if
set searchID to sourceIDlist's item sourceIDptr
considering numeric strings
repeat while userID < searchID
if userIDptr ≥ userIDCount then exit repeat
set userIDptr to userIDptr + 1
set userID to userIDList's item userIDptr
end repeat
end considering
if userID = searchID then
if debugLogFound then log {"Found", "Search", sourceIDptr, searchID, "User", userIDptr, userID}
if userIDptr < userIDCount then
set userIDptr to userIDptr + 1
set userID to userIDList's item userIDptr
end if
else
set notFoundIdCount to notFoundIdCount + 1
if searchTypeIsVariant then
tell application "Capture One 16.3.4" to tell document coDocName to select variant (variant id searchID)
else
tell application "Capture One 16.3.4" to tell document coDocName to select variants (variants of image id searchID)
end if
if debugLogMissing then
if userIDptr = userIDCount then
log {"Missing", "{Ptr,Value}", sourceIDptr, searchID, "User", "{Ptr,Value}", userIDptr - 1, (userIDList's item (userIDptr - 1)), "{Ptr_next,Value_next}", userIDptr, userID, "At End of List"}
else if userIDptr = 1 then
log {"Missing", "Ptr,Value}", sourceIDptr, searchID, "User", "{Ptr_next,Value_next}", userIDptr, userID, "{Ptr_next,Value_next}", userIDptr + 1, (userIDList's item (userIDptr + 1)), "At Beginning of List"}
else
log {"Missing", "{Ptr,Value}", sourceIDptr, searchID, "User", "{Ptr,Value}", userIDptr - 1, (userIDList's item (userIDptr - 1)), "{Ptr_next,Value_next}", userIDptr, userID}
end if
end if
if debugMissingAlert then display alert "Unable to locate " & itemLabelCapSingular & " ID:" & searchID & " in user collections"
end if
end repeat
tell application "Capture One 16.3.4" to set {progress completed units, progress additional text} to {sourceIDptr, ("" & notFoundIdCount & " missing ")}
if debugMissingAlert then display alert "Found " & notFoundIdCount & itemLabelCap & " missing from the user collections"
return {notFoundIdCount, sourceIDCount}
end findMissingID
on reportMissingVariants(notFoundIdCount, sourceIDCount)
## Eric Valk 2024
## Report the Variant/Image IDs which are in the source but not in (other) user collections
global coDocName, coDdockindR, sourceCollectionRef, sourceCollectionName, sourceCollectionKind_R, debugIDLogEnable, userCollectionCount, wasFrontMostProcess
global excludedUserCollectionsL, excludeUserSmartAlbums, collectProjectIds, itemLabel_LC, itemLabelCap, searchTypeIsVariant, exParseResult
local docString, docKind_s, collKind_s
local itemCtr, theItemID, variantList, missingVariantListnotfoundNameList, theName
set docKind_s to change_case((convertKindList(coDdockindR)), "proper")
set collKind_s to change_case((convertKindList(sourceCollectionKind_R)), "proper")
set docString to "Source: " & sourceIDCount & " " & itemLabel_LC & " of " & ¬
(collKind_s & " \"" & sourceCollectionName & "\"") & ¬
return & "in " & (docKind_s & " \"" & coDocName & "\"") & ¬
return & return & ("Searched " & userCollectionCount & " user collections")
if excludeUserSmartAlbums then
set docString to docString & return & "Excluded Smart Albums"
else
set docString to docString & return & "Included user Smart Albums"
end if
if collectProjectIds then
set docString to docString & ", " & "Included Projects but excluded their collections"
else
set docString to docString & ", " & "Skipped Projects but included their collections"
end if
if 0 < (get count of exParseResult) then ¬
set docString to docString & return & "Matching Terms for Excluding Collections:" & return & "\"" & joinListToString(exParseResult, "\"; \"") & "\""
local ExcludedCollections_S, countExcludedCollections, clipboardReport_S, alertReport_S
set countExcludedCollections to (get count of excludedUserCollectionsL)
if 0 < countExcludedCollections then
set clipboardReport_S to docString & return & return & "Excluded collections: \"" & joinListToString(excludedUserCollectionsL, "\", \"") & "\""
if 31 > countExcludedCollections then
set alertReport_S to clipboardReport_S
else
set alertReport_S to alertReport_S & return & return & countExcludedCollections & "Excluded collections - full list on the Clipb0ard"
end if
else
set ExcludedCollections_S to ""
end if
if 0 = notFoundIdCount then
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to ¬
{"", 0, 0, ""}
tell application "System Events" to set frontmost of application process wasFrontMostProcess to true
display alert padParagraphs4Alert(("All " & itemLabelCap & " found in a User Collection" & return & return & alertReport_S), 16)
return
end if
local variantNameList, variantPositionList, notfoundNameList
if searchTypeIsVariant then
tell application "Capture One 16.3.4" to tell document coDocName to tell sourceCollectionRef
set {variantNameList, variantPositionList} to {name, position} of (every variant whose selected is true)
if debugIDLogEnable then set notfoundIDList to id of (every variant whose selected is true)
end tell
set notfoundNameList to {}
repeat with itemCtr from 1 to count of variantNameList
copy ((variantNameList's item itemCtr) & "[" & (variantPositionList's item itemCtr) & "]") to end of notfoundNameList
end repeat
else
tell application "Capture One 16.3.4" to tell document coDocName to tell sourceCollectionRef
set notfoundNameList to name of its parent image of (every variant whose selected is true and position is 1)
if debugIDLogEnable then set notfoundIDList to id of (every variant whose selected is true and position is 1)
end tell
end if
if debugIDLogEnable then
log ("search Type Is Variant: " & searchTypeIsVariant)
log "not found ID List"
log notfoundIDList
log "not found name List"
log notfoundNameList
log "excluded User Collections List"
log excludedUserCollectionsL
end if
local countNameList, nameString
set notfoundNameList to SortList_F(notfoundNameList)
set countNameList to count of notfoundNameList
set nameString to return & return & itemLabelCap & " not found in a user collection:" & return & joinListToString(notfoundNameList, return)
set clipboardReport_S to clipboardReport_S & nameString
if countNameList < 31 then
set alertReport_S to alertReport_S & nameString
else
set alertReport_S to alertReport_S & return & return & (countNameList & " " & itemLabelCap & " not found in a user collection - full list on the Clipboard")
end if
tell application "Capture One 16.3.4" to set {progress text, progress completed units, progress total units, progress additional text} to ¬
{"", 0, 0, ""}
tell application "System Events" to set frontmost of application process wasFrontMostProcess to true
display alert (padParagraphs4Alert(alertReport_S, 16)) -- with less than 16 lines, the Alert Message becomes quite narrow and hard to read
set the clipboard to clipboardReport_S
end reportMissingVariants
#### Utility Handlers (use U : script "Utilities_1501")
on splitStringToList(theString, theDelim)
# Public Domain
set theList to {}
copy AppleScript's text item delimiters to astid
try
copy theDelim to AppleScript's text item delimiters
set theList to text items of theString
end try
copy astid to AppleScript's text item delimiters
return theList
end splitStringToList
on joinListToString(theList, theDelim)
# Public Domain
set theString to ""
copy AppleScript's text item delimiters to astid
try
copy theDelim to AppleScript's text item delimiters
set theString to theList as string
end try
copy astid to AppleScript's text item delimiters
return theString
end joinListToString
on change_case(this_text, new_case)
# Eric Valk 2024
# this_text is a string
# new_case is "upper", "lower" or "proper"
# if new_case is "proper" then the first letter in each word is captitalized
# Problem with the word " _ " has been fixed
local charOffset, SourceMin, SourceMax, new_text, this_char, x, wordList, this_word
if (new_case is "lower") or (new_case is "proper") then
set charOffset to (id of "a") - (id of "A")
set SourceMin to (id of "A")
set SourceMax to (id of "Z")
else if (new_case is "upper") then
set charOffset to (id of "A") - (id of "a")
set SourceMin to (id of "a")
set SourceMax to (id of "z")
else
error "Unsupported Case Specification: \"" & new_case & "\""
end if
set the new_text to ""
repeat with x in id of this_text
if (x ≤ SourceMax) and (x ≥ SourceMin) then set x to x + charOffset
set the new_text to (the new_text & character id x) as string
end repeat
if (new_case is "proper") then
set wordList to splitStringToList(new_text, " ")
set new_text to ""
set charOffset to (id of "A") - (id of "a")
set SourceMin to (id of "a")
set SourceMax to (id of "z")
repeat with this_word in wordList
if 0 = length of this_word then
set the new_text to the new_text & " "
else
set x to id of this_word's character 1
if (x ≤ SourceMax) and (x ≥ SourceMin) then set x to x + charOffset
set the new_text to (the new_text & character id x & text 2 thru -1 of (this_word & " ")) as string
end if
end repeat
set the new_text to text 1 thru -2 of new_text
end if
return the new_text
end change_case
on padParagraphs4Alert(theString, targetParaCount)
## Eric Valk 2024
## Prepare theString for "display alert" padding it with extra paragrapghs if necessary
local padcount, paddingString
set theString to theString as string -- make sure this is a string, not a list
set padcount to targetParaCount - (get count of paragraphs of theString)
set paddingString to ""
repeat padcount times
set paddingString to paddingString & return
end repeat
set theString to theString & paddingString
return theString
end padParagraphs4Alert
on GetTick_Now()
# From MacScripter Author "Jean.O.matiC"
# returns duration in seconds since since 00:00 January 2nd, 2000 GMT, calculated using computer ticks
script GetTick
property parent : a reference to current application
use framework "Foundation" --> for more precise timing calculations
on Now()
return (current application's NSDate's timeIntervalSinceReferenceDate) as real
end Now
end script
return GetTick's Now()
end GetTick_Now
on MSduration(firstTicks, lastTicks)
# Eric Valk 2024
# Faster than using "round to nearest" but same result
# inputs are durations, in seconds, from GetTick's Now()
return (get (10000 * (lastTicks - firstTicks)) as integer) / 10.0
end MSduration
#### Sort Handlers (use S : script "Sorting 1")
on SortList_F(theList)
## From "peavine" on MacScripter.net
## The following ASObjC script performed the reference sort in 1 millisecond (with the Foundation framework in memory) and only requires two lines of code.
## I include this script primarily to set a benchmark, because my main interest is sort routines that employ basic AppleScript.
## Adapted using "Jean.O.matiC"'s method of using a script inside a handler for isolation purposes
local thislist
script GetSorted
property parent : a reference to current application
property thislist : theList
use framework "Foundation"
local theArray
on sortList(theList)
set theArray to current application's NSArray's arrayWithArray:thislist
return (theArray's sortedArrayUsingSelector:"localizedStandardCompare:") as list
end sortList
end script
return GetSorted's sortList(theList)
end SortList_F
#### Capture One Handlers (use C : script "CaptureOne_1512")
on makeCaptureOneEnums()
global COkind
set COkind to run script "{Doc_Catalog:«constant ****COct»,Doc_Session:«constant ****COsd», Coll_Project:«constant ****CCpj»,Coll_Group:«constant ****CCgp»,Coll_Album:«constant ****CCal»,Coll_Smart_Album:«constant ****CCsm»,Coll_Favorite:«constant ****CCfv»,Coll_Catalog_Folder:«constant ****CCff»,Lay_Background:«constant ****CLbg»,Lay_Adjustment:«constant ****CLnm»,Lay_Clone:«constant ****CLcl»,Lay_Heal:«constant ****CLhl»,Wm_None:«constant ****CRWn»,Wm_Textual:«constant ****CRWt»,Wm_Imagery:«constant ****CRWi»,Grid_Rectangular:«constant ****CGre»,Grid_Golden:«constant ****CGgr»,Grid_Fibonacci:«constant ****CGfs»}"
return
end makeCaptureOneEnums
on convertKindList(theKind)
## Copyright 2024 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General Purpose Handler for scripts using Capture One Pro
## Capture One returns the chevron form of the "kind" property when AppleScript is run as an Application
## Unless care is taken to avoid text conversion of this property, this bug breaks script decisions based on "kind"
## This script converts text strings with the chevron form to strings with the expected text form
## The input may be a single string, a single enum, a list of strings or a list of enums
## The code is not compact but runs very fast, between 60us and 210us per item
local crashMessage, theKindIsList, theKindList, kindResult_SL, theKindItem, code_start, theKindItem_S, kind_code, kind_type
if (text = (class of theKind)) and ("«" ≠ (get text 1 of theKind)) and ("»" ≠ (get text -1 of theKindItem_S)) then return theKind
set theKindIsList to false
if (text = (class of theKind)) then
set theKindList to {theKind}
else if list = (class of theKind) then
copy theKind to theKindList
set theKindIsList to true
else
set theKindList to {theKind}
end if
set code_start to -5
set kindResult_SL to {}
repeat with theKindItem in theKindList
if (text = (class of theKindItem)) then
set theKindItem_S to "" & contents of theKindItem
else
using terms from application "Capture One 16.3.4"
set theKindItem_S to (get theKindItem as text)
end using terms from
end if
if ("«" ≠ (get text 1 of theKindItem_S)) and ("»" ≠ (get text -1 of theKindItem_S)) then
copy theKindItem_S to the end of kindResult_SL
else if (theKindItem_S does not start with "«constant") then
set crashMessage to ("convertKindList received an unexpected Kind string: " & theKindItem_S)
error crashMessage
else
set kind_code to get (text code_start thru (code_start + 3) of theKindItem_S)
set kind_type to get (text code_start thru (code_start + 1) of theKindItem_S)
set kindResult to missing value
if kind_type = "CC" then ## Collection Kinds
if kind_code = "CCpj" then
set kindResult to "project"
else if kind_code = "CCgp" then
set kindResult to "group"
else if kind_code = "CCal" then
set kindResult to "album"
else if kind_code = "CCsm" then
set kindResult to "smart album"
else if kind_code = "CCfv" then
set kindResult to "favorite"
else if kind_code = "CCff" then
set kindResult to "catalog folder"
end if
else if kind_type = "CL" then ## Layer Kinds
if kind_code = "CLbg" then
set kindResult to "background"
else if kind_code = "CLnm" then
set kindResult to "adjustment"
else if kind_code = "CLcl" then
set kindResult to "clone"
else if kind_code = "CLhl" then
set kindResult to "heal"
end if
else if kind_type = "CR" then ## Watermark Kinds
if kind_code = "CRWn" then
set kindResult to "none"
else if kind_code = "CRWt" then
set kindResult to "textual"
else if kind_code = "CRWi" then
set kindResult to "imagery"
end if
else if kind_type = "CO" then ## Document Kinds
if kind_code = "COct" then
set kindResult to "catalog"
else if kind_code = "COsd" then
set kindResult to "session"
end if
end if
if (kindResult = missing value) then
set crashMessage to ("convertKindList received an unexpected Kind string: " & theKindItem_S)
error crashMessage
end if
copy kindResult to the end of kindResult_SL
end if
end repeat
if theKindIsList then return kindResult_SL
return kindResult_SL's item 1
end convertKindList
-
Thank you for this script, Erik! I'm writing this to thank you and hopefully help others find this script by commenting about it here.
While working through the last of my latest trip albums yesterday, I went to empty the catalog trash and was very confused to find it empty. With a bit of investigation I discovered that for some time now I have not been actually deleting images from my albums with my long-accustomed cmd-delete shortcut but just removing them, leaving them orphaned among the 19,000 other images in this particular library alone. Cmd-delete is just "Remove Variant"? Wait, what? How long has this been true, how long have I been not deleting images? But I remember doing empty trash occasionally... was I confusing this memory with Final Cut Pro or Apple Photos or Aperture long ago?
Anyway, I then proceeded to try to find a way to filter the images in the library to select all those not in any album and was frustrated to find that there is no such filter criteria available in Capture One. (Really?!) I started searching the web for a solution and came across some discussion threads of folks asking for similar behavior and being chastised for it as being a ridiculously unnecessary feature. (sigh). Anyway, I eventually found your older script that sounded like it might help. Yay! I did some browsing through this subforum to see if there were any other suggested solutions and came across this apparently improved version of your script.
I ran the script configured to find images and it found 1089 orphans and after confirming that they were indeed originally meant to be deleted, went to move them to the trash. I then got this unexpected warning that there were some images with variants still in albums and that those would be removed too. No! So I ran the script again but configured for variants but it also found 1089 orphans. I then proceeded to delete photos in batches to find these troublesome variants. In the end it turned out to be just one variant. I'm confused as to why trying to delete that should want to delete the other variant still in an album. However, I found what album it came from, moved it back and then moved it to the trash from there successfully. (Weird.)
Regarding my confusion over the keyboard shortcut, I finally figured out that I lost my long-taken-for-granted custom cmd-delete shortcut I had put on "Move to Catalog Trash" (to match the other photo/video collection editing apps I've used) when I got a new computer late last year. D'oh! (And for anyone looking to edit shortcuts, it's "Edit Keyboard Shortcuts" under the Edit menu – not in the Settings panels.)
Anyway... thanks again, Erik!
0
Please sign in to leave a comment.
Comments
1 comment