Script to find Images not in any User Collection (CO12-CO21)
This is an AppleScript to identify images (items) not in any user collection. The speed has been substntially improved, and has been modfiedso it runs on Capture One 12 and all later versions; I have tested it for versions 20 and 21 on OS10.14.6 with a catalog of 17000 images. It won't complain about any newer version, and it won't run on Capture One 11 and earlier.
Please do add a comment if this script works for you, and especially if something is wrong. If there is a problem I don't advise spending much time to debug it, let me know first.
The GUI to adjust settings while the script is running is not included. I could be added but I don't think it adds value to this script.
This script is most useful if you have 1000's of images or 10's of 1000';s of images in a catalog, then items not in any user collection may never be found. Other operations such as splitting a catalog can also end up with a lot of files not in any user catalog.
This AppleScript can report results by Dialog, by Notifications, through Text Edit, onthe ClipBoard, and by adding the images to the Capture One project ScriptSerachResults (this project is ignored during the serach phase).
If there are a large number of items, adding the items to a user collection makes it easy to do something with them.
This Applescript can be compiled as an Application and added to the Capture One Scripts menu.
## Applescript to search a Capture One Catalog for variants not in a User Collection
## Version 1.14.08 NOTICE Best effort Support, no warranty
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
##
-- ***To Setup
-- Start Script Editor, open a new (blank) file, copy and paste both parts into one Script Editor Document, compile (hammer symbol) and save.
-- Best if you make "Scripts" folder somewhere in your Documents or Desktop
-- This file is suitable to use as an application in Capture One Pro's Script Menu
-- *** Operation in Script Editor
-- Open the compiled and saved file
-- Open the Script Editor log window, and select the messages tab
-- Select only a small number of variants, from any collection
-- Run the script
-- Results can appear in Notifications, the Script Editor Log and the Text Edit document when the search for each variant is completed, and in the Clipboard and Display Dialog Window when all searching is completed
-- Images that are found can be added an added to an album in the ScriptSearchResults project.
-- The user may elect to set defaults for enabling or disabling results in Notifications, TextEdit and Script Editor by setting the "enable" variables at beginning of the script
-- The user may change the default amount of reporting by setting the "debugLogLevel" and "ResultsFileMaxDebug" variables at beginning of the script
-- If you are having some issues, then set debugLogLevel to 3 and send me the results from Script Editors log window, or Text Edit.
use AppleScript version "2.5"
use scripting additions
global debugLogEnable, Loqqing, enableNotifications
initLoqqingGlobals2(0, "")
######## User Settings
## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
tell Loqqing
set its debugLogLevel to 0 -- 1...6 Set to 1 to allow changing other settings. Increasing Values result in increasing amounts of debug data that takes longer to report
set its enableResultsFile to true -- Enables Results in Textedit file (true/false)
set its ResultsFileMaxDebug to 3 -- 1...6
set its enableResultsByDialog to true -- Enables Results in Dialog (true/false)
set {its maxDialogPercent, its maxDialogLines, its maxDialogChar, its dialogTimeout} to {50, 25, 1000, 20}
set its enableResultsByClipboard to true -- Enables Results by Clipboard (true/false)
set its clipboardMaxDebug to 6 -- 1...6
set its enableCompleteAlert to false -- (true/false)
end tell
set enableNotifications to true -- (true/false)
set maxSearchlevel to 100 -- Reduce if you only want to search top level collections or if you start getting stack overflow messages Range 1....... Verified to 100
set ExcludedCollectionNames to {} -- Collections in this list will not be searched, nor will their collections
set enableResultsCollection to true -- (true/false)
set Result_AlbumRoot to "UncollectedImages" -- First part of the name of the Album containing the images found by this script (last part is the date&time)
## ****** Highly recomended
set Result_ProjectName to "ScriptSearchResults" -- Project that contains the Results album
set ExcludeScriptResultsCollection to true -- exclude the Results Project from the search
## ***** Not safe to change stuff below this line, unless you have some background in SW development.
## I generally won't help much if you change stuff below this line. I may explain the design intent.
if ExcludeScriptResultsCollection then set ExcludedCollectionNames to ExcludedCollectionNames & {Result_ProjectName}
## Initial access to other applications to generate security access query
if Loqqing's enableResultsFile then
tell application "Finder" to activate
tell application "TextEdit" to activate
tell application "Finder" to get count of windows
tell application "TextEdit" to get count of windows
end if
tell application "Capture One 21" to activate
set Loqqing's path2ResultsFiles to setup4CaptureOneScript((get Loqqing's debugLogLevel), (get Loqqing's enableResultsFile), false)'s path2CoLogFolder
set ResultMethod to my setupLoqqing7()
loqThis2(-1, false, ("Started from: " & (Loqqing's parent_name) & return & "Action: Find images not in any user collection"))
set minCOPversion to "12"
set maxCOPversion to "14"
set theDocRef to validateCOP5(minCOPversion, maxCOPversion)'s COPDocRef
validateCOPdoc5(theDocRef, {"catalog", "session"})
set notFoundNameList to searchDocument(theDocRef)
set countNotFound to (count of notFoundNameList)
set Result_ProjectName to "ScriptSearchResults"
if 0 < countNotFound then
set displayString to "Result: " & (get countNotFound as text) & " Images were not found in any user collection: " & joinListToString(notFoundNameList, ", ")
else
set displayString to "Result: All Images were found in some User Collection"
end if
loqThis2(-1, true, displayString)
return
## Script Specific Handlers ########
on searchDocument(thisDocRef)
## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## Seach each user collection, and scheck the results
global theImageListSize, theImageList, debugLogEnable, ExcludedCollectionNames, ExcludeScriptResultsCollection
local theCollRefList, countImages, theCollKindList, theCollCollRefList, thisCollCollList, theDocName, theCollNameList, maxImageID, everyImageIDList
tell application "Capture One 21" to tell thisDocRef
set theDocName to its name
tell collection "All Images" to set countImages to (get count of images)
end tell
if enableNotifications then display notification "Finding Images not in a user collection: " & countImages & " images of " & theDocName
## bulk collection of information improves speed
tell application "Capture One 21" to tell thisDocRef to tell (every collection whose user is true) to ¬
set {theCollRefList, theCollCollRefList, theCollKindList, theCollNameList} to ¬
{it, its collection, my convertKindList(its kind), its name}
## The image list is created, but nothing gets changed here
local theIncr
tell application "Capture One 21" to tell thisDocRef to tell collection "All Images" to set everyImageIDList to (get id of images)
set theIncr to (countImages / 10) as integer
copy (theIncr + countImages) to maxImageID
repeat with imgCtr from 1 to countImages
if maxImageID < ((everyImageIDList's item imgCtr) as integer) then copy (theIncr + (everyImageIDList's item imgCtr)) to maxImageID
end repeat
set {theImageList, theImageListSize} to {{}, 0} -- initialise and clear the Image list
makeImageList(maxImageID)
if enableNotifications then display notification "Checking each collection"
local thisCollRef, thisCollImageIDList, collCtr
repeat with collCtr from 1 to (count of theCollRefList)
set thisCollRef to (get contents of theCollRefList's item collCtr) -- a CO reference
tell application "Capture One 21" to tell thisDocRef to set thisCollImageIDList to get id of images of thisCollRef
indexCollection(thisCollRef, (theCollKindList's item collCtr), (theCollCollRefList's item collCtr), thisCollImageIDList, (theCollNameList's item collCtr))
end repeat
if enableNotifications then display notification "Done Checking Collections, Now the analysis .."
local everyImageIDList, notFoundIdList, notFoundIndexList, notFoundNameList, theImageID, theImageNameList, anIndex
tell application "Capture One 21" to tell thisDocRef to tell collection "All Images" to set {everyImageNameList} to {name of images}
set notFoundNameList to {}
global Result_ProjectName, Result_AlbumRoot, enableResultsCollection
local ref2ResultAlbum, Coll_Init_Text, imgCtr, thisImageID
set ref2ResultAlbum to false
if enableResultsCollection then
set Coll_Init_Text to "Images not in any user collection will be added to album "
set ref2ResultAlbum to InitializeResultsCollection2(thisDocRef, Result_ProjectName, Result_AlbumRoot, Coll_Init_Text, false)
end if
repeat with imgCtr from 1 to countImages
set thisImageID to (get (everyImageIDList's item imgCtr) as integer)
if thisImageID > theImageListSize then
copy (everyImageNameList's item imgCtr) to end of notFoundNameList
else if not (get theImageList's item thisImageID) then
copy (everyImageNameList's item imgCtr) to end of notFoundNameList
if ref2ResultAlbum is not false then
tell application "Capture One 21" to tell thisDocRef to tell collection "All Images" to tell image id (get thisImageID as text) to ¬
add inside ref2ResultAlbum variants (get variants)
end if
end if
end repeat
set {theImageList, everyImageNameList, everyImageIDList} to {null, null, null} -- clear large lists to control AppleScript's memory utilization
return notFoundNameList
end searchDocument
on indexCollection(ThisRef, theCollKind, theCollRefList, theIDStringList, thisCollName)
## Copyright 2019 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
global theImageListSize, theImageList, debugLogEnable, ExcludedCollectionNames
## index the images if the collection holds images
local anIDString
if ExcludedCollectionNames contains thisCollName then return
tell application "Capture One 21" to tell ThisRef to if (0 < (get count of images)) then
repeat with anIDString in theIDStringList
set theImageList's item (get anIDString as integer) to true
end repeat
end if
local theCollCollRefList, theCollImageIDList, theCollKindList, theCollNameList
if (0 = (get count of theCollRefList)) or ("project" = theCollKind) then return -- don't search below this collection if it is a project, or has no subcollections
## This handler only handles user collections, no need to check subcollections
tell application "Capture One 21" to tell ThisRef to set {theCollCollRefList, theCollImageIDList, theCollKindList, theCollNameList} to ¬
{every collection's collection, every collection's image's id, my convertKindList(every collection's kind), every collection's name}
local collCtr
repeat with collCtr from 1 to (get count of theCollRefList)
indexCollection((theCollRefList's item collCtr), (theCollKindList's item collCtr), (theCollCollRefList's item collCtr), (theCollImageIDList's item collCtr), (theCollNameList's item collCtr))
end repeat
return
end indexCollection
on makeImageList(newImageListSize)
global theImageListSize, theImageList, debugLogEnable
## make some extras to avoid calling this repeatedly
set newImageListSize to newImageListSize + 2
if 0 < theImageListSize then
if newImageListSize > theImageListSize then
set theImageList to theImageList & makeList3((newImageListSize - theImageListSize), false)
if debugLogEnable then log "Increased image list from " & theImageListSize & " to " & newImageListSize
end if
else
set theImageList to makeList3(newImageListSize, false)
end if
set theImageListSize to get count of theImageList
end makeImageList
on finalCleanup()
global theImageList, everyImageNameList, everyImageIDList
set {theImageList, everyImageNameList, everyImageIDList} to {null, null, null}
end finalCleanup
###########################################################################################################################
## Capture One General Handlers Version 2020
on validateCOP5(minCOPversionstr, maxCOPversionstr)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General purpose initialisation handler for scripts using Capture One Pro
## Extract and check basic information about the Capture One application
global debugLogEnable
local theAppName, copVersion, copVersionStr, copDetailedVersion
local minVersionPass, maxVersionPass
tell application "Capture One 21" to set {theAppName, copVersionStr, copDetailedVersion, COPDocRef} to {name, app version, version, current document}
set copVersion to (word -1 of copVersionStr)
set theAppName to (get ("" & theAppName) as text)
if debugLogEnable then loqThis2(1, false, ("Using " & theAppName & " version " & copDetailedVersion))
set minVersionPass to compareVersion2(copVersion, minCOPversionstr)
if not minVersionPass then return {hasErrors:true, errorText:(get loqqed_Error_Halt5(("This Script does not support version " & copDetailedVersion & " of Capture One - versions " & minCOPversionstr & " and later are supported")))}
return {hasErrors:false, theAppName:theAppName, copVersion:copVersion, COPDocRef:COPDocRef}
end validateCOP5
on validateCOPdoc5(theDocRef, validDocKindList)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General purpose initialisation handler for scripts using Capture One Pro
## Extract and check basic information about a document
global debugLogEnable
--local COPDocKind_s, COPDocKind_p, COPDocName
if "text" = (get class of theDocRef as text) and (0 = (get count of theDocRef)) then tell application "Capture One 21" to set theDocRef to get current document
try
tell application "Capture One 21" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
on error errorText number errorNumber
return {hasErrors:true, errorText:(get loqqed_Error_Halt5("The Script could not retrieve Capture One document info. Error " & errorNumber & ": \"" & errorText & "\""))}
end try
set COPDocKind_s to convertKindList(COPDocKind_p)
if validDocKindList does not contain COPDocKind_s then return {hasErrors:true, errorText:(get loqqed_Error_Halt5((COPDocName & " is a " & COPDocKind_s & " -- not supported by this script")))}
return {hasErrors:false, COPDocName:COPDocName, COPDocKind_s:COPDocKind_s}
end validateCOPdoc5
on validateCOcollection6(theDocRef)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General purpose initialisation handler for scripts using Capture One Pro
## Extract basic information regarding the current collection
global debugLogEnable
local collectionRef, collectionName, collectionKind_s, collectionId, collectionUser, collectionIsMirrored, docKind_p, docKind_s, selectedCollectionIsUser, collectionIsAllImages
tell application "Capture One 21" to tell theDocRef
set docKind_p to kind
set collectionRef to get current collection
if (missing value = collectionRef) then
set current collection to first collection
set collectionRef to get current collection
if debugLogEnable then loqThis2(0, true, "No collection was selected - selected the first collection")
end if
tell collectionRef to set {collectionName, collectionKind_s, collectionId, collectionUser} to {name, my convertKindList(kind), id, user}
if debugLogEnable then loqThis2(2, false, ("Collection: Name-" & collectionName & "; Kind-" & collectionKind_s & "; ID-" & collectionId & "; User-" & collectionUser))
if (docKind_p = session) then
tell application "Capture One 21" to tell theDocRef to set selectedCollectionIsUser to ¬
collectionUser and (missing value = (get folder of collectionRef))
if debugLogEnable then loqThis2(2, false, ("Session Collection Is User: " & selectedCollectionIsUser))
set collectionIsMirrored to false
else if (docKind_p = catalog) then
set selectedCollectionIsUser to collectionUser
set collectionIsMirrored to collectionUser and (collectionId = (get id of last collection))
if debugLogEnable then loqThis2(2, false, ("Catalog Collection Is Mirrored at Top Last: " & collectionIsMirrored))
set collectionIsAllImages to (not collectionUser) and (collectionId = (get id of first collection))
else
tell application "Capture One 21" to set docKind_s to (docKind_p as text)
return {hasErrors:true, errorText:(get loqqed_Error_Halt5("validateCOcollection6 received an unexpected Document Kind: " & docKind_s))}
end if
end tell
return {hasErrors:false, selectedCollectionRef:collectionRef, selectedCollectionKind_s:collectionKind_s, selectedCollectionName:collectionName, selectedCollectionMirroredAtTopLast:collectionIsMirrored, selectedCollectionIsUser:selectedCollectionIsUser, selectedCollectionID:collectionId, selectedCollectionIsAllImages:collectionIsAllImages}
end validateCOcollection6
on convertKindList(theKind)
## Copyright 2020 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 kind_sl, theItem, kindItem_s, code_start, kindItem_s, kind_code, kind_type
if list = (class of theKind) then
set kind_sl to {}
repeat with theItem in theKind
set the end of kind_sl to convertKindList(theItem)
end repeat
return kind_sl
else if text = (class of theKind) then
if "«" ≠ (get text 1 of theKind) then return theKind
set kindItem_s to theKind
else
tell application "Capture One 21" to set kindItem_s to (get theKind as text)
if "«" ≠ (get text 1 of kindItem_s) then return kindItem_s
end if
set code_start to -5
if ("»" ≠ (get text -1 of kindItem_s)) or (16 > (count of kindItem_s)) then ¬
error (get loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & kindItem_s))
set kind_code to get (text code_start thru (code_start + 3) of kindItem_s)
set kind_type to get (text code_start thru (code_start + 1) of kindItem_s)
if kind_type = "CC" then ## Collection Kinds
if kind_code = "CCpj" then
return "project"
else if kind_code = "CCgp" then
return "group"
else if kind_code = "CCal" then
return "album"
else if kind_code = "CCsm" then
return "smart album"
else if kind_code = "CCfv" then
return "favorite"
else if kind_code = "CCff" then
return "catalog folder"
end if
else if kind_type = "CL" then ## Layer Kinds
if kind_code = "CLbg" then
return "background"
else if kind_code = "CLnm" then
return "adjustment"
else if kind_code = "CLcl" then
return "clone"
else if kind_code = "CLhl" then
return "heal"
end if
else if kind_type = "CR" then ## Watermark Kinds
if kind_code = "CRWn" then
return "none"
else if kind_code = "CRWt" then
return "textual"
else if kind_code = "CRWi" then
return "imagery"
end if
else if kind_type = "CO" then ## Document Kinds
if kind_code = "COct" then
return "catalog"
else if kind_code = "COsd" then
return "session"
end if
end if
error (get loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & kindItem_s))
end convertKindList
on InitializeResultsCollection2(COPDocRef, nameResultProject, nameResultAlbumRoot, Coll_Init_Text, HaltOnFail)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General Purpose Handler for scripts using Capture One Pro
## Sets up a project and albums for collecting images
global debugLogEnable, C1, Loqqing
local coll_ctr, nameResultAlbum, resultProjectList, Coll_Init_Text, ref2ResultAlbum
tell application "Capture One 21" to tell COPDocRef
if 0 = (get count of (collections whose name is nameResultProject and user is true)) then
set ref2ResultProject to make new collection with properties {kind:project, name:nameResultProject}
if debugLogEnable then loqThis2(1, false, ("Created " & nameResultProject))
else
set resultProjectList to collections whose name is nameResultProject and kind is project
if 1 = (count of resultProjectList) then
set ref2ResultProject to first item of resultProjectList
if debugLogEnable then loqThis2(1, false, ("Found " & nameResultProject))
else
if HaltOnFail then error (get loqqed_Error_Halt5("A user collection named \"" & nameResultProject & "\" already exists, and it is not a project."))
loqThis2(0, true, ("A user collection named \"" & nameResultProject & "\" already exists, and it is not a project."))
return false
end if
end if
end tell
set coll_ctr to 1
set nameResultAlbum to nameResultAlbumRoot & "_" & (get short date string of (get current date)) & "_"
repeat
tell application "Capture One 21" to tell ref2ResultProject
if not (exists collection named (get nameResultAlbum & coll_ctr)) then
set nameResultAlbum to (get nameResultAlbum & coll_ctr)
set ref2ResultAlbum to make new collection with properties {kind:album, name:nameResultAlbum}
exit repeat
else
set coll_ctr to coll_ctr + 1
end if
end tell
end repeat
if 0 < length of Coll_Init_Text then set Coll_Init_Text to Coll_Init_Text & ": "
loqThis2(1, false, (Coll_Init_Text & nameResultProject & ">" & nameResultAlbum))
return ref2ResultAlbum
end InitializeResultsCollection2
on setup4CaptureOneScript(debugLogLevel, gateReportFiles, gatePrefFiles)
## Purpose - provide paths to the folders for script logs and script preferences; create if necessary
local alias2PrefsParent_as, alias2PrefsParent_a, alias2ReportsParent_a, alias2CoScriptPrefs, alias2CoScriptReports
set {alias2CoScriptReports, alias2CoScriptPrefs} to {false, false}
set alias2PrefsParent_as to (((get path to scripts folder) as text) & "Capture One Scripts:")
if 1 ≤ debugLogLevel then tell me to log "Finding Reports and Prefs parent folder \"" & (get POSIX path of alias2PrefsParent_as) & "\""
try
set alias2PrefsParent_a to alias alias2PrefsParent_as
set alias2ReportsParent_a to alias2PrefsParent_a
on error errorString number errorNumber
error "setup4CaptureOneScript() had error " & errorNumber & " \"" & errorString & "\"."
end try
if gatePrefFiles then set alias2CoScriptPrefs to findTargetFolder(alias2PrefsParent_a, "Script Preferences", debugLogLevel, true)
if gateReportFiles then set alias2CoScriptReports to findTargetFolder(alias2ReportsParent_a, "Script Reports", debugLogLevel, true)
return {path2CoLogFolder:(alias2CoScriptReports as text), path2CoPrefFolder:(alias2CoScriptPrefs as text)}
end setup4CaptureOneScript
###########################################################################################################################
## General Handlers Version 2019/11/6
on initLoqqingGlobals2(debugLogLevel, path2ResultsFiles)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## Handler to setup the Global Variables used by the loqqing system at the start of the script
## loqResultDocRef is kept separate from Loqqing to allow easy listing of Loqqing
global debugLogEnable, Loqqing
global loqResultDocRef, loqResultMethod, loqDialogTextList, loqClipboardTextS
## Setup for logging controlled by the enable settings only
## Logging by Clipboard, Notifications and Logging
set {loqResultDocRef, loqResultMethod, loqDialogTextList, loqClipboardTextS} to {false, "", "", ""}
set debugLogEnable to (get debugLogLevel > 0)
tell application "System Events" to set parent_name to name of current application
set Script_Title to getScriptTitle2(true, "_")
set Loqqing to {enableResultsFile:false, nameResultDoc:(Script_Title & ".txt"), ResultsFileMaxDebug:0} & ¬
{enableResultsByClipboard:true, clipboardMaxDebug:6} & ¬
{enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000, dialogTimeout:20} & ¬
{enableResultsByNotifications:false, enableDebugNotifications:false, notificationsMaxDebug:0} & ¬
{enableResultsByLoq:true, enableDebugByLoq:false} & ¬
{enableCompleteAlert:false, enableScriptProgressBar:false} & ¬
{debugLogLevel:(0 + debugLogLevel), path2ResultsFiles:path2ResultsFiles} & ¬
{Script_Title:Script_Title, parent_name:parent_name}
if {"Script Editor", "Script Debugger"} contains parent_name then
set Loqqing's enableDebugByLoq to true
set loqResultMethod to loqResultMethod & ", Logs"
else
set Loqqing's enableDebugNotifications to true
set loqResultMethod to loqResultMethod & ", Notifications"
end if
return
end initLoqqingGlobals2
on setupLoqqing7()
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## Handler to initialize logging of results
## Do use loq_Results() until the end of the handler and initialising is completed
## Doesn't set loqResultMethod, this is set by caller of this handler
global debugLogEnable
global Loqqing, loqDialogTextList, loqResultDocRef, loqClipboardTextS
set debugLogEnable to (0 < (get Loqqing's debugLogLevel))
local LogMethods, LogHeader, date_string, originLine, errorText, errorNumber
tell current application to set date_string to (current date) as text
set LogMethods to {}
set LogHeader to ("Script \"" & Loqqing's Script_Title & "\" results on " & date_string)
set originLine to (" by \"" & Loqqing's Script_Title & "\" on " & date_string)
if debugLogEnable then loqThis2(3, false, "setupLoqqing7: Script Title \"" & Loqqing's Script_Title & "\"")
local TextEditlist, DocName_Ext, initResultDoc
if debugLogEnable then loqThis2(4, false, "Name Result Doc \"" & (Loqqing's nameResultDoc) & "\"; Enable Results File: " & (Loqqing's enableResultsFile))
if Loqqing's enableResultsFile then
set DocName_Ext to Loqqing's nameResultDoc
set end of LogMethods to "TextEdit: " & DocName_Ext
set initResultDoc to false
## If TextEdit is already open and has the document open then add the header
tell application "System Events" to set TextEditlist to get background only of every application process whose name is "TextEdit"
if (0 < (count of TextEditlist)) and not item 1 of TextEditlist then
if (DocName_Ext is in (get name of documents of application "TextEdit")) then
tell application "TextEdit" to tell document DocName_Ext
set loqResultDocRef to it
tell its text to set paragraph (1 + (count paragraphs)) to return & "-----" & return & LogHeader & return
end tell
set initResultDoc to true
if debugLogEnable then loqThis2(3, false, "Path to \"" & DocName_Ext & "\" obtained" & originLine)
end if
local TextFirstLine, targetFolderParent_a, targetFolderParent_p, targetFolderName, targetFolder_a, targetFolder_p, ResultDocPath_a, ResultDocPath_p, newFolderRef, newFileRef
if not initResultDoc then
set targetFolder_a to alias (get Loqqing's path2ResultsFiles)
set targetFolder_p to get POSIX path of targetFolder_a
set ResultDocPath_p to targetFolder_p & "/" & DocName_Ext
if debugLogEnable then loqThis2(3, false, " Initializing \"" & DocName_Ext & "\" in \"" & targetFolder_p & "\"")
try
set ResultDocPath_a to (get alias POSIX file ResultDocPath_p)
if debugLogEnable then loqThis2(4, false, "File " & ResultDocPath_p & " Exists")
set TextFirstLine to "Initialised" & originLine
on error errorText number errorNumber -- create the document
tell application "Finder" to set newFileRef to make new file at targetFolder_a with properties {name:DocName_Ext}
set ResultDocPath_a to newFileRef as alias
set TextFirstLine to "Created" & originLine
end try
tell application "TextEdit" -- open the document and add the first line if empty
activate
set loqResultDocRef to open ResultDocPath_a
tell text of loqResultDocRef
if (0 = (count of paragraphs)) then set paragraph 1 to TextFirstLine & return
set paragraph (1 + (count paragraphs)) to return & LogHeader & return
end tell
end tell
end if
end if
end if
local screenWidthO, screenHeightO, screenWidth, screenHeight, fontSize_pts, charactersPerLine, dotsPer_Point, linesPerScreen, borderWidth
if Loqqing's enableResultsByDialog then
set end of LogMethods to "Dialogs"
set loqDialogTextList to (get LogHeader & return)
## Debugged with the largest and smallest Mac screens - 11"MBA and 27" iMac
tell application "Finder" to set {screenWidthO, screenHeightO, screenWidth, screenHeight} to bounds of window of desktop
set fontSize_pts to 12 -- estimated font size for display dialog, including line spacing
set charactersPerLine to 58 -- estimated characters per line for display dialog, if there are no "return" characters
set dotsPer_Point to 1.5 -- similar for 5K 27" iMac and 11" MBA - similar for others - retina independent
set linesPerScreen to (screenHeight - screenHeightO) / (fontSize_pts * dotsPer_Point)
set borderWidth to 5 -- lines
set Loqqing's maxDialogLines to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderWidth)) as integer)
set Loqqing's maxDialogChar to (get (charactersPerLine * (Loqqing's maxDialogLines)) as integer)
if debugLogEnable then loqThis2(4, false, ("maxDialogPercent-" & (Loqqing's maxDialogPercent) & "; screenHeight-" & (screenHeight) & "; linesPerScreen-" & (linesPerScreen) & "; maxDialogLines-" & (Loqqing's maxDialogLines) & "; maxDialogChar-" & (Loqqing's maxDialogChar)))
end if
if Loqqing's enableResultsByClipboard then set loqClipboardTextS to (LogHeader & return) -- extra line
if {"Script Editor", "Script Debugger"} contains (get Loqqing's parent_name) then
set Loqqing's enableDebugByLoq to true
set end of LogMethods to " " & (Loqqing's parent_name) & " Log"
end if
if Loqqing's enableResultsByNotifications then set end of LogMethods to "Notifications"
set LogMethods_S to joinListToString(LogMethods, ", ")
if debugLogEnable then loqThis2(3, false, ("Result Reported by " & LogMethods_S))
return LogMethods_S
end setupLoqqing7
on loqThis2(thisLogDebugLevel, MakeFront, log_Text)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General purpose handler for logging results
## log results if the debug level of the message is below the the threshold set by debugLogLevel
## log the results by whatever mechanism is enabled - {Script Editor Log, Text Editor Log, Display Dialog}
global debugLogEnable
global Loqqing, loqDialogTextList, loqResultDocRef, loqClipboardTextS
local log_Text_S, dialogTriggeredMakeFront
if thisLogDebugLevel > Loqqing's debugLogLevel then return "" -- immediate return if thisLogDebugLevel exceeds threshold
set log_Text_S to joinListToString(log_Text, "; ")
if thisLogDebugLevel ≥ 0 then set log_Text_S to "[" & thisLogDebugLevel & "]" & log_Text_S
set dialogTriggeredMakeFront to false
## Dialog
if Loqqing's enableResultsByDialog and (thisLogDebugLevel ≤ 0) then
set loqDialogTextList to (get loqDialogTextList & return & log_Text_S)
if MakeFront or (0 ≥ Loqqing's maxDialogLines) or ¬
(Loqqing's maxDialogLines < (get count of paragraphs of loqDialogTextList)) or ¬
(Loqqing's maxDialogChar < (get length of loqDialogTextList)) then
tell application "System Events" to set frontmost of process (get Loqqing's parent_name) to true
set dialogTriggeredMakeFront to true
set dialogResult to display dialog loqDialogTextList with title "Report for " & Loqqing's Script_Title & " (timeout " & (Loqqing's dialogTimeout) & ¬
"s)" buttons {"Continue", "Hold"} default button "Continue" giving up after Loqqing's dialogTimeout -- assignment prevents Apple Event timeout
if gave up of dialogResult then set dialogResult to ¬
display dialog loqDialogTextList with title "Hold this Dialog?" buttons {"Continue", "Hold"} default button "Continue" giving up after 10
if button returned of dialogResult is "Hold" then set dialogResult to ¬
display dialog loqDialogTextList with title "Report for " & Loqqing's Script_Title & " (holding) " buttons {"Continue"} default button "Continue"
set loqDialogTextS to ""
end if
end if
## Result Document
if Loqqing's enableResultsFile and (thisLogDebugLevel ≤ Loqqing's ResultsFileMaxDebug) then ¬
tell application "TextEdit" to tell text of loqResultDocRef to ¬
set paragraph (1 + (count paragraphs)) to ((log_Text_S as text) & return)
## Log
if Loqqing's enableDebugByLoq and (thisLogDebugLevel ≥ 0) then log (log_Text)
if Loqqing's enableResultsByLoq and (thisLogDebugLevel < 0) then log (log_Text)
## Clipboard
if Loqqing's enableResultsByClipboard and (thisLogDebugLevel ≤ Loqqing's clipboardMaxDebug) then ¬
set loqClipboardTextS to (loqClipboardTextS & return & log_Text_S)
## Notifications
## If a notification has too many characters then the notification system hangs
## Constrain it to 3 lines of 39 characters max.
local paraCtr, notString, lineCnt, paramax, remChar, thePara, thisCount
if (Loqqing's enableDebugNotifications and (thisLogDebugLevel ≤ Loqqing's notificationsMaxDebug)) or ¬
(Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0)) then
set {paraCtr, notString, lineCnt, paramax} to {0, "", 39, 3}
copy (get paramax * lineCnt) to remChar
repeat with thePara in (get paragraphs of (get joinListToString(log_Text, return)))
set thisCount to (get count of (get contents of thePara))
if thisCount > 0 then
set paraCtr to paraCtr + 1
if thisCount > remChar then copy remChar to thisCount
set notString to notString & (get text 1 thru thisCount of thePara)
set remChar to remChar - lineCnt * (thisCount div lineCnt)
if (0 < (thisCount mod lineCnt)) then set remChar to remChar - lineCnt
if (paramax ≤ paraCtr) or (0 ≥ remChar) then exit repeat
set notString to notString & return
end if
end repeat
display notification notString
end if
if (not dialogTriggeredMakeFront) and (MakeFront or (0 = thisLogDebugLevel)) then --
if Loqqing's enableDebugByLoq then tell application "System Events" to set frontmost of process (get Loqqing's parent_name) to true
if Loqqing's enableResultsFile then tell application "System Events" to set frontmost of process "TextEdit" to true
end if
return log_Text_S
end loqThis2
## General Utility Handlers Version 1300 2020/04/14
on splitstringtolist(theString, theDelim)
## Public Domain
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to theDelim
set theList to text items of theString
on error
set AppleScript's text item delimiters to astid
end try
set AppleScript's text item delimiters to astid
return theList
end splitstringtolist
to joinListToString(theList, theDelim)
## Public Domain
set theString to ""
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to theDelim
set theString to theList as string
on error
set AppleScript's text item delimiters to astid
end try
set AppleScript's text item delimiters to astid
return theString
end joinListToString
on makeList3(listLength, theElement)
-- Note that the theElement can even be a List
if listLength = 0 then return {}
if listLength = 1 then return {theElement}
set theList to {theElement}
repeat while (count of theList) < listLength / 2
copy contents of theList to ListB
copy theList & ListB to theList
end repeat
copy contents of theList to ListB
return (theList & items 1 thru (listLength - (count of ListB)) of ListB)
end makeList3
on compareVersion2(testVersion_S, minVersion_S)
## Copyright 2020 Eric Valk, Ottawa, Canada Creative Commons License CC BY-SA No Warranty.
## General purpose handler for comparing versions
--local digitMult, testVersionNumber, minVersionNumber, testVersion_LS, minVersion_LS, groupsCount, group_ctr
--local testGroupsCount, minGroupsCount, hasTestGroup, hasMinGroup, testGroupVersion, minGroupVersion
--local allBoundsPass, lowerBoundPass, lowerBoundFail
if (text ≠ (get class of testVersion_S)) or (text ≠ (get class of minVersion_S)) then error "compareVersion2() failed: Text inputs only"
set testVersion_LS to splitstringtolist(testVersion_S, ".")
set minVersion_LS to splitstringtolist(minVersion_S, ".")
set testGroupsCount to get count of testVersion_LS
set minGroupsCount to get count of minVersion_LS
set groupsCount to testGroupsCount + 0
if testGroupsCount < minGroupsCount then set groupsCount to minGroupsCount + 0
set {lowerBoundPass, lowerBoundFail} to {false, false}
repeat with group_ctr from 1 to groupsCount
set hasTestGroup to (get group_ctr ≤ testGroupsCount)
set hasMinGroup to (get group_ctr ≤ minGroupsCount)
if hasTestGroup then set testGroupVersion to 0 + (item group_ctr of testVersion_LS)
if hasMinGroup then set minGroupVersion to 0 + (item group_ctr of minVersion_LS)
if not (lowerBoundPass or lowerBoundFail) then
if hasMinGroup and hasTestGroup then
if testGroupVersion > minGroupVersion then set lowerBoundPass to true
if testGroupVersion < minGroupVersion then set lowerBoundFail to true
else if hasMinGroup then -- (and no testGroup) e.g. test 11 , min 11.1
set lowerBoundFail to true --
else if hasTestGroup then -- (and no minGroup) e.g. test 11.1, min 11 --> OK
set lowerBoundPass to true
end if
end if
end repeat
return (not lowerBoundFail)
end compareVersion2
on getScriptTitle2(removeExtension, replacePeriod)
## extracts the script tile from an alias as a string, removing the file path
## if removeExtension the file extension and period are removed
## Remaining "." are replaced by the replacePeriod characters
set script_path to (path to me) as string
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to ":"
if (script_path ends with ":") then
set Script_Title to text item -2 of script_path
else
set Script_Title to text item -1 of script_path
end if
set lastTextItem to -1
if removeExtension and (Script_Title contains ".") then set lastTextItem to -2
set AppleScript's text item delimiters to "."
set Script_Title to text items 1 thru lastTextItem of Script_Title
if false ≠ replacePeriod then set AppleScript's text item delimiters to replacePeriod
set Script_Title to Script_Title as text
end try
set AppleScript's text item delimiters to astid
if 0 = (get length of Script_Title) then error "getScriptTitle: Script Title is empty"
return Script_Title
end getScriptTitle2
on findTargetFolder(targetFolderParent_a, targetFolderName, debugLogLevel, enableCreate)
## Return a refernce to a folder. If the folder doesn't exist, create it.
## targetFolderParent_a can be an alias or a string
local targetFolder_p, targetFolder_a
local errorString1, errorNumber1, errorString2, errorNumber2
try
set targetFolder_p to (POSIX path of targetFolderParent_a) & targetFolderName
if 1 ≤ debugLogLevel then tell me to log " Initializing Folder \"" & targetFolder_p & "\""
set targetFolder_a to (get alias POSIX file targetFolder_p)
if 2 ≤ debugLogLevel then tell me to log "Folder \"" & targetFolderName & "\" exists"
on error errorString1 number errorNumber1
try
if (errorNumber1 = -1728) then
if enableCreate then
tell application "Finder" to set targetFolder_a to (make new folder at targetFolderParent_a with properties {name:targetFolderName}) as alias
if 2 ≤ debugLogLevel then tell me to log "Folder \"" & targetFolderName & "\" was created"
else
if 1 ≤ debugLogLevel then tell me to log "Could not find folder \"" & targetFolder_p & "\"."
set targetFolder_a to false
end if
else
error errorString1 number errorNumber1
end if
on error errorString2 number errorNumber2
error ("findTargetFolder() had error " & errorNumber2 & " \"" & errorString2 & "\".") number errorNumber2
end try
end try
return targetFolder_a
end findTargetFolder
-
Hi Eric,
thanks so much! This is extremely helpful.
I successfully just ran this on my two main CO21 catalogues (CO21 Build 14.0.0.275 on macOS 11.0.1).
One remark, though: in searchDocument the script searches for the Collection "All Images", which only exists if the system or app language is set to English. For other app languages, the script runs into an error, because the name of that collection is localized. I solved this by setting the app language for CO21 to english, a better approach might be to make the name of the collection containing all images one of the user changeable variables at the top of the script.
0 -
Hi Markus
I had suspected such a problem, I didn't think of your solution.
I will make an update accordingly.
Eric
0 -
Hi Markus
With the system app langauge set to your native language can you run the following code and report the result. Does it correctly identify the name of thee "All Images" folder?
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additionstell application "Capture One 12" to tell first document to set allImagesnames to {name of first collection whose user is false and kind is smart album, name of first collection}
log allImagesnames0 -
Hi Eric,
this is the result in the log window (and yes, "Alle Bilder" is the correct localized name for the collection, so this obviously works):
tell application "Capture One 21"
get name of collection 1 of document 1 whose user = false and kind = smart album
--> "Alle Bilder"
get name of collection 1 of document 1
--> "Alle Bilder"
end tell
(*Alle Bilder, Alle Bilder*)0 -
Hi Markus, I will make an update accordingly.
As of this morning I have lost the ability to use the the "code" format in in a forum posting, I don't yet understand if it is a browser cache issue or something with forum. Until I solve this I cannot post any more long scripts.
If you were to do a replace operation, replacing collection "All Images" with first collection then I think it will run and be independent of localisation (It runs for me)
0 -
Hi Eric,
thanks again. I just tried your suggestion and "first collection" seems to work as expected.
The code format seems to be gone in the edit window, while it still is available for new replies. So, I assume it's a forum issue.
0 -
Hi Walter, would you add it for me? I'm a little short of time and I would need to re-learn the proceedure.
0
Please sign in to leave a comment.
Comments
7 comments