SemiAutomatic Image Sequencer

Discussion regarding all scripting related questions
Please DO NOT post to this thread anything that is not directly related to scripting of Capture One.

SemiAutomatic Image Sequencer

Postby Eric Nepean » Tue Mar 12, 2019 7:55 pm

To efficiently handle large number of images, its key to separate the images into groups, and to do this efficiently.

I have been working for some time on an automatic image sequencer, in that process I have developed this much simpler semi automatic version where the user selects the variants in a sequence, and then the script takes care of the rest.

Invoking the script the first time creates a group called "Stacks" which will hold the albums for each image, and a smart album "Unsorted", which shows all the variants not yet in a sequence.

Images which are selected are copied into a sequence folder like SEQ 0030-0055 where the file name of the first variant ends with 0030 and the file name of the last variant ends with 0055. These images are assigned a blue color tag. The smart album hides all images with a blue or magenta color tag (so the user can assign Magenta to selected images).

Here is the script. Let me know how it works for you.

If you have an image naming system which isn't handled by the image name parser (resulting in errors or incorrect album names) give me some examples of your image filenames, and I can likely generate a parser rule that works for you.

The script is slightly too large to fit within the limits of this website (too many long variable names). Here is the first part, the second part will be in the next post. Just copy them in sequence to a Script Editor window.

Use the improved version of part 1 in post number 3 below
Code: Select all
## Applescript to sequence images in a COP 12 Catalog
## Version 12.21 !! NO GUARANTEE OF SUPPORT !!  Best effort
## Copyright 2019 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 document
-- Open the Script Editor log window, and select the messages tab
-- 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.

-- *** Operation
-- select a Project, an album or smart album which is under a Project or Group (or both)
--  Select some variants and run this script
-- If not already present, a group "Stacks" and a smart album "Unsorted" will be created
-- the smart album rules will hide all images with blue or magenta color tag
-- an album with a name starting with SEQ will be created in the group Stacks
-- the album name will contain the lowest and highest numbers sequences from the selected variants
-- the selected variants will be color tagged Blue and copied to the album
-- Once "Unsorted" Exists, continue creating sequences from there, only the unsequenced images will be shown.

use AppleScript version "2.5"
use scripting additions

property M : missing value
set M to me
global C, L, U
set C to me
set L to me
set U to me

set loqGUIsettings_L to L's setupLoqqing5() -- setup the variables used for Loqqing system

## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
##   Default values

set Loqqing's debugLogLevel to 0 --                  0...6 Values >1 result in increasing amounts of debug data that takes longer to report
set Loqqing's enableResultsFile to true --               (true/false)
set Loqqing's enableResultsByClipboard to false --      (true/false)
set Loqqing's enableNotifications to true --               (true/false)  - enable notifications of errors and exceptions
set Loqqing's notificationsMaxDebug to 1 --            0...6  suggest not more than 1
set Loqqing's ResultsFileMaxDebug to 3 --            0...6  suggest not more than 2
set Loqqing's enableResultsByNotifications to false

set nameStackGroup to "Stacks"
set nameUnsortedAlbum to "Unsorted"
set enableAlbumNameEntry to false

set sortedColorTag1 to 5 -- for the stacked images
set sortedColorTag2 to 6 -- for the picked stacked images
--Color Tag
--Criterion
--0--> none
--1-->red
--2-->orange
--3-->yellow
--4-->green
--5-->blue
--6-->magenta
--7-->purple

## ***** Semi 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.

set inpGroup1 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them 
set inpGroup2 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character
set inpMethod1 to {"SOOC Parse", {inpGroup1, inpGroup2}} -- parses the image file names typically assigned by a camera

set inpGroup3 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}, {"-", "M", 0}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them. Start over next method if a "-" is found.
set inpGroup4 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{{"-", "M", 0}}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character. Start over next method if a "-" is found.

set inpGroup5 to {inpPref:"", inpAlp:false, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:4, inpAct:{{return, "O"}, {return, "D"}}} -- Take Alpha and Numbers, stop on Space or symbol or 4 characters. Then discard
set inpGroup6 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:3, inpAct:{{"$", "G"}, {return, "O"}, {return, "D"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 3 characters. Then discard. Start over if a "$" is found
set inpGroup7 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:5, inpAct:{{"$", "G"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 5 characters. Start over if a "$" is found
set inpMethod2 to {"Universal Parse", {inpGroup3, inpGroup4}, {inpGroup5, inpGroup6, inpGroup7}} -- combination of Method 3 and Method 1
set inpMethod3 to {"Eric's Detailed Parse", {inpGroup5, inpGroup6, inpGroup7}} -- parses Eric's image file names

set imageNameParsingMethod to inpMethod1 -- OK to change this

## ***** 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.

set debugLogEnable to true

## Result reporting methods which are valid for this script
set Loqqing's gateResultsFile to true
set Loqqing's gateResultsByClipboard to true
set Loqqing's gateResultsNotification to true

## Reporting methods which are NOT valid for this script
-- disable the reporting method
set Loqqing's gateResultsDialog to false
-- disable user control
set Loqqing's enableResultsByDialog to false --         (true/false)
set Loqqing's maxDialogPercent to 85 --                   (0% to 100% of the monitor) The amount of data that triggers a dialog report
##


tell application "System Events" to set parent_name to name of current application
set Script_Title to (get name of me)
set Result_DocName to "CO_Image_Sorting_Report.txt"
set Loqqing's enableResultsByLoq to {"Script Editor"} contains parent_name

set loqResultMethod to L's InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

set {minCOPversion, maxCOPversion} to {"12", "12"}
tell C's validateCOP5(minCOPversion, maxCOPversion)
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {theAppName, copVersion} to {its theAppName, its copVersion}
end tell

tell application "Capture One 12" to set COPDocRef to get current document
tell C's validateCOPdoc5(COPDocRef, {"Catalog"})
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
end tell

tell C's validateCOPcollections5(COPDocRef) to set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
   {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}

tell application "Capture One 12" to set countSelectedVariants to get count of selected variants
tell application "Capture One 12" to set countEveryVariant to count of variants

set startSortRule to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"
set endSortRule to "</Criterion></Condition></MatchOperator></MatchOperator>"
set midSortRule to "</Criterion></Condition></MatchOperator><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"

set smartSortRule to startSortRule & sortedColorTag1 & midSortRule & sortedColorTag2 & endSortRule

set {theStacksRef, foundStacks} to findCollection(selectedCollectionRef, nameStackGroup, "group", true, true, {"group", "project"}, "")
if not foundStacks then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameStackGroup & "\" group")

set {theUnsortedRef, foundUnsorted} to findCollection(selectedCollectionRef, nameUnsortedAlbum, "smart album", true, true, {"group", "project"}, smartSortRule)
if not foundUnsorted then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameUnsortedAlbum & "\" smart album")

tell application "Capture One 12" to tell (variants whose selected is true) to set {theVariantsRef, theVariantsName} to {it, its name}
if (0 = (count of theVariantsName)) then error L's loqqed_Error_Halt5("No variants selected")


set numdigits to -1
repeat with aName in theVariantsName
   set thisCtr to findBaseName(aName, imageNameParsingMethod)
   set {thisLen, thisCtr} to {(count of thisCtr), (thisCtr as integer)}
   if -1 = numdigits then
      set {numdigits, minCtr, maxCtr} to {thisLen, (0 + (thisCtr as integer)), (0 + (thisCtr as integer))}
   else
      if thisLen > numdigits then set numdigits to thisLen
      if thisCtr < minCtr then set minCtr to thisCtr
      if thisCtr > maxCtr then set maxCtr to thisCtr
   end if
end repeat
set padZeros to U's makeList(numdigits, 0) as string
set seqCollName to "SEQ " & (text (-numdigits) thru -1 of (padZeros & minCtr))
if maxCtr > minCtr then set seqCollName to seqCollName & "-" & (text (-numdigits) thru -1 of (padZeros & maxCtr))

set userCanceled to false
if enableAlbumNameEntry then
   try
      set dialogResult to display dialog ¬
         "Sequence Name" buttons {"Cancel", "OK"} ¬
         default button "OK" cancel button "Cancel" default answer seqCollName
   on error number -128
      set userCanceled to true
   end try
   set seqCollName to dialogResult's text returned
end if

if not userCanceled then
   set {theSeqRef, gotSeq} to findCollection(theStacksRef, seqCollName, "album", true, false, {"group"}, "")
   if gotSeq then
      tell application "Capture One 12"
         add inside theSeqRef variants theVariantsRef
         tell (variants whose selected is true)
            set its color tag to sortedColorTag1
         end tell
      end tell
      L's loqThis(-1, false, "Copied " & (count of theVariantsRef) & " variants to  \"" & seqCollName & "\"")
   else
      error L's loqqed_Error_Halt5("Can't create the \"" & seqCollName & "\" album")
   end if
end if

################  The End ###############


on findCollection(thisCollRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler  to find and create a collection, starting from some arbitrary collection
   
   global debugLogEnable
   local thisCollKind, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, errorText, parentString, theParentRef, parentList, nextRef, refCtr, lastRef, hasFoundColl, cntColl, docMark, docName
   local saRules, saNoRules, debugText
   
   tell application "Capture One 12" to tell thisCollRef to set {thisCollKind, thisCollName, subCollNames, subCollRef} to {its kind, its name, name of its collections, its collections}
   set thisCollKind_S to C's convertKindList(thisCollKind)
   
   set debugText to kindTargetColl & " \"" & nameTargetColl & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
   if debugLogEnable then L's loqThis(2, false, return & "Find Collection: " & debugText & " with  Create: " & enableCreate & " Search: " & enableSearch & " Sort Rule: " & (count of saSortRule) & " characters")
   
   set isChevronForm to ("«" = text 1 of (thisCollKind as text))
   if debugLogEnable then
      L's loqThis(3, false, "SubCollections {" & U's joinListToString(subCollNames, "; ") & "}")
      L's loqThis(3, false, "Chevron Form: " & isChevronForm)
   end if
   
   set refFoundColl to {}
   if (kindParentColl_L contains thisCollKind_S) and (subCollNames contains nameTargetColl) then
      tell application "Capture One 12" to tell thisCollRef
         if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
         if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
         if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
         if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album))
      end tell
   end if
   
   set hasFoundColl to false
   if (1 = (count of refFoundColl)) then set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}
   
   if hasFoundColl then
      if debugLogEnable then L's loqThis(1, false, "Found " & debugText)
   else if (not enableCreate) and (not enableSearch) then
      L's loqThis(0, false, "The " & kindTargetColl & " \"" & nameTargetColl & "\" is missing from " & thisCollKind_S & " \"" & thisCollName & "\"")
   else
      if (not hasFoundColl) and enableCreate and (kindParentColl_L contains thisCollKind_S) then
         if debugLogEnable then L's loqThis(2, false, "Attempting to Create the collection")
         tell application "Capture One 12" to tell thisCollRef
            set saNoRules to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>"
            if ("group" = kindTargetColl) then set refFoundColl to {make new collection with properties {kind:group, name:nameTargetColl}}
            if ("album" = kindTargetColl) then set refFoundColl to {make new collection with properties {kind:album, name:nameTargetColl}}
            if ("smart album" = kindTargetColl) then
               set saNoRules to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>"
               try
                  if (text 1 thru 10 of saNoRules) = (text 1 thru 10 of saSortRule) then set saRules to saSortRule
               on error
                  set saRules to saNoRules
               end try
               set refFoundColl to {make new collection with properties {kind:smart album, name:nameTargetColl, rules:saRules}}
            end if
            try
               if ("project" = kindTargetColl) then set refFoundColl to {make new collection with properties {kind:project, name:nameTargetColl}}
            on error errorText
               if debugLogEnable then L's loqThis(2, false, "Creation failed with: " & errorText)
            end try
         end tell
         if (1 = (count of refFoundColl)) then
            set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}
            L's loqThis(1, false, "Created " & debugText)
         end if
      end if
      
      if (not hasFoundColl) and enableSearch then
         if debugLogEnable then L's loqThis(2, false, "Starting Search")
         try
            get || of {thisCollRef}
         on error errorText
         end try
         if isChevronForm then set errorText to U's replaceText(errorText, "«class COcl»", "collection")
         set parentList to U's splitStringToList(errorText, {"of", "{", "}"})
         repeat with refCtr from (count of parentList) to 0 by -1
            try
               if "document" = first word of item refCtr of parentList then exit repeat
            end try
         end repeat
         if 0 = (get (contents of refCtr) as integer) then error L's loqqed_Error_Halt5("Didn't find the document reference in " & errorText)
         set docMark to contents of refCtr
         set docName to U's removeLeadingTrailingSpaces((get item 2 of U's splitStringToList((U's removeLeadingTrailingSpaces((get item docMark of parentList))), "\"")))
         tell application "Capture One 12" to set lastRef to document docName
         set cntColl to 0
         repeat with refCtr from 3 to docMark - 1
            try
               if "collection" = (first word of item refCtr of parentList) then
                  set cntColl to cntColl + 1
                  if 2 ≤ cntColl then
                     tell application "Capture One 12" to set nextRef to collection id (get last word of parentList's item refCtr) of lastRef
                     copy nextRef to lastRef
                  end if
               end if
            end try
         end repeat
         if (0 = cntColl) then
            L's loqThis(0, false, "Unable to identify parent collection")
         else
            set {refFoundColl, hasFoundColl} to my findCollection(lastRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
         end if
      end if
   end if
   
   if not hasFoundColl then set refFoundColl to missing value
   return {refFoundColl, hasFoundColl}
end findCollection


on findBaseName(theString, theMethod_L)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler to parse an image file name
   
   ## Looks long, but execution time is about 20 - 80  microseconds
   ## theMethod_L is a list of Methods. Each Method describes a parsing method (a particular way of parsing a string)
   ## Each Method is a list of Groups, each Group is a record that describes how to find the end of a group of characters.
   ## While a group runs each character is copied from theString to the output until the end is found.
   ## The algorithm starts with Group 1 of Method 1. When a Group ends the next one starts
   ## When there are no Groups left or no Characters left,  the algorithm stops reading characters and returns the output characters.
   ## The Group record: {inpAlp, inpNum, inpSpa, inpSym, inpCnt, inpBck, inpAct}:
   ##  (inpAlp, inpNum, inpSpa, inpSym, inpBck) boolean,  (inpCnt)  integer,    inpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
   #3 Character Type Tests are evaluated first, then Actions
   ## A group ends upon finding Alphabetic characters (if inpAlp true), or Numbers (if inpNum true), or a Space(if inpSpa true),
   ## or Symbols (if inpSym true), or finding more than inpCnt characters (if inpCnt >0).
   ##  If inpBck is true, when a group ends, the last character becomes part of the next group
   ## EG Group3: {inpAlp:false, inpNum:false, inpSpa:false, inpSym:true, inpBck:true, inpCnt:9, inpAct:aList}
   ## Means:  stop if a symbol is found or after the 9th character, and the last character becomes the first character of the next group
   ## Thus up to 9 Numbers, Spaces and Alphabetic characters are accepted, and 9th character or symbol is part of the next group.
   ## if Group 3 is the last Group, then  9th or symbol character (and all subsequent characters) are dropped.
   
   ## inpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character.  inpAct= {} results in no actions
   ##     X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
   ##     X= return triggers the action on a Group Increment from Character Type Tests
   ## Actions (1):  T-Translate Character to "Z" ; G- Switch to Group Z ; M- Switch to Method Z ; I- Increment group by Z;
   ##     B- Drop Back 1 character; J- Jump ahead Z characters, D- Drop trigger Characters, O - Drop Output
   ##    Actions A,B,C,N,S and P set the value of inpAlp, inpBck, inpCnt, inpNum, inpSpa and inpSym
   ## If the Alist has no "z" value: for "I", z is taken as 1; for "J", z is taken as the length of the trigger; for "T" z is taken as "";
   ##    for G, z is taken as "0"; for M, the method is incremented, output and trigger are dropped
   ##    for all other commands z is taken as true when missing
   ## Selecting Group < 1 resets the parser to Group 1 and clears all output. If inpBck is true, the current character is dropped.
   ## Selecting Method 0 triggers a Method reset, output and input are reset, and then the previous method is incremented
   ## Switching to Method whose number does not exist causes the previous output to be cleared, the remaining input characters are accepted
   ## E.G. For inpAct:{{"$","M"},{"-","T"}} in Method 1 --> when an "$" is encountered, start over again with Group 1 of Method 2, also drop every "-"
   ## If there is no Method 2, all remaining characters of the input are taken as output.
   
   global debugLogEnable
   local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, iterationCtr, SafetyLimit
   local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, AlistTriggerId_L, anAlist, theAction, Prefix
   local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, countChars, stringCount
   local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newCharIDs, hasNewChars, nextGroup, nextMethod, nextCharPointer
   local triggerBack1Char, hasPar3, valuePar3, endTriggerPointer, lenActTrigger, actionString, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop, lenActTrigger
   
   
   set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList} to ¬
      {(get id of theString), (get count of theString), 1, (count of lists of theMethod_L), true, {}}
   set {charPointer, iterationCtr, SafetyLimit} to {1, 0, (MethodCount * stringCount * 4)}
   if debugLogEnable then
      L's loqThis(2, false, "Parsing \"" & theString & "\"")
      if (0 < (count of strings of theMethod_L)) then L's loqThis(2, false, ("Using Method " & (string 1 of theMethod_L)))
      L's loqThis(3, false, {"Initialising: stringCount " & stringCount & ",  Method " & nextMethod & ",  MethodCount " & MethodCount})
   end if
   
   repeat while (charPointer ≤ stringCount) and (iterationCtr < SafetyLimit)
      set iterationCtr to iterationCtr + 1
      
      if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, thisMethod} to ¬
         {1, (count of records of theMethod_L's list nextMethod), true, nextMethod}
      if debugLogEnable and triggerMethodInit then L's loqThis(3, false, "Start Method #" & nextMethod & ", Group Count: " & (count of theMethod_L's list nextMethod))
      
      if triggerGroupInit then
         tell theMethod_L's list thisMethod's record nextGroup to set {Prefix, Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, countChars, thisGroup} to ¬
            {its inpPref, its inpAlp, its inpNum, its inpSpa, its inpSym, its inpCnt, (0 < its inpCnt), its inpBck, its inpAct, 0, nextGroup}
         if debugLogEnable then L's loqThis(3, false, "Start Group #" & thisGroup & " ,  Prefix \"" & Prefix & "\",  Alpha " & Alpha & ",  Number " & Numb & ",  Blank " & Blank & ",  Symbol " & Symbol & ",  Max Count " & MaxCnt & ",  has Max Count " & hasMaxCnt & ",  Drop Back Last Character " & backOneOnGrpInc & " ,  ActionList : " & (count of ActionList) & " action lists")
         if (0 < (count of ActionList)) and ("list" ≠ ((class of ActionList's item 1) as text)) then error "Group " & thisGroup & " has an incorrectly formatted Action List"
         set parsedNumList to parsedNumList & (id of Prefix)
      end if
      
      set {triggerBack1Char, triggerGroupInc, triggerGroupInit, triggerMethodInit, validChar, newCharIDs, hasNewChars, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop} to ¬
         {false, false, false, false, true, {}, false, false, missing value, 0, false, false}
      
      set {thisCharsId, countChars, nextCharPointer, lenActTrigger} to {(theCharIDList's item charPointer), (countChars + 1), charPointer, 1}
      if debugLogEnable then L's loqThis(5, false, "Character " & charPointer & " : \"" & (string id (get theCharIDList's item charPointer)) & "\"")
      
      if hasMaxCnt and (countChars ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
         set triggerGroupInc to true
         if debugLogEnable then L's loqThis(3, false, "Count triggers Group Increment")
      else --  check the character type
         if debugLogEnable then L's loqThis(3, false, "Character Type Check")
         if ((thisCharsId ≥ 65) and (thisCharsId ≤ 90)) or ((thisCharsId ≥ 97) and (thisCharsId ≤ 122)) or (thisCharsId ≥ 192) then -- a latin character
            if Alpha then set triggerGroupInc to true
            if debugLogEnable and Alpha then L's loqThis(4, false, "Alpha triggers Group Increment")
         else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
            if Numb then set triggerGroupInc to true
            if debugLogEnable and Numb then L's loqThis(4, false, "Number triggers Group Increment")
         else if thisCharsId = 32 then --- its a space
            if Blank then set triggerGroupInc to true
            if debugLogEnable and Blank then L's loqThis(4, false, "Space triggers Group Increment")
         else if thisCharsId > 32 then -- its a symbol
            if Symbol then set triggerGroupInc to true
            if debugLogEnable and Symbol then L's loqThis(4, false, "Symbol triggers Group Increment")
         end if
      end if
      
      repeat with anAlist in ActionList
         set {AlistTriggerId_L, endTriggerPointer} to {((id of contents of anAlist's item 1) as list), (charPointer + (count of anAlist's item 1) - 1)}
         
         if (endTriggerPointer ≤ stringCount) and ((not hasActionTrigger) or (theActionTrigger = AlistTriggerId_L)) and ¬
            ((triggerGroupInc and ({13} = AlistTriggerId_L)) or (AlistTriggerId_L = (theCharIDList's items charPointer thru endTriggerPointer))) ¬
               then
            
            if debugLogEnable and not hasActionTrigger then L's loqThis(4, false, "Action List is triggered by \"" & (anAlist's item 1) & "\"")
            if not hasActionTrigger then set {hasActionTrigger, theActionTrigger, lenActTrigger} to {true, AlistTriggerId_L, (count of AlistTriggerId_L)}
            
            set {theAction, hasPar3, valuePar3} to {(item 2 of anAlist), (3 ≤ (count of anAlist)), true}
            if hasPar3 then set valuePar3 to (contents of item 3 of anAlist)
            
            if debugLogEnable then
               if hasPar3 then
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\" ; \"" & (contents of item 3 of anAlist) & "\""
               else
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\""
               end if
            end if
            
            ## Group 1 - Character Control -  Does Not clear triggerGroupInc
            
            if ("A" = theAction) then
               set Alpha to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpAlp to " & valuePar3)
               
            else if "B" = theAction then --
               set backOneOnGrpInc to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpBck to " & valuePar3)
            else if ("C" = theAction) then
               if (not hasPar3) or (0 = valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {0, false}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to 0")
               else if (0 < valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {valuePar3, true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to " & valuePar3)
               else if (0 > valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {(MaxCnt - valuePar3), true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Increment inpCnt by " & (-valuePar3))
               end if
               
            else if ("N" = theAction) then
               if hasPar3 then set Numb to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpNum to " & valuePar3)
               
            else if ("P" = theAction) then
               set Symbol to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & "- Set inpSym to " & valuePar3)
            else if ("Q" = theAction) then
               if debugLogEnable then L's loqThis(4, false, actionString & "- Quit Actions")
               exit repeat
               
            else if "S" = theAction then
               set Blank to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpSpa to " & valuePar3)
               
               ## Group 2 - Character Control -  clears triggerGroupInc on multi character trigger
               
            else if "D" = theAction then --
               set validChar to not valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Action Trigger: " & valuePar3)
               
            else if "O" = theAction then --
               set triggerOutputDrop to valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Output characters")
               
            else if "J" = theAction then --
               if not hasPar3 then set valuePar3 to lenActTrigger
               set {nextCharJump, hasJump} to {valuePar3, true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Jump forward " & valuePar3 & " Characters")
               
            else if "T" = theAction then -- translate/drop this character
               if not hasPar3 then set valuePar3 to ""
               set {newCharIDs, hasNewChars} to {(id of valuePar3), true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Translate characters to \"" & valuePar3 & "\"")
               
               ## Group 3 - Group & Method control, clears triggerGroupInc - later
            else if "G" = theAction then -- new group
               if not hasPar3 then set valuePar3 to 0
               set {nextGroup, triggerGroupInit} to {valuePar3, true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Group " & valuePar3)
               
            else if "I" = theAction then -- increment group
               if not hasPar3 then set valuePar3 to 1
               set {nextGroup, triggerGroupInit} to {(thisGroup + valuePar3), true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Increment Group to " & nextGroup)
               
            else if "M" = theAction then -- set method
               if not hasPar3 then set valuePar3 to (thisMethod + 1)
               set {nextMethod, triggerMethodInit, triggerOutputDrop, validChar} to {valuePar3, true, true, false}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Method " & valuePar3 & " and drop output and trigger")
               
            else
               if debugLogEnable then L's loqThis(3, false, actionString & " - Command not found ")
            end if
         end if
      end repeat
      
      if (lenActTrigger > 1) then set countChars to countChars - 1 + lenActTrigger
      if hasMaxCnt and (countChars ≥ MaxCnt) then set {triggerGroupInc} to {true, L's loqThis(3, false, "Character count triggers Group Increment")}
      
      if (triggerGroupInit or triggerMethodInit) then set triggerGroupInc to false
      if (triggerGroupInc or triggerGroupInit or triggerMethodInit) and backOneOnGrpInc then set triggerBack1Char to true
      
      if (0 ≥ nextMethod) then
         if debugLogEnable then L's loqThis(3, false, "Executing Method Reset - Increment Method, Clear output, Reset Input Queue")
         set {parsedNumList, nextMethod, triggerMethodInit, nextCharPointer} to {{}, (thisMethod + 1), true, 1, false}
         set {triggerBack1Char, triggerGroupInc, hasJump, hasNewChars, hasMaxCnt} to {false, false, false, false, false}
      else
         if (0 ≥ nextGroup) then
            set {triggerBack1Char, validChar, triggerGroupInc, hasJump, hasNewChars} to {false, false, false, false, false}
            if debugLogEnable then L's loqThis(3, false, "Executing Group Reset - Switch to Group 1, Clear output , dropping back " & (-nextGroup) & " characters")
            if (-1 ≥ nextGroup) then set {nextCharJump, hasJump} to {(nextGroup), true}
            set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
         end if
         
         if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true, L's loqThis(3, false, "Executing Group Increment - Switch to Group " & (thisGroup + 1))}
         
         if debugLogEnable and triggerOutputDrop then L's loqThis(4, false, "Dropped Output")
         if triggerOutputDrop then set parsedNumList to {}
         if triggerBack1Char then
            set {validChar, nextCharPointer} to {false, charPointer}
            if debugLogEnable then L's loqThis(3, false, "Drop Back 1 Character")
         else if (hasJump or hasNewChars) then
            set validChar to false
            if (hasJump and (((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or (0 < nextCharJump)))) then
               set nextCharPointer to (charPointer + nextCharJump)
               if nextCharPointer < 1 then set nextCharPointer to 1
               if hasNewChars and (0 < nextCharJump) and (lenActTrigger > nextCharJump) then set nextCharPointer to (charPointer + lenActTrigger)
            else if hasJump then
               ## do nothing - can't move charPointer back
               if debugLogEnable then L's loqThis(3, false, "Unable to move back " & (-nextCharJump) & " characters - infinite loop")
            else if hasNewChars then -- and not hasJump
               set nextCharPointer to (charPointer + lenActTrigger)
            end if
            if debugLogEnable then L's loqThis(3, false, "Input Moved Forward " & (nextCharPointer - charPointer) & " characters")
         else
            set nextCharPointer to (charPointer + 1)
            if debugLogEnable then L's loqThis(5, false, "Input Moved Forward 1 character")
         end if
         if hasNewChars then set {parsedNumList} to {(parsedNumList & newCharIDs), L's loqThis(4, false, "Added new Characters to the output")}
         if validChar then set end of parsedNumList to thisCharsId
         if debugLogEnable and not validChar then L's loqThis(3, false, "The input character has been dropped")
      end if
      if debugLogEnable then L's loqThis(3, false, "The output is: \"" & (string id parsedNumList) & "\"")
      
      if nextMethod > MethodCount then -- replace the output with  every remaining character
         if debugLogEnable then L's loqThis(3, false, "Got Method " & nextMethod & "  Return remaining characters")
         if charPointer > 1 then set parsedNumList to (theCharIDList's items charPointer thru stringCount)
         if charPointer ≤ 1 then copy theCharIDList to parsedNumList
         exit repeat
      else if (nextGroup > GroupCount) then
         if debugLogEnable then L's loqThis(3, false, "Finished all groups, exitting")
         exit repeat
      end if
      set charPointer to nextCharPointer -- must be last statement in the the loop
   end repeat
   if debugLogEnable and (charPointer > stringCount) then L's loqThis(3, false, "Finished all characters")
   
   return (string id parsedNumList)
end findBaseName

##########################################
## Capture One General Handlers  Version 2019/03/11

on validateCOP5(minCOPversionstr, maxCOPversionstr)
   ## Copyright 2019 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 COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
   local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
   local theAppName, copVersion
   
   tell application "System Events"
      if debugLogEnable then
         L's loqThis(2, false, ("COP Processes:" & (get U's joinListToString((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
         L's loqThis(3, false, ("All Processes: " & (get U's joinListToString((get name of every process whose background only is false), ", "))))
      end if
      set COPProcList to every process whose name begins with "Capture One" and background only is false
      if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("COP is not running"))}
      if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
      set theAppRef to item 1 of COPProcList
      set theAppName to ((get name of theAppRef) as text)
      set copDetailedVersion to get version of my application theAppName
   end tell
   
   tell application "Capture One 12" to set copVersionStr to (get app version)
   set copVersion to (word -1 of copVersionStr)
   
   if debugLogEnable then
      L's loqThis(2, false, ("theAppName: " & theAppName))
      L's loqThis(1, false, copVersionStr)
      L's loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
   end if
   tell U's compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
   if not minVersionPass then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5(("This Script does not support Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
   if not maxVersionPass then L's loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
   return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
end validateCOP5

on validateCOPdoc5(theDocRef, validDocKindList)
   ## Copyright 2019 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 12" to set theDocRef to get current document
   
   try
      tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   on error errorText number errorNumber
      return {hasErrors:true, errorText:(get L's 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 L's 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 validateCOPcollections5(theDocRef)
   ## Copyright 2019 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, and the top level collections
   global debugLogEnable
   local namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection
   
   tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   set COPDocKind_s to convertKindList(COPDocKind_p)
   
   tell application "Capture One 12" to tell theDocRef
      set selectedCollectionRef to get current collection
      if (missing value = selectedCollectionRef) then
         try
            set current collection to collection "All Images"
         on error
            set current collection to last collection
         end try
         set selectedCollectionRef to get current collection
      end if
      tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
      set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
   end tell
   set countTopCollections to count of namesTopCollections
   
   repeat with collectionCounter from 1 to countTopCollections
      if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
         (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
         set selectedCollectionIndex to collectionCounter
         exit repeat
      end if
   end repeat
   
   local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections
   
   if COPDocKind_s = "catalog" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter - 1
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set selectedCollectionMirroredAtTopLast to ¬
         (selectedCollectionIndex = countTopCollections) and ¬
         ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)
      
      set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}
      
   else if COPDocKind_s = "session" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("favorite" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set countFavoriteCollections to countTopCollections - topUserCollectionIndex
      if 0 = countFavoriteCollections then
         set namesFavoriteCollections to {}
      else
         set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
      end if
      
      set selectedCollectionMirroredAtTopLast to false
   end if
   
   local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
   set selectedCollectionIsUser to ¬
      (selectedCollectionMirroredAtTopLast or ¬
         ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))
   
   if topUserCollectionIndex < bottomUserCollectionIndex then
      set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
      set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
   else
      set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
      set countTopUserCollections to count of namesTopUserCollections
   end if
   
   return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}
   
end validateCOPcollections5

on convertKindList(kind_list)
   ## Copyright 2019 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_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type
   
   set kind_s_list to {}
   set input_is_list to ("list" = (get (class of kind_list) as text))
   if input_is_list then
      if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      repeat with theItem in kind_list
         tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
      end repeat
      if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
   else
      if ("text" = (get (class of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
      if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
      set kind_s_list to {kind_s1}
   end if
   
   set fail_flag to false
   set code_start to -5
   
   set kind_list to {}
   repeat with Kind_s_item in kind_s_list
      if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
      set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
      set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)
      
      if kind_type = "CC" then ## Collection Kinds
         if kind_code = "CCpj" then
            set the end of kind_list to "project"
         else if kind_code = "CCgp" then
            set the end of kind_list to "group"
         else if kind_code = "CCal" then
            set the end of kind_list to "album"
         else if kind_code = "CCsm" then
            set the end of kind_list to "smart album"
         else if kind_code = "CCfv" then
            set the end of kind_list to "favorite"
         else if kind_code = "CCff" then
            set the end of kind_list to "catalog folder"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CL" then ## Layer Kinds
         if kind_code = "CLbg" then
            set the end of kind_list to "background"
         else if kind_code = "CLnm" then
            set the end of kind_list to "adjustment"
         else if kind_code = "CLcl" then
            set the end of kind_list to "clone"
         else if kind_code = "CLhl" then
            set the end of kind_list to "heal"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CR" then ## Watermark Kinds
         if kind_code = "CRWn" then
            set the end of kind_list to "none"
         else if kind_code = "CRWt" then
            set the end of kind_list to "textual"
         else if kind_code = "CRWi" then
            set the end of kind_list to "imagery"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CO" then ## Document Kinds
         if kind_code = "COct" then
            set the end of kind_list to "catalog"
         else if kind_code = "COsd" then
            set the end of kind_list to "session"
         else
            set fail_flag to true
         end if
      else
         set fail_flag to true
      end if
      
      if fail_flag then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
   end repeat
   
   if input_is_list then
      return kind_list
   else
      return item 1 of kind_list
   end if
   
end convertKindList
Last edited by Eric Nepean on Fri Mar 15, 2019 12:10 am, edited 4 times in total.
Cheers, Eric
[late 2015 iMac, 4GHz i7, 24GB RAM, external SSDs. GX8, E-M1, GX7, GM5, GM1 ....]
Eric Nepean
 
Posts: 445
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa

Re: SemiAutomatic Image Sequencer

Postby Eric Nepean » Tue Mar 12, 2019 7:56 pm

Here is the second part of the script

Code: Select all
###############################
## Logging Handlers  Version 2019/02/23

on setupLoqqing5()
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## Handler to setup the variables used by the loqqing system at the start of the script
   
   global debugLogEnable, Loqqing, loqResultDocRef, loqResultMethod, loqDialogTextList
   ## loqResultDocRef is kept separate from Loqqing to allow easy listing of Loqqing
   
   ## Default values are entered
   set {loqResultDocRef, loqResultMethod} to {false, "not Initialized"}
   set Loqqing to {stateResultDoc:false, gateResultsFile:false, enableResultsFile:false, initResultDoc:false, ResultsFileMaxDebug:0, debugLogLevel:0}
   set Loqqing to Loqqing & {stateResultsByClipboard:true, gateResultsByClipboard:true, enableResultsByClipboard:true}
   set Loqqing to Loqqing & {stateResultsByDialog:false, gateResultsDialog:false, enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000}
   set Loqqing to Loqqing & {stateResultsByNotification:false, gateResultsNotification:false, enableResultsByNotifications:false, enableNotifications:true, notificationsMaxDebug:0}
   set Loqqing to Loqqing & {stateResultsByLoq:true, gateParentLoqqing:true, enableResultsByLoq:true, initLoqqing:false, gateLoqqing:true}
   
   return {}
end setupLoqqing5

on InitializeLoqqing5(DocName_Ext, sourceTitle)
   ## Copyright 2019 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 parent_name, Script_Title, Loqqing, gateScriptProgress, loqDialogTextList, loqResultDocRef
   
   local LogMethods, LogHeader, date_string
   tell current application to set date_string to (current date) as text
   set LogMethods to {}
   set LogHeader to (sourceTitle & " results on " & date_string)
   
   local targetFileWasCreated, TextEditlist, createdLine
   if Loqqing's gateResultsFile and Loqqing's enableResultsFile then
      set end of LogMethods to DocName_Ext
      if not Loqqing's initResultDoc then
         set targetFileWasCreated to false
         set Loqqing's stateResultDoc to false
         set Loqqing's initResultDoc to true
         
         ## If TextEdit is already open and has the document open then add the headerline
         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 & LogHeader & return
                  set Loqqing's stateResultDoc to true
               end tell
            end if
         end if
      end if
      
      local targetFolderParent_a, targetFolderParent_p, targetFolderName, targetFolder_a, targetFolder_p, ResultDocPath_a, ResultDocPath_p, newFolderRef, newFileRef
      if not Loqqing's stateResultDoc or (false = loqResultDocRef) then
         -- create the document and the folder if necessary
         -- Do not use finder to test for the file existence because it has a bug that ignores leading 0's
         -- https://www.macscripter.net/viewtopic.php?id=45178
         set targetFolderParent_a to alias (get path to desktop folder as text)
         set targetFolderParent_p to get POSIX path of targetFolderParent_a
         set targetFolderName to "ScriptReports"
         set targetFolder_p to (targetFolderParent_p & targetFolderName)
         set ResultDocPath_p to targetFolder_p & "/" & DocName_Ext
         
         try
            set ResultDocPath_a to (get alias POSIX file ResultDocPath_p)
         on error
            try
               set targetFolder_a to (get alias POSIX file targetFolder_p) --x1
            on error
               tell application "Finder" to set newFolderRef to make new folder at targetFolderParent_a with properties {name:targetFolderName}
               set targetFolder_a to newFolderRef as alias
            end try
            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 targetFileWasCreated to true
         end try
         
         set createdLine to ("Created by " & Script_Title & " on " & date_string)
         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 targetFileWasCreated then
                  set paragraph 1 to createdLine & return & return
               else
                  if (0 = (count of paragraphs)) then set paragraph 1 to createdLine & return & return
               end if
            end tell
            if 2 ≤ Loqqing's debugLogLevel then tell me to log ResultDocPath_p & ": " & createdLine
         end tell
         
         set Loqqing's stateResultDoc to true -- prevents initialisation from repeating
         tell application "TextEdit" to tell text of loqResultDocRef to ¬
            set paragraph (1 + (count paragraphs)) to return & LogHeader & return
      end if
   end if
   if Loqqing's gateResultsFile and Loqqing's stateResultDoc and not Loqqing's enableResultsFile then
      set Loqqing's stateResultDoc to false
      tell application "TextEdit" to tell text of loqResultDocRef to ¬
         set paragraph (1 + (count paragraphs)) to return & parent_name & "Results reporting disabled for: " & Script_Title & return
   end if
   
   local screenWidthO, screenHeightO, screenWidth, screenHeight, fontSize_pts, charactersPerLine, dotsPer_Point, linesPerScreen, borderLines
   if Loqqing's gateResultsDialog and Loqqing's enableResultsByDialog then
      set end of LogMethods to "Dialogs"
      if not Loqqing's stateResultsByDialog then
         set loqDialogTextList to (get LogHeader & return)
         set Loqqing's stateResultsByDialog to true
      end if
      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 fo 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 borderLines to 5
      set Loqqing's maxDialogLines to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines)) as integer)
      set Loqqing's maxDialogChar to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines) * charactersPerLine) as integer)
      if 2 ≤ Loqqing's debugLogLevel then log {"maxDialogPercent", Loqqing's maxDialogPercent, "screenHeight", screenHeight, "linesPerScreen", linesPerScreen, "maxDialogLines", Loqqing's maxDialogLines, "maxDialogChar", Loqqing's maxDialogChar}
   end if
   if Loqqing's gateResultsDialog and Loqqing's stateResultsByDialog and not Loqqing's enableResultsByDialog then
      set Loqqing's stateResultsByDialog to false
      set loqDialogTextList to ""
   end if
   
   if Loqqing's gateResultsByClipboard and Loqqing's enableResultsByClipboard then
      set end of LogMethods to "Clipboard"
      if not Loqqing's stateResultsByClipboard then
         set the clipboard to LogHeader
         set Loqqing's stateResultsByClipboard to true
      end if
   end if
   if Loqqing's gateResultsByClipboard and Loqqing's stateResultsByClipboard and not Loqqing's enableResultsByClipboard then
      set the clipboard to {}
      set Loqqing's stateResultsByClipboard to false
   end if
   
   global gateScriptProgress
   if not Loqqing's initLoqqing then
      set Loqqing's initLoqqing to true
      if ("Script Editor" = parent_name) then
         set Loqqing's gateParentLoqqing to true
         set Loqqing's gateLoqqing to true
         set gateScriptProgress to true
         try -- Open the Log History window
            tell application "System Events" to tell application process "Script Editor"
               if (get name of windows) does not contain "log History" then ¬
                  click menu item "Log History" of menu "Window" of menu bar 1
            end tell
         end try
      else if ("Script Debugger" = parent_name) then
         set Loqqing's gateParentLoqqing to false
         set Loqqing's gateLoqqing to true
         set gateScriptProgress to true
         ## Avoids compiler errors when Script Debugger is not present
         run script "tell application \"Script Debugger\" to tell first document to set event log visible to true"
         run script "tell application \"Script Debugger\" to tell first document to set event log scope bar visible to true"
      else
         set Loqqing's gateParentLoqqing to false
         set Loqqing's gateLoqqing to false
         set gateScriptProgress to false
      end if
   end if
   
   if Loqqing's gateParentLoqqing and Loqqing's enableResultsByLoq then
      set end of LogMethods to " " & parent_name & " Log"
      if not Loqqing's stateResultsByLoq then
         set Loqqing's stateResultsByLoq to true
         log "Results Logging enabled for: " & Script_Title
      end if
   end if
   if Loqqing's gateParentLoqqing and Loqqing's stateResultsByLoq and not Loqqing's enableResultsByLoq then
      set Loqqing's stateResultsByLoq to false
      log "Results Logging disabled for: " & Script_Title
   end if
   
   if (Loqqing's gateResultsNotification and Loqqing's enableResultsByNotifications) or Loqqing's enableNotifications then
      set end of LogMethods to "Notifications"
      if not Loqqing's stateResultsByNotification then
         --display notification "Notifications enabled for: " & Script_Title
         set Loqqing's stateResultsByNotification to true
      end if
   else if Loqqing's stateResultsByNotification then
      display notification "Notifications disabled for: " & Script_Title
      set Loqqing's stateResultsByNotification to false
   end if
   
   set LogMethods_S to U's joinListToString(LogMethods, ", ")
   if 2 ≤ Loqqing's debugLogLevel then loqThis(2, false, ("Result Reported by " & LogMethods_S))
   return LogMethods_S
end InitializeLoqqing5

on loqThis(thisLogDebugLevel, MakeFront, log_Text)
   ## Copyright 2019 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 ebabled - {Script Editor Log, Text Editor Log, Display Dialog}
   
   global parent_name, Loqqing, loqDialogTextList, loqResultDocRef
   local log_Text_S
   
   if thisLogDebugLevel ≤ Loqqing's debugLogLevel then
      set log_Text_S to U's joinListToString(log_Text, "; ")
      set triggeredDialog to false
      
      if (thisLogDebugLevel = 0) then -- Critical errors
         if Loqqing's gateLoqqing then log (log_Text_S)
         if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))
         if Loqqing's enableResultsFile then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to (log_Text_S & return)
         if Loqqing's enableResultsByDialog then display alert log_Text_S giving up after 10
         (Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0))
      else if (thisLogDebugLevel ≤ 0) then -- Expected Results
         if Loqqing's enableResultsByLoq then log (log_Text_S)
         if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))
         
         if Loqqing's enableResultsFile then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to (log_Text_S & return)
         
         if Loqqing's enableResultsByDialog then -- process the dialog last
            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 parent_name to true
               display dialog loqDialogTextList
               set triggeredDialog to true
               set loqDialogTextList to ""
            end if
         end if
         
      else -- debugging information
         if Loqqing's gateLoqqing then log (log_Text_S)
         if Loqqing's enableResultsFile and ((thisLogDebugLevel ≤ Loqqing's ResultsFileMaxDebug) or not Loqqing's gateLoqqing) then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to ((log_Text_S as text) & return)
      end if
      
      if (Loqqing's enableNotifications and (thisLogDebugLevel ≤ Loqqing's notificationsMaxDebug)) or ¬
         (Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0)) then
         set paraCtr to 0
         set notString to ""
         set lineCnt to 39
         set paramax to 3
         copy (get paramax * lineCnt) to remChar
         repeat with thePara in (get paragraphs of log_Text_S)
            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 triggeredDialog) and (MakeFront or (0 = thisLogDebugLevel)) then --
         if Loqqing's gateLoqqing then tell application "System Events" to set frontmost of process parent_name to true
         if Loqqing's enableResultsFile then tell application "System Events" to set frontmost of process "TextEdit" to true
      end if
      
   else
      set log_Text_S to ""
   end if
   return log_Text_S
end loqThis

on loqqed_Error_Halt5(errorText)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler for logging during script termination
   global debugLogEnable, Script_Title
   try
      M's finalCleanup()
   end try
   tell current application to set date_string to (current date) as text
   return (get loqThis(0, true, ("Script \"" & Script_Title & "\" is exitting at " & date_string & "Reason: " & errorText & return)))
end loqqed_Error_Halt5

####################################
## General Utility Handlers  Version 2019/01/12
## No Dependencies on other Libraries

on compareVersion(testVersion_S, minVersion_S, maxVersion_S)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler for comparing versions
   --local digitMult, testVersionNumber, minVersionNumber, maxVersionNumber, testVersion_LS, minVersion_LS, maxVersion_LS, groupsCount, group_ctr
   --local testGroupsCount, minGroupsCount, maxGroupsCount, hasTestGroup, hasMinGroup, hasMaxGroup, testGroupVersion, minGroupVersion, maxGroupVersion
   --local allBoundsPass, lowerBoundPass, upperBoundPass, lowerBoundFail, upperBoundFail
   
   if (text ≠ (get class of testVersion_S)) or (text ≠ (get class of minVersion_S)) or (text ≠ (get class of maxVersion_S)) then error "Text inputs only"
   
   set testVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(testVersion_S), "."))
   set minVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(minVersion_S), "."))
   set maxVersion_LS to (splitStringToList(removeLeadingTrailingSpaces(maxVersion_S), "."))
   
   set testGroupsCount to get count of testVersion_LS
   set minGroupsCount to get count of minVersion_LS
   set maxGroupsCount to get count of maxVersion_LS
   
   set groupsCount to testGroupsCount
   if groupsCount < minGroupsCount then set groupsCount to minGroupsCount
   if groupsCount < maxGroupsCount then set groupsCount to maxGroupsCount
   
   set lowerBoundPass to false
   set lowerBoundFail to false
   set upperBoundPass to false
   set upperBoundFail to false
   
   repeat with group_ctr from 1 to groupsCount
      
      set hasTestGroup to (get group_ctr ≤ testGroupsCount)
      set hasMinGroup to (get group_ctr ≤ minGroupsCount)
      set hasMaxGroup to (get group_ctr ≤ maxGroupsCount)
      
      if hasTestGroup then set testGroupVersion to (get (item group_ctr of testVersion_LS) as integer)
      if hasMinGroup then set minGroupVersion to (get (item group_ctr of minVersion_LS) as integer)
      if hasMaxGroup then set maxGroupVersion to (get (item group_ctr of maxVersion_LS) as integer)
      
      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 --  not symetric with upper bound behaviour
         else if hasTestGroup then -- (and no minGroup) e.g. test 11.1, min 11 --> OK
            set lowerBoundPass to true
         end if
      end if
      
      if not (upperBoundPass or upperBoundFail) then
         if hasMaxGroup and hasTestGroup then
            if testGroupVersion < maxGroupVersion then set upperBoundPass to true
            if testGroupVersion > maxGroupVersion then set upperBoundFail to true
         else if hasMaxGroup then -- (and no testGroup) e.g. test 11.1 , max 11.1.2 or test 11 , max 11.1
            set upperBoundPass to true --  not symetric with lower bound behaviour
         else if hasTestGroup then --(and no maxGroup) e.g. test 11.1, max 11 --> pass
            set upperBoundPass to true
         end if
      end if
      
   end repeat
   
   return {maxVersionPass:(not upperBoundFail), minVersionPass:(not lowerBoundFail)}
end compareVersion

on deReference(theItem, theclassName)
   ## General purpose handler for removing references from a variable
   ## reusult is a value, or list of values, of the specified class
   ## Enables data handling of multiple classes of items in the same code
   
   if class = (get class of theclassName) then set theclassName to (get theclassName as text)
   
   if list = (get class of (get theItem)) then
      set theResult to {}
      set cntItems to length of theItem
      if (text = (get class of theclassName)) then
         set hasClassList to false
      else if (list = (get class of theclassName)) then
         if (1 = (get length of theclassName)) then
            set hasClassList to false
         else if (cntItems = (get length of theclassName)) then
            set hasClassList to true
         else
            error "deReference() can't handle a mismatch between number of items and number of classes"
         end if
      end if
      if not hasClassList then copy (get theclassName as text) to thisclassName
      repeat with item_ctr from 1 to cntItems
         if hasClassList then copy (get (theclassName's item item_ctr) as text) to thisclassName
         copy (get theItem's item item_ctr) to thisItem
         if "boolean" = thisclassName then
            set the end of theResult to false or (get thisItem as boolean)
         else if "integer" = thisclassName then
            set the end of theResult to 0 + (get thisItem as integer)
         else if "text" = thisclassName then
            set the end of theResult to "" & (get thisItem as text)
         else if "real" = thisclassName then
            set the end of theResult to 0.0 + (get thisItem as real)
         else if "date" = thisclassName then
            set the end of theResult to (get thisItem as date) + 0
         else
            error "deReference() can't handle class \"" & thisclassName & "\""
         end if
      end repeat
      
   else
      if "boolean" = theclassName then
         set theResult to false or (get theItem as boolean)
      else if "integer" = theclassName then
         set theResult to 0 + (get theItem as integer)
      else if "text" = theclassName then
         set theResult to "" & (get theItem as text)
      else if "real" = theclassName then
         set theResult to 0.0 + (get theItem as real)
      else if "date" = theclassName then
         set theResult to (get theItem as date) + 0
      else
         error "deReference() can't handle class \"" & theclassName & "\" as " & (get class of theclassName)
      end if
   end if
   return theResult
end deReference

on makeList(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 makeList

on splitStringToList(theString, theDelim)
   ## Public Domain
   set theList to {}
   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

on 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
   end try
   set AppleScript's text item delimiters to astid
   return theString
end joinListToString

on removeLeadingTrailingSpaces(theListString)
   ## 40% faster than a version which trims the string 1 space at a time
   ## handles both string and list input correctly
   -- local theListString, input_is_list, cleanList, thecount, hasTriggered, indexLow, indexHigh
   
   set input_is_list to ("list" = (get (class of theListString) as text))
   if not input_is_list then set theListString to {theListString}
   
   set cleanList to {}
   repeat with theString in theListString
      if ("text" = (get (class of theString) as text)) then
         set thecount to (get count of theString)
         set hasTriggered to false
         repeat with indexLow from 1 to thecount
            if " " ≠ (get text indexLow of theString as text) then
               set hasTriggered to true
               exit repeat
            end if
         end repeat
         if not hasTriggered then
            set theString to ""
         else
            repeat with indexHigh from -1 to (-thecount) by -1
               if " " ≠ (get text indexHigh of theString as text) then exit repeat
            end repeat
            set theString to text indexLow thru indexHigh of theString
         end if
      end if
      
      set the end of cleanList to theString
   end repeat
   
   if input_is_list then return cleanList
   
   return (get contents of theString)
end removeLeadingTrailingSpaces

on replaceText(this_text, search_string, replacement_string)
   set astid to AppleScript's text item delimiters
   try
      set AppleScript's text item delimiters to the search_string
      set the item_list to every text item of this_text
      set AppleScript's text item delimiters to the replacement_string
      set this_text to the item_list as string
   end try
   set AppleScript's text item delimiters to astid
   return this_text
end replaceText
Cheers, Eric
[late 2015 iMac, 4GHz i7, 24GB RAM, external SSDs. GX8, E-M1, GX7, GM5, GM1 ....]
Eric Nepean
 
Posts: 445
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa

Re: SemiAutomatic Image Sequencer

Postby Eric Nepean » Thu Mar 14, 2019 11:51 pm

Heree is an update which is better documented and has more functions.

With this version you can:
  • have a counter in the sequence name instead of the image numbers
  • the sequence name can be copied into the variant's metadata (selectable)
  • Making albums for each sequence is selectable
  • Making the Unsorted smart album is selectable

Also a couple of bug fixes.

This is the first part, the second part has not changed.
Copy and paste this part into Script Editor's script window, and then copy and paste the second part from thee previous posting.

Code Editted once
Code: Select all
## Applescript to sequence images in a COP 12 Catalog
## Version 12.22 !! NO GUARANTEE OF SUPPORT !!  Best effort
## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.

-- *** Use and Function
-- select a Project, an album or smart album which is under a Project or Group (or both)
--  Select some variants and run this script
-- If not already present, a group "Stacks" and a smart album "Unsorted" will be created
-- the smart album rules will hide all images with blue or magenta color tag
-- an album with a name starting with SEQ will be created in the group Stacks
-- the album name will contain the lowest and highest numbers sequences from the selected variants
-- the selected variants will be color tagged Blue and copied to the album
-- the Sequence name will be copied into the IPTC Image Scenes metadata of the Variants
-- Once "Unsorted" Exists, continue creating sequences from there, only the unsequenced images will be shown.
-- ***To Setu

-- 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 document
-- Open the Script Editor log window, and select the messages tab
-- 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

property M : missing value
set M to me
global C, L, U
set C to me
set L to me
set U to me

set loqGUIsettings_L to L's setupLoqqing5() -- setup the variables used for Loqqing system

## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
##   Default values

### Read Carefully
set enableSeqImageName to true --         enable image based sequence name; disabling results in counter based sequence names

set enableCreateCollections to true --         this enable creating of albums for each sequence, and creating of the "Stacks" group
set enableCreateUnsorted to true --         this enable creating of  of the "Unsorted" smart album
set nameStackGroup to "Stacks" --         this is the name of group that holds the albums for each sequence
set nameUnsortedAlbum to "Unsorted" --      this is the name of smart album that shows the not yet sequenced variants
set enableSequenceNameEntry to false --      enables manual entry of sequence names
set seqBaseImageName to "SEQ " --         the base used for image based sequence name
set seqBaseCountedName to "SEQ-" --      the base used for counter based sequence name
set seqCountDigits to 2 --               the number of counter digits in a counter based sequence name
property sequenceCount : 1 --            the starting count in a counter based sequence name
set enableSeqInMetaData to true --         shall the sequence name be written to the IPTC Image Scenes metadata
set enableMetaDataMultiSeq to true --      shall the sequence name be added to other sequences in the IPTC Image Scenes metadata

set Loqqing's debugLogLevel to 0 --                  0...6 Values >1 result in increasing amounts of debug data that takes longer to report
set Loqqing's enableResultsFile to true --               (true/false)
set Loqqing's enableResultsByClipboard to false --      (true/false)
set Loqqing's enableNotifications to true --               (true/false)  - enable notifications of errors and exceptions
set Loqqing's notificationsMaxDebug to 1 --            0...6  suggest not more than 1
set Loqqing's ResultsFileMaxDebug to 3 --            0...6  suggest not more than 2
set Loqqing's enableResultsByNotifications to false
set Loqqing's enableResultsByLoq to true


set sortedColorTag1 to 5 -- for the stacked images
set sortedColorTag2 to 6 -- for the picked stacked images
--Color Tag
--Criterion
--0--> none
--1-->red
--2-->orange
--3-->yellow
--4-->green
--5-->blue
--6-->magenta
--7-->purple


## ***** 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.

set inpGroup1 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them 
set inpGroup2 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character
set inpMethod1 to {"SOOC Parse", {inpGroup1, inpGroup2}} -- parses the image file names typically assigned by a camera

set inpGroup3 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}, {"-", "M", 0}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them. Start over next method if a "-" is found.
set inpGroup4 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{{"-", "M", 0}}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character. Start over next method if a "-" is found.

set inpGroup5 to {inpPref:"", inpAlp:false, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:4, inpAct:{{return, "O"}, {return, "D"}}} -- Take Alpha and Numbers, stop on Space or symbol or 4 characters. Then discard
set inpGroup6 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:3, inpAct:{{"$", "G"}, {return, "O"}, {return, "D"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 3 characters. Then discard. Start over if a "$" is found
set inpGroup7 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:5, inpAct:{{"$", "G"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 5 characters. Start over if a "$" is found
set inpMethod2 to {"Universal Parse", {inpGroup3, inpGroup4}, {inpGroup5, inpGroup6, inpGroup7}} -- combination of Method 3 and Method 1
set inpMethod3 to {"Eric's Detailed Parse", {inpGroup5, inpGroup6, inpGroup7}} -- parses Eric's image file names

set imageNameParsingMethod to inpMethod1 -- OK to change this

set debugLogEnable to true

## Result reporting methods which are valid for this script
set Loqqing's gateResultsFile to true
set Loqqing's gateResultsByClipboard to true
set Loqqing's gateResultsNotification to true

## Reporting methods which are NOT valid for this script
-- disable the reporting method
set Loqqing's gateResultsDialog to false
-- disable user control
set Loqqing's enableResultsByDialog to false --         (true/false)
set Loqqing's maxDialogPercent to 85 --                   (0% to 100% of the monitor) The amount of data that triggers a dialog report
##


tell application "System Events" to set parent_name to name of current application
set Script_Title to (get name of me)
set Result_DocName to "CO_Image_Sorting_Report.txt"

set loqResultMethod to L's InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

set {minCOPversion, maxCOPversion} to {"12", "12"}
tell C's validateCOP5(minCOPversion, maxCOPversion)
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {theAppName, copVersion} to {its theAppName, its copVersion}
end tell

tell application "Capture One 12" to set COPDocRef to get current document
tell C's validateCOPdoc5(COPDocRef, {"Catalog"})
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
end tell

tell C's validateCOPcollections5(COPDocRef) to set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
   {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}

tell application "Capture One 12" to set countSelectedVariants to get count of selected variants
tell application "Capture One 12" to set countEveryVariant to count of variants

set startSortRule to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"
set endSortRule to "</Criterion></Condition></MatchOperator></MatchOperator>"
set midSortRule to "</Criterion></Condition></MatchOperator><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"

set smartSortRule to startSortRule & sortedColorTag1 & midSortRule & sortedColorTag2 & endSortRule

if enableCreateCollections then
   set {theStacksRef, foundStacks, refStacksParent} to findCollection(selectedCollectionRef, nameStackGroup, "group", true, true, {"group", "project"}, "")
   if not foundStacks then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameStackGroup & "\" group")
end if

if enableCreateUnsorted then
   set {theUnsortedRef, foundUnsorted, refUnsortedParent} to findCollection(selectedCollectionRef, nameUnsortedAlbum, "smart album", true, true, {"group", "project"}, smartSortRule)
   if not foundUnsorted then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameUnsortedAlbum & "\" smart album")
end if

tell application "Capture One 12" to tell (variants whose selected is true) to set {theVariantsRef, theVariantsName} to {it, its name}
if (0 = (count of theVariantsName)) then
   L's loqThis(-1, true, "No variants selected")
else
   
   if enableSeqImageName then
      set numdigits to -1
      repeat with aName in theVariantsName
         set thisCtr to findBaseName(aName, imageNameParsingMethod)
         set {thisLen, thisCtr} to {(count of thisCtr), (thisCtr as integer)}
         if -1 = numdigits then
            set {numdigits, minCtr, maxCtr} to {thisLen, (0 + (thisCtr as integer)), (0 + (thisCtr as integer))}
         else
            if thisLen > numdigits then set numdigits to thisLen
            if thisCtr < minCtr then set minCtr to thisCtr
            if thisCtr > maxCtr then set maxCtr to thisCtr
         end if
      end repeat
      set padZeros to U's makeList(numdigits, 0) as string
      set sequenceName to seqBaseImageName & (text (-numdigits) thru -1 of (padZeros & minCtr))
      if maxCtr > minCtr then set sequenceName to sequenceName & "-" & (text (-numdigits) thru -1 of (padZeros & maxCtr))
   else
      set padZeros to U's makeList(seqCountDigits, 0) as string
      set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & sequenceCount))
      set sequenceCount to sequenceCount + 1
      
      if enableCreateCollections then
         tell application "Capture One 12" to tell theStacksRef to set seqAlbumExists to (0 < (count of (every collection whose name begins with sequenceName)))
         if seqAlbumExists then
            tell application "Capture One 12" to tell theStacksRef to set seqAlbumName_L to name of every collection whose name begins with seqBaseCountedName
            set {ptrFirstNum, maxAlbumCount} to {(1 + (count of seqBaseCountedName)), 1}
            repeat with aName in seqAlbumName_L
               set {aNamesId_L, ptrChar, lenName} to {(id of aName), (ptrFirstNum + 0), (length of aName)}
               repeat while (48 ≤ aNamesId_L's item ptrChar) and (57 ≥ aNamesId_L's item ptrChar)
                  set ptrChar to ptrChar + 1
                  if ptrChar > lenName then exit repeat
               end repeat
               set ptrChar to ptrChar - 1 -- now ptrChar points to the last number character
               if ((ptrChar - ptrFirstNum + 1) ≤ seqCountDigits) and ((ptrChar - ptrFirstNum) > 0) then
                  set albumsCount to (string id (items ptrFirstNum thru ptrChar of aNamesId_L)) as integer
                  if albumsCount > maxAlbumCount then set maxAlbumCount to albumsCount
               end if
            end repeat
            set sequenceCount to maxAlbumCount + 2
            set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & (maxAlbumCount + 1)))
         end if
      end if
   end if
   
   set userCanceled to false
   if enableSequenceNameEntry then
      try
         set dialogResult to display dialog ¬
            "Sequence Name" buttons {"Cancel", "OK"} ¬
            default button "OK" cancel button "Cancel" default answer sequenceName
      on error number -128
         set userCanceled to true
      end try
      set sequenceName to dialogResult's text returned
   end if
   
   if not userCanceled then
      set gotSequenceAlbum to false
      if enableCreateCollections then
         set {refSequenceAlbum, gotSequenceAlbum, refSeqParent} to findCollection(theStacksRef, sequenceName, "album", true, false, {"group"}, "")
         if gotSequenceAlbum then
            tell application "Capture One 12" to add inside refSequenceAlbum variants theVariantsRef
            L's loqThis(-1, false, "Copied " & (count of theVariantsRef) & " variants to  \"" & sequenceName & "\"")
         else
            error L's loqqed_Error_Halt5("Can't create the \"" & sequenceName & "\" album")
         end if
      end if
      if enableSeqInMetaData then
         tell application "Capture One 12"
            if enableMetaDataMultiSeq then
               set oneByOne_L to get variants whose (image scenes is not "") and (selected is true)
               tell (variants whose selected is true and image scenes is "") to set its image scenes to sequenceName
               
               repeat with aVariant in oneByOne_L
                  tell aVariant
                     set the aVarSeq_L to its image scenes
                     if aVarSeq_L does not contain sequenceName then set its image scenes to (aVarSeq_L & "," & sequenceName)
                  end tell
               end repeat
            else
               tell (variants whose selected is true) to set its image scenes to sequenceName
            end if
            
         end tell
      end if
      try
         tell application "Capture One 12" to tell (variants whose selected is true) to set its color tag to sortedColorTag1 -- important to do this last to avoid the effect of color filtering
      on error
         if gotSequenceAlbum then
            tell application "Capture One 12" to tell refSequenceAlbum to tell its variants to set its color tag to sortedColorTag1
         else
            error L's loqqed_Error_Halt5("Unable to set the color tag because the variants have disappeared from the collection. Likely cause is a filter that has been set on the Image Scenes MetaData")
         end if
      end try
   end if
end if

################  The End ###############


on findCollection(thisCollRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler  to find and create a collection, starting from some arbitrary collection
   
   global debugLogEnable
   local thisCollKind, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, errorText, parentString, theParentRef, parentList, nextRef, refCtr, lastRef, hasFoundColl, cntColl, docMark, docName
   local saRules, saNoRules, debugText
   
   tell application "Capture One 12" to tell thisCollRef to set {thisCollKind, thisCollName, subCollNames, subCollRef, refParentColl} to {its kind, its name, name of its collections, its collections, it}
   set thisCollKind_S to C's convertKindList(thisCollKind)
   
   set debugText to kindTargetColl & " \"" & nameTargetColl & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
   if debugLogEnable then L's loqThis(2, false, return & "Find Collection: " & debugText & " with  Create: " & enableCreate & " Search: " & enableSearch & " Sort Rule: " & (count of saSortRule) & " characters")
   
   set isChevronForm to ("«" = text 1 of (thisCollKind as text))
   if debugLogEnable then
      L's loqThis(3, false, "SubCollections {" & U's joinListToString(subCollNames, "; ") & "}")
      L's loqThis(3, false, "Chevron Form: " & isChevronForm)
   end if
   
   set refFoundColl to {}
   if (kindParentColl_L contains thisCollKind_S) and (subCollNames contains nameTargetColl) then
      tell application "Capture One 12" to tell thisCollRef
         if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
         if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
         if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
         if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album))
      end tell
   end if
   
   set hasFoundColl to false
   if (1 = (count of refFoundColl)) then set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}
   
   if hasFoundColl then
      if debugLogEnable then L's loqThis(1, false, "Found " & debugText)
   else if (not enableCreate) and (not enableSearch) then
      L's loqThis(0, false, "The " & kindTargetColl & " \"" & nameTargetColl & "\" is missing from " & thisCollKind_S & " \"" & thisCollName & "\"")
   else
      if (not hasFoundColl) and enableCreate and (kindParentColl_L contains thisCollKind_S) then
         if debugLogEnable then L's loqThis(2, false, "Attempting to Create the collection")
         set collCreated to false
         tell application "Capture One 12" to tell thisCollRef
            set saNoRules to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>"
            if ("group" = kindTargetColl) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:group, name:nameTargetColl}}}
            if ("album" = kindTargetColl) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:album, name:nameTargetColl}}}
            if ("smart album" = kindTargetColl) then
               set saNoRules to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>"
               try
                  if (text 1 thru 10 of saNoRules) = (text 1 thru 10 of saSortRule) then set saRules to saSortRule
               on error
                  set saRules to saNoRules
               end try
               set ctrSaName to 1
               repeat until ({} = (every collection whose name is nameTargetColl and kind is album))
                  set {nameTargetColl, ctrSaName} to {(nameTargetColl & ctrSaName), ctrSaName + 1}
                  if 10 < ctrSaName then error
               end repeat
               set refFoundColl to (every collection whose name is nameTargetColl and kind is smart album)
               if (0 = (count of refFoundColl)) then set {collCreated, refFoundColl} to {true, {make new collection with properties {kind:smart album, name:nameTargetColl, rules:saRules}}}
               if (1 < ctrSaName) then set debugText to kindTargetColl & " \"" & nameTargetColl & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
            end if
            try
               if ("project" = kindTargetColl) then set refFoundColl to {make new collection with properties {kind:project, name:nameTargetColl}}
               set collCreated to true
            on error errorText
               if debugLogEnable then L's loqThis(2, false, "Creation failed with: " & errorText)
            end try
         end tell
         if (1 = (count of refFoundColl)) then
            set {refFoundColl, hasFoundColl} to {(item 1 of refFoundColl), true}
            if collCreated then L's loqThis(1, false, "Created " & debugText)
         end if
      end if
      
      if (not hasFoundColl) and enableSearch then
         if debugLogEnable then L's loqThis(2, false, "Starting Search")
         try
            get || of {thisCollRef}
         on error errorText
         end try
         if isChevronForm then set errorText to U's replaceText(errorText, "«class COcl»", "collection")
         set parentList to U's splitStringToList(errorText, {"of", "{", "}"})
         repeat with refCtr from (count of parentList) to 0 by -1
            try
               if "document" = first word of item refCtr of parentList then exit repeat
            end try
         end repeat
         if 0 = (get (contents of refCtr) as integer) then error L's loqqed_Error_Halt5("Didn't find the document reference in " & errorText)
         set docMark to contents of refCtr
         set docName to U's removeLeadingTrailingSpaces((get item 2 of U's splitStringToList((U's removeLeadingTrailingSpaces((get item docMark of parentList))), "\"")))
         tell application "Capture One 12" to set lastRef to document docName
         set cntColl to 0
         repeat with refCtr from 3 to docMark - 1
            try
               if "collection" = (first word of item refCtr of parentList) then
                  set cntColl to cntColl + 1
                  if 2 ≤ cntColl then
                     tell application "Capture One 12" to set nextRef to collection id (get last word of parentList's item refCtr) of lastRef
                     copy nextRef to lastRef
                  end if
               end if
            end try
         end repeat
         if (0 = cntColl) then
            L's loqThis(0, false, "Unable to identify parent collection")
         else
            set {refFoundColl, hasFoundColl, refParentColl} to my findCollection(lastRef, nameTargetColl, kindTargetColl, enableCreate, enableSearch, kindParentColl_L, saSortRule)
         end if
      end if
   end if
   
   if not hasFoundColl then set refFoundColl to missing value
   return {refFoundColl, hasFoundColl, refParentColl}
end findCollection


on findBaseName(theString, theMethod_L)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler to parse an image file name
   
   ## Looks long, but execution time is about 20 - 80  microseconds
   ## theMethod_L is a list of Methods. Each Method describes a parsing method (a particular way of parsing a string)
   ## Each Method is a list of Groups, each Group is a record that describes how to find the end of a group of characters.
   ## While a group runs each character is copied from theString to the output until the end is found.
   ## The algorithm starts with Group 1 of Method 1. When a Group ends the next one starts
   ## When there are no Groups left or no Characters left,  the algorithm stops reading characters and returns the output characters.
   ## The Group record: {inpAlp, inpNum, inpSpa, inpSym, inpCnt, inpBck, inpAct}:
   ##  (inpAlp, inpNum, inpSpa, inpSym, inpBck) boolean,  (inpCnt)  integer,    inpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
   #3 Character Type Tests are evaluated first, then Actions
   ## A group ends upon finding Alphabetic characters (if inpAlp true), or Numbers (if inpNum true), or a Space(if inpSpa true),
   ## or Symbols (if inpSym true), or finding more than inpCnt characters (if inpCnt >0).
   ##  If inpBck is true, when a group ends, the last character becomes part of the next group
   ## EG Group3: {inpAlp:false, inpNum:false, inpSpa:false, inpSym:true, inpBck:true, inpCnt:9, inpAct:aList}
   ## Means:  stop if a symbol is found or after the 9th character, and the last character becomes the first character of the next group
   ## Thus up to 9 Numbers, Spaces and Alphabetic characters are accepted, and 9th character or symbol is part of the next group.
   ## if Group 3 is the last Group, then  9th or symbol character (and all subsequent characters) are dropped.
   
   ## inpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character.  inpAct= {} results in no actions
   ##     X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
   ##     X= return triggers the action on a Group Increment from Character Type Tests
   ## Actions (1):  T-Translate Character to "Z" ; G- Switch to Group Z ; M- Switch to Method Z ; I- Increment group by Z;
   ##     B- Drop Back 1 character; J- Jump ahead Z characters, D- Drop trigger Characters, O - Drop Output
   ##    Actions A,B,C,N,S and P set the value of inpAlp, inpBck, inpCnt, inpNum, inpSpa and inpSym
   ## If the Alist has no "z" value: for "I", z is taken as 1; for "J", z is taken as the length of the trigger; for "T" z is taken as "";
   ##    for G, z is taken as "0"; for M, the method is incremented, output and trigger are dropped
   ##    for all other commands z is taken as true when missing
   ## Selecting Group < 1 resets the parser to Group 1 and clears all output. If inpBck is true, the current character is dropped.
   ## Selecting Method 0 triggers a Method reset, output and input are reset, and then the previous method is incremented
   ## Switching to Method whose number does not exist causes the previous output to be cleared, the remaining input characters are accepted
   ## E.G. For inpAct:{{"$","M"},{"-","T"}} in Method 1 --> when an "$" is encountered, start over again with Group 1 of Method 2, also drop every "-"
   ## If there is no Method 2, all remaining characters of the input are taken as output.
   
   global debugLogEnable
   local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, iterationCtr, SafetyLimit
   local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, AlistTriggerId_L, anAlist, theAction, Prefix
   local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, countChars, stringCount
   local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newCharIDs, hasNewChars, nextGroup, nextMethod, nextCharPointer
   local triggerBack1Char, hasPar3, valuePar3, endTriggerPointer, lenActTrigger, actionString, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop, lenActTrigger
   
   
   set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList} to ¬
      {(get id of theString), (get count of theString), 1, (count of lists of theMethod_L), true, {}}
   set {charPointer, iterationCtr, SafetyLimit} to {1, 0, (MethodCount * stringCount * 4)}
   if debugLogEnable then
      L's loqThis(2, false, "Parsing \"" & theString & "\"")
      if (0 < (count of strings of theMethod_L)) then L's loqThis(2, false, ("Using Method " & (string 1 of theMethod_L)))
      L's loqThis(3, false, {"Initialising: stringCount " & stringCount & ",  Method " & nextMethod & ",  MethodCount " & MethodCount})
   end if
   
   repeat while (charPointer ≤ stringCount) and (iterationCtr < SafetyLimit)
      set iterationCtr to iterationCtr + 1
      
      if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, thisMethod} to ¬
         {1, (count of records of theMethod_L's list nextMethod), true, nextMethod}
      if debugLogEnable and triggerMethodInit then L's loqThis(3, false, "Start Method #" & nextMethod & ", Group Count: " & (count of theMethod_L's list nextMethod))
      
      if triggerGroupInit then
         tell theMethod_L's list thisMethod's record nextGroup to set {Prefix, Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, countChars, thisGroup} to ¬
            {its inpPref, its inpAlp, its inpNum, its inpSpa, its inpSym, its inpCnt, (0 < its inpCnt), its inpBck, its inpAct, 0, nextGroup}
         if debugLogEnable then L's loqThis(3, false, "Start Group #" & thisGroup & " ,  Prefix \"" & Prefix & "\",  Alpha " & Alpha & ",  Number " & Numb & ",  Blank " & Blank & ",  Symbol " & Symbol & ",  Max Count " & MaxCnt & ",  has Max Count " & hasMaxCnt & ",  Drop Back Last Character " & backOneOnGrpInc & " ,  ActionList : " & (count of ActionList) & " action lists")
         if (0 < (count of ActionList)) and ("list" ≠ ((class of ActionList's item 1) as text)) then error "Group " & thisGroup & " has an incorrectly formatted Action List"
         set parsedNumList to parsedNumList & (id of Prefix)
      end if
      
      set {triggerBack1Char, triggerGroupInc, triggerGroupInit, triggerMethodInit, validChar, newCharIDs, hasNewChars, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop} to ¬
         {false, false, false, false, true, {}, false, false, missing value, 0, false, false}
      
      set {thisCharsId, countChars, nextCharPointer, lenActTrigger} to {(theCharIDList's item charPointer), (countChars + 1), charPointer, 1}
      if debugLogEnable then L's loqThis(5, false, "Character " & charPointer & " : \"" & (string id (get theCharIDList's item charPointer)) & "\"")
      
      if hasMaxCnt and (countChars ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
         set triggerGroupInc to true
         if debugLogEnable then L's loqThis(3, false, "Count triggers Group Increment")
      else --  check the character type
         if debugLogEnable then L's loqThis(3, false, "Character Type Check")
         if ((thisCharsId ≥ 65) and (thisCharsId ≤ 90)) or ((thisCharsId ≥ 97) and (thisCharsId ≤ 122)) or (thisCharsId ≥ 192) then -- a latin character
            if Alpha then set triggerGroupInc to true
            if debugLogEnable and Alpha then L's loqThis(4, false, "Alpha triggers Group Increment")
         else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
            if Numb then set triggerGroupInc to true
            if debugLogEnable and Numb then L's loqThis(4, false, "Number triggers Group Increment")
         else if thisCharsId = 32 then --- its a space
            if Blank then set triggerGroupInc to true
            if debugLogEnable and Blank then L's loqThis(4, false, "Space triggers Group Increment")
         else if thisCharsId > 32 then -- its a symbol
            if Symbol then set triggerGroupInc to true
            if debugLogEnable and Symbol then L's loqThis(4, false, "Symbol triggers Group Increment")
         end if
      end if
      
      repeat with anAlist in ActionList
         set {AlistTriggerId_L, endTriggerPointer} to {((id of contents of anAlist's item 1) as list), (charPointer + (count of anAlist's item 1) - 1)}
         
         if (endTriggerPointer ≤ stringCount) and ((not hasActionTrigger) or (theActionTrigger = AlistTriggerId_L)) and ¬
            ((triggerGroupInc and ({13} = AlistTriggerId_L)) or (AlistTriggerId_L = (theCharIDList's items charPointer thru endTriggerPointer))) ¬
               then
            
            if debugLogEnable and not hasActionTrigger then L's loqThis(4, false, "Action List is triggered by \"" & (anAlist's item 1) & "\"")
            if not hasActionTrigger then set {hasActionTrigger, theActionTrigger, lenActTrigger} to {true, AlistTriggerId_L, (count of AlistTriggerId_L)}
            
            set {theAction, hasPar3, valuePar3} to {(item 2 of anAlist), (3 ≤ (count of anAlist)), true}
            if hasPar3 then set valuePar3 to (contents of item 3 of anAlist)
            
            if debugLogEnable then
               if hasPar3 then
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\" ; \"" & (contents of item 3 of anAlist) & "\""
               else
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\""
               end if
            end if
            
            ## Group 1 - Character Control -  Does Not clear triggerGroupInc
            
            if ("A" = theAction) then
               set Alpha to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpAlp to " & valuePar3)
               
            else if "B" = theAction then --
               set backOneOnGrpInc to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpBck to " & valuePar3)
            else if ("C" = theAction) then
               if (not hasPar3) or (0 = valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {0, false}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to 0")
               else if (0 < valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {valuePar3, true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to " & valuePar3)
               else if (0 > valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {(MaxCnt - valuePar3), true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Increment inpCnt by " & (-valuePar3))
               end if
               
            else if ("N" = theAction) then
               if hasPar3 then set Numb to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpNum to " & valuePar3)
               
            else if ("P" = theAction) then
               set Symbol to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & "- Set inpSym to " & valuePar3)
            else if ("Q" = theAction) then
               if debugLogEnable then L's loqThis(4, false, actionString & "- Quit Actions")
               exit repeat
               
            else if "S" = theAction then
               set Blank to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpSpa to " & valuePar3)
               
               ## Group 2 - Character Control -  clears triggerGroupInc on multi character trigger
               
            else if "D" = theAction then --
               set validChar to not valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Action Trigger: " & valuePar3)
               
            else if "O" = theAction then --
               set triggerOutputDrop to valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Output characters")
               
            else if "J" = theAction then --
               if not hasPar3 then set valuePar3 to lenActTrigger
               set {nextCharJump, hasJump} to {valuePar3, true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Jump forward " & valuePar3 & " Characters")
               
            else if "T" = theAction then -- translate/drop this character
               if not hasPar3 then set valuePar3 to ""
               set {newCharIDs, hasNewChars} to {(id of valuePar3), true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Translate characters to \"" & valuePar3 & "\"")
               
               ## Group 3 - Group & Method control, clears triggerGroupInc - later
            else if "G" = theAction then -- new group
               if not hasPar3 then set valuePar3 to 0
               set {nextGroup, triggerGroupInit} to {valuePar3, true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Group " & valuePar3)
               
            else if "I" = theAction then -- increment group
               if not hasPar3 then set valuePar3 to 1
               set {nextGroup, triggerGroupInit} to {(thisGroup + valuePar3), true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Increment Group to " & nextGroup)
               
            else if "M" = theAction then -- set method
               if not hasPar3 then set valuePar3 to (thisMethod + 1)
               set {nextMethod, triggerMethodInit, triggerOutputDrop, validChar} to {valuePar3, true, true, false}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Method " & valuePar3 & " and drop output and trigger")
               
            else
               if debugLogEnable then L's loqThis(3, false, actionString & " - Command not found ")
            end if
         end if
      end repeat
      
      if (lenActTrigger > 1) then set countChars to countChars - 1 + lenActTrigger
      if hasMaxCnt and (countChars ≥ MaxCnt) then set {triggerGroupInc} to {true, L's loqThis(3, false, "Character count triggers Group Increment")}
      
      if (triggerGroupInit or triggerMethodInit) then set triggerGroupInc to false
      if (triggerGroupInc or triggerGroupInit or triggerMethodInit) and backOneOnGrpInc then set triggerBack1Char to true
      
      if (0 ≥ nextMethod) then
         if debugLogEnable then L's loqThis(3, false, "Executing Method Reset - Increment Method, Clear output, Reset Input Queue")
         set {parsedNumList, nextMethod, triggerMethodInit, nextCharPointer} to {{}, (thisMethod + 1), true, 1, false}
         set {triggerBack1Char, triggerGroupInc, hasJump, hasNewChars, hasMaxCnt} to {false, false, false, false, false}
      else
         if (0 ≥ nextGroup) then
            set {triggerBack1Char, validChar, triggerGroupInc, hasJump, hasNewChars} to {false, false, false, false, false}
            if debugLogEnable then L's loqThis(3, false, "Executing Group Reset - Switch to Group 1, Clear output , dropping back " & (-nextGroup) & " characters")
            if (-1 ≥ nextGroup) then set {nextCharJump, hasJump} to {(nextGroup), true}
            set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
         end if
         
         if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true, L's loqThis(3, false, "Executing Group Increment - Switch to Group " & (thisGroup + 1))}
         
         if debugLogEnable and triggerOutputDrop then L's loqThis(4, false, "Dropped Output")
         if triggerOutputDrop then set parsedNumList to {}
         if triggerBack1Char then
            set {validChar, nextCharPointer} to {false, charPointer}
            if debugLogEnable then L's loqThis(3, false, "Drop Back 1 Character")
         else if (hasJump or hasNewChars) then
            set validChar to false
            if (hasJump and (((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or (0 < nextCharJump)))) then
               set nextCharPointer to (charPointer + nextCharJump)
               if nextCharPointer < 1 then set nextCharPointer to 1
               if hasNewChars and (0 < nextCharJump) and (lenActTrigger > nextCharJump) then set nextCharPointer to (charPointer + lenActTrigger)
            else if hasJump then
               ## do nothing - can't move charPointer back
               if debugLogEnable then L's loqThis(3, false, "Unable to move back " & (-nextCharJump) & " characters - infinite loop")
            else if hasNewChars then -- and not hasJump
               set nextCharPointer to (charPointer + lenActTrigger)
            end if
            if debugLogEnable then L's loqThis(3, false, "Input Moved Forward " & (nextCharPointer - charPointer) & " characters")
         else
            set nextCharPointer to (charPointer + 1)
            if debugLogEnable then L's loqThis(5, false, "Input Moved Forward 1 character")
         end if
         if hasNewChars then set {parsedNumList} to {(parsedNumList & newCharIDs), L's loqThis(4, false, "Added new Characters to the output")}
         if validChar then set end of parsedNumList to thisCharsId
         if debugLogEnable and not validChar then L's loqThis(3, false, "The input character has been dropped")
      end if
      if debugLogEnable then L's loqThis(3, false, "The output is: \"" & (string id parsedNumList) & "\"")
      
      if nextMethod > MethodCount then -- replace the output with  every remaining character
         if debugLogEnable then L's loqThis(3, false, "Got Method " & nextMethod & "  Return remaining characters")
         if charPointer > 1 then set parsedNumList to (theCharIDList's items charPointer thru stringCount)
         if charPointer ≤ 1 then copy theCharIDList to parsedNumList
         exit repeat
      else if (nextGroup > GroupCount) then
         if debugLogEnable then L's loqThis(3, false, "Finished all groups, exitting")
         exit repeat
      end if
      set charPointer to nextCharPointer -- must be last statement in the the loop
   end repeat
   if debugLogEnable and (charPointer > stringCount) then L's loqThis(3, false, "Finished all characters")
   
   return (string id parsedNumList)
end findBaseName

##########################################
## Capture One General Handlers  Version 2019/03/11

on validateCOP5(minCOPversionstr, maxCOPversionstr)
   ## Copyright 2019 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 COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
   local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
   local theAppName, copVersion
   
   tell application "System Events"
      if debugLogEnable then
         L's loqThis(2, false, ("COP Processes:" & (get U's joinListToString((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
         L's loqThis(3, false, ("All Processes: " & (get U's joinListToString((get name of every process whose background only is false), ", "))))
      end if
      set COPProcList to every process whose name begins with "Capture One" and background only is false
      if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("COP is not running"))}
      if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
      set theAppRef to item 1 of COPProcList
      set theAppName to ((get name of theAppRef) as text)
      set copDetailedVersion to get version of my application theAppName
   end tell
   
   tell application "Capture One 12" to set copVersionStr to (get app version)
   set copVersion to (word -1 of copVersionStr)
   
   if debugLogEnable then
      L's loqThis(2, false, ("theAppName: " & theAppName))
      L's loqThis(1, false, copVersionStr)
      L's loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
   end if
   tell U's compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
   if not minVersionPass then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5(("This Script does not support Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
   if not maxVersionPass then L's loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
   return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
end validateCOP5

on validateCOPdoc5(theDocRef, validDocKindList)
   ## Copyright 2019 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 12" to set theDocRef to get current document
   
   try
      tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   on error errorText number errorNumber
      return {hasErrors:true, errorText:(get L's 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 L's 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 validateCOPcollections5(theDocRef)
   ## Copyright 2019 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, and the top level collections
   global debugLogEnable
   local namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection
   
   tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   set COPDocKind_s to convertKindList(COPDocKind_p)
   
   tell application "Capture One 12" to tell theDocRef
      set selectedCollectionRef to get current collection
      if (missing value = selectedCollectionRef) then
         try
            set current collection to collection "All Images"
         on error
            set current collection to last collection
         end try
         set selectedCollectionRef to get current collection
      end if
      tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
      set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
   end tell
   set countTopCollections to count of namesTopCollections
   
   repeat with collectionCounter from 1 to countTopCollections
      if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
         (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
         set selectedCollectionIndex to collectionCounter
         exit repeat
      end if
   end repeat
   
   local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections
   
   if COPDocKind_s = "catalog" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter - 1
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set selectedCollectionMirroredAtTopLast to ¬
         (selectedCollectionIndex = countTopCollections) and ¬
         ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)
      
      set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}
      
   else if COPDocKind_s = "session" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("favorite" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set countFavoriteCollections to countTopCollections - topUserCollectionIndex
      if 0 = countFavoriteCollections then
         set namesFavoriteCollections to {}
      else
         set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
      end if
      
      set selectedCollectionMirroredAtTopLast to false
   end if
   
   local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
   set selectedCollectionIsUser to ¬
      (selectedCollectionMirroredAtTopLast or ¬
         ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))
   
   if topUserCollectionIndex < bottomUserCollectionIndex then
      set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
      set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
   else
      set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
      set countTopUserCollections to count of namesTopUserCollections
   end if
   
   return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}
   
end validateCOPcollections5

on convertKindList(kind_list)
   ## Copyright 2019 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_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type
   
   set kind_s_list to {}
   set input_is_list to ("list" = (get (class of kind_list) as text))
   if input_is_list then
      if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      repeat with theItem in kind_list
         tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
      end repeat
      if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
   else
      if ("text" = (get (class of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
      if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
      set kind_s_list to {kind_s1}
   end if
   
   set fail_flag to false
   set code_start to -5
   
   set kind_list to {}
   repeat with Kind_s_item in kind_s_list
      if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
      set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
      set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)
      
      if kind_type = "CC" then ## Collection Kinds
         if kind_code = "CCpj" then
            set the end of kind_list to "project"
         else if kind_code = "CCgp" then
            set the end of kind_list to "group"
         else if kind_code = "CCal" then
            set the end of kind_list to "album"
         else if kind_code = "CCsm" then
            set the end of kind_list to "smart album"
         else if kind_code = "CCfv" then
            set the end of kind_list to "favorite"
         else if kind_code = "CCff" then
            set the end of kind_list to "catalog folder"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CL" then ## Layer Kinds
         if kind_code = "CLbg" then
            set the end of kind_list to "background"
         else if kind_code = "CLnm" then
            set the end of kind_list to "adjustment"
         else if kind_code = "CLcl" then
            set the end of kind_list to "clone"
         else if kind_code = "CLhl" then
            set the end of kind_list to "heal"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CR" then ## Watermark Kinds
         if kind_code = "CRWn" then
            set the end of kind_list to "none"
         else if kind_code = "CRWt" then
            set the end of kind_list to "textual"
         else if kind_code = "CRWi" then
            set the end of kind_list to "imagery"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CO" then ## Document Kinds
         if kind_code = "COct" then
            set the end of kind_list to "catalog"
         else if kind_code = "COsd" then
            set the end of kind_list to "session"
         else
            set fail_flag to true
         end if
      else
         set fail_flag to true
      end if
      
      if fail_flag then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
   end repeat
   
   if input_is_list then
      return kind_list
   else
      return item 1 of kind_list
   end if
   
end convertKindList
Cheers, Eric
[late 2015 iMac, 4GHz i7, 24GB RAM, external SSDs. GX8, E-M1, GX7, GM5, GM1 ....]
Eric Nepean
 
Posts: 445
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa

Bug Fix Re: SemiAutomatic Image Sequencer

Postby Eric Nepean » Sat Mar 23, 2019 9:52 pm

Bug Fix. Complete rewrite of the findCollection() handler.

This is the first part of the script, a complete replacement for the script from postings 1 or 3. The second part of the script has not changed.
Copy and paste the script below into Script Editor's script window, and then copy and paste the script from posting 2.

Code: Select all
## Applescript to sequence images in a COP 12 Catalog
## Version 12.23 !! NO GUARANTEE OF SUPPORT !!  Best effort
## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.

-- *** Use and Function
-- select a Project, an album or smart album which is under a Project or Group (or both)
--  Select some variants and run this script
-- If not already present, a group "Stacks" and a smart album "Unsorted" will be created
-- the smart album rules will hide all images with blue or magenta color tag
-- an album with a name starting with SEQ will be created in the group Stacks
-- the album name will contain the lowest and highest numbers sequences from the selected variants
-- the selected variants will be color tagged Blue and copied to the album
-- the Sequence name will be copied into the IPTC Image Scenes metadata of the Variants
-- Once "Unsorted" Exists, continue creating sequences from there, only the unsequenced images will be shown.
-- ***To Setu

-- 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 document
-- Open the Script Editor log window, and select the messages tab
-- 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

property M : missing value
set M to me
global C, L, U
set C to me
set L to me
set U to me

set loqGUIsettings_L to L's setupLoqqing5() -- setup the variables used for Loqqing system

## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
##   Default values

### Read Carefully
set enableSeqImageName to true --         enable image based sequence name; disabling results in counter based sequence names

set enableCreateCollections to true --         this enable creating of albums for each sequence, and creating of the "Stacks" group
set enableCreateUnsorted to true --         this enable creating of  of the "Unsorted" smart album
set nameStackGroup to "Stacks" --         this is the name of group that holds the albums for each sequence
set nameUnsortedAlbum to "Unsorted" --      this is the name of smart album that shows the not yet sequenced variants
set enableSequenceNameEntry to false --      enables manual entry of sequence names
set seqBaseImageName to "SEQ " --         the base used for image based sequence name
set seqBaseCountedName to "SEQ-" --      the base used for counter based sequence name
set seqCountDigits to 2 --               the number of counter digits in a counter based sequence name
property sequenceCount : 1 --            the starting count in a counter based sequence name
set enableSeqInMetaData to true --         shall the sequence name be written to the IPTC Image Scenes metadata
set enableMetaDataMultiSeq to true --      shall the sequence name be added to other sequences in the IPTC Image Scenes metadata

set Loqqing's debugLogLevel to 0 --                  0...6 Values >1 result in increasing amounts of debug data that takes longer to report
set Loqqing's enableResultsFile to true --               (true/false)
set Loqqing's enableResultsByClipboard to false --      (true/false)
set Loqqing's enableNotifications to true --               (true/false)  - enable notifications of errors and exceptions
set Loqqing's notificationsMaxDebug to 1 --            0...6  suggest not more than 1
set Loqqing's ResultsFileMaxDebug to 3 --            0...6  suggest not more than 2
set Loqqing's enableResultsByNotifications to false
set Loqqing's enableResultsByLoq to true


set sortedColorTag1 to 5 -- for the stacked images
set sortedColorTag2 to 6 -- for the picked stacked images
--Color Tag
--Criterion
--0--> none
--1-->red
--2-->orange
--3-->yellow
--4-->green
--5-->blue
--6-->magenta
--7-->purple


## ***** 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.

set inpGroup1 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them 
set inpGroup2 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character
set inpMethod1 to {"SOOC Parse", {inpGroup1, inpGroup2}} -- parses the image file names typically assigned by a camera

set inpGroup3 to {inpPref:"", inpAlp:false, inpNum:true, inpSpa:false, inpSym:false, inpBck:true, inpCnt:5, inpAct:{{return, "O"}, {"-", "M", 0}}} -- Take up to 5 alphabetic, symbol or space characters. Then discard them. Start over next method if a "-" is found.
set inpGroup4 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:0, inpAct:{{"-", "M", 0}}} -- Take the next number characters, stop on a Symbol or Space or alphabetic character. Start over next method if a "-" is found.

set inpGroup5 to {inpPref:"", inpAlp:false, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:4, inpAct:{{return, "O"}, {return, "D"}}} -- Take Alpha and Numbers, stop on Space or symbol or 4 characters. Then discard
set inpGroup6 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:false, inpCnt:3, inpAct:{{"$", "G"}, {return, "O"}, {return, "D"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 3 characters. Then discard. Start over if a "$" is found
set inpGroup7 to {inpPref:"", inpAlp:true, inpNum:false, inpSpa:true, inpSym:true, inpBck:true, inpCnt:5, inpAct:{{"$", "G"}}} -- Take Only Numbers, stop on Alpha or Space or Symbol or 5 characters. Start over if a "$" is found
set inpMethod2 to {"Universal Parse", {inpGroup3, inpGroup4}, {inpGroup5, inpGroup6, inpGroup7}} -- combination of Method 3 and Method 1
set inpMethod3 to {"Eric's Detailed Parse", {inpGroup5, inpGroup6, inpGroup7}} -- parses Eric's image file names

set imageNameParsingMethod to inpMethod1 -- OK to change this

set debugLogEnable to true

## Result reporting methods which are valid for this script
set Loqqing's gateResultsFile to true
set Loqqing's gateResultsByClipboard to true
set Loqqing's gateResultsNotification to true

## Reporting methods which are NOT valid for this script
-- disable the reporting method
set Loqqing's gateResultsDialog to false
-- disable user control
set Loqqing's enableResultsByDialog to false --         (true/false)
set Loqqing's maxDialogPercent to 85 --                   (0% to 100% of the monitor) The amount of data that triggers a dialog report
##


tell application "System Events" to set parent_name to name of current application
set Script_Title to (get name of me)
set Result_DocName to "CO_Image_Sorting_Report.txt"

set loqResultMethod to L's InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

set {minCOPversion, maxCOPversion} to {"12", "12"}
tell C's validateCOP5(minCOPversion, maxCOPversion)
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {theAppName, copVersion} to {its theAppName, its copVersion}
end tell

tell application "Capture One 12" to set COPDocRef to get current document
tell C's validateCOPdoc5(COPDocRef, {"Catalog"})
   if its hasErrors then error L's loqqed_Error_Halt5(get its errorText)
   set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
end tell

tell C's validateCOPcollections5(COPDocRef) to set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
   {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}

tell application "Capture One 12" to set countSelectedVariants to get count of selected variants
tell application "Capture One 12" to set countEveryVariant to count of variants

set startSortRule to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"
set endSortRule to "</Criterion></Condition></MatchOperator></MatchOperator>"
set midSortRule to "</Criterion></Condition></MatchOperator><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_BASIC_URGENCY</Key><Operator>1</Operator><Criterion>"

set smartSortRule to startSortRule & sortedColorTag1 & midSortRule & sortedColorTag2 & endSortRule

if enableCreateCollections then
   set {theStacksRef, foundStacks, refStacksParent} to findCollection({selectedCollectionRef}, nameStackGroup, "group", true, true, 1, {"group", "project"}, "")
   if not foundStacks then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameStackGroup & "\" group")
end if

if enableCreateUnsorted then
   set {theUnsortedRef, foundUnsorted, refUnsortedParent} to findCollection({selectedCollectionRef}, nameUnsortedAlbum, "smart album", true, true, 50, {"group", "project"}, smartSortRule)
   if not foundUnsorted then error L's loqqed_Error_Halt5("Can't find or create the \"" & nameUnsortedAlbum & "\" smart album")
end if

tell application "Capture One 12" to tell (variants whose selected is true) to set {theVariantsRef, theVariantsName} to {it, its name}
if (0 = (count of theVariantsName)) then
   L's loqThis(-1, true, "No variants selected")
else
   
   if enableSeqImageName then
      set numdigits to -1
      repeat with aName in theVariantsName
         set thisCtr to findBaseName(aName, imageNameParsingMethod)
         set {thisLen, thisCtr} to {(count of thisCtr), (thisCtr as integer)}
         if -1 = numdigits then
            set {numdigits, minCtr, maxCtr} to {thisLen, (0 + (thisCtr as integer)), (0 + (thisCtr as integer))}
         else
            if thisLen > numdigits then set numdigits to thisLen
            if thisCtr < minCtr then set minCtr to thisCtr
            if thisCtr > maxCtr then set maxCtr to thisCtr
         end if
      end repeat
      set padZeros to U's makeList(numdigits, 0) as string
      set sequenceName to seqBaseImageName & (text (-numdigits) thru -1 of (padZeros & minCtr))
      if maxCtr > minCtr then set sequenceName to sequenceName & "-" & (text (-numdigits) thru -1 of (padZeros & maxCtr))
   else
      set padZeros to U's makeList(seqCountDigits, 0) as string
      set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & sequenceCount))
      set sequenceCount to sequenceCount + 1
      
      if enableCreateCollections then
         tell application "Capture One 12" to tell theStacksRef to set seqAlbumExists to (0 < (count of (every collection whose name begins with sequenceName)))
         if seqAlbumExists then
            tell application "Capture One 12" to tell theStacksRef to set seqAlbumName_L to name of every collection whose name begins with seqBaseCountedName
            set {ptrFirstNum, maxAlbumCount} to {(1 + (count of seqBaseCountedName)), 1}
            repeat with aName in seqAlbumName_L
               set {aNamesId_L, ptrChar, lenName} to {(id of aName), (ptrFirstNum + 0), (length of aName)}
               repeat while (48 ≤ aNamesId_L's item ptrChar) and (57 ≥ aNamesId_L's item ptrChar)
                  set ptrChar to ptrChar + 1
                  if ptrChar > lenName then exit repeat
               end repeat
               set ptrChar to ptrChar - 1 -- now ptrChar points to the last number character
               if ((ptrChar - ptrFirstNum + 1) ≤ seqCountDigits) and ((ptrChar - ptrFirstNum) > 0) then
                  set albumsCount to (string id (items ptrFirstNum thru ptrChar of aNamesId_L)) as integer
                  if albumsCount > maxAlbumCount then set maxAlbumCount to albumsCount
               end if
            end repeat
            set sequenceCount to maxAlbumCount + 2
            set sequenceName to seqBaseCountedName & (text (-seqCountDigits) thru -1 of (padZeros & (maxAlbumCount + 1)))
         end if
      end if
   end if
   
   set userCanceled to false
   if enableSequenceNameEntry then
      try
         set dialogResult to display dialog ¬
            "Sequence Name" buttons {"Cancel", "OK"} ¬
            default button "OK" cancel button "Cancel" default answer sequenceName
      on error number -128
         set userCanceled to true
      end try
      set sequenceName to dialogResult's text returned
   end if
   
   if not userCanceled then
      set gotSequenceAlbum to false
      if enableCreateCollections then
         set {refSequenceAlbum, gotSequenceAlbum, refSeqParent} to findCollection({theStacksRef}, sequenceName, "album", true, false, 0, {"group"}, "")
         if gotSequenceAlbum then
            tell application "Capture One 12" to add inside refSequenceAlbum variants theVariantsRef
            L's loqThis(-1, false, "Copied " & (count of theVariantsRef) & " variants to  \"" & sequenceName & "\"")
         else
            error L's loqqed_Error_Halt5("Can't create the \"" & sequenceName & "\" album")
         end if
      end if
      if enableSeqInMetaData then
         tell application "Capture One 12"
            if enableMetaDataMultiSeq then
               set oneByOne_L to get variants whose (image scenes is not "") and (selected is true)
               tell (variants whose selected is true and image scenes is "") to set its image scenes to sequenceName
               
               repeat with aVariant in oneByOne_L
                  tell aVariant
                     set the aVarSeq_L to its image scenes
                     if aVarSeq_L does not contain sequenceName then set its image scenes to (aVarSeq_L & "," & sequenceName)
                  end tell
               end repeat
            else
               tell (variants whose selected is true) to set its image scenes to sequenceName
            end if
            
         end tell
      end if
      try
         tell application "Capture One 12" to tell (variants whose selected is true) to set its color tag to sortedColorTag1 -- important to do this last to avoid the effect of color filtering
      on error
         if gotSequenceAlbum then
            tell application "Capture One 12" to tell refSequenceAlbum to tell its variants to set its color tag to sortedColorTag1
         else
            error L's loqqed_Error_Halt5("Unable to set the color tag because the variants have disappeared from the collection. Likely cause is a filter that has been set on the Image Scenes MetaData")
         end if
      end try
   end if
end if

################  The End ###############

on findCollection(parentRefList, nameTargetColl, kindTargetColl, enableCreate, enableFindParents, MaxCount, kindParentColl_L, saSortRule)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler  to find and create a collection, starting from some arbitrary collection
   ## Version March 17 2019
   
   global debugLogEnable
   local refParentColl, ptrCollRef, thisCollRef, thisCollKind, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, hasFoundColl, saRules, debugText, collSearchFail
   
   copy (contents of item 1 of parentRefList) to thisCollRef
   if 1 < (count of parentRefList) then set enableFindParents to false
   
   set saRules to "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>" -- No rules string -- length 80, first part is 38 long
   if (80 < (get length of saSortRule)) and (text 1 thru 38 of saRules) = (text 1 thru 38 of saSortRule) then set saRules to saSortRule
   set {refFoundColl, hasFoundColl, collCreated, refParentColl} to {{}, false, false, missing value}
   
   repeat with ptrCollRef in parentRefList
      set thisCollRef to contents of ptrCollRef
      
      tell application "Capture One 12" to tell thisCollRef to set {thisCollKind, thisCollName, subCollNames, subCollRef, refParentColl} to {its kind, its name, name of its collections, its collections, it}
      set thisCollKind_S to C's convertKindList(thisCollKind)
      
      set debugText to kindTargetColl & " \"" & nameTargetColl & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
      if debugLogEnable then L's loqThis(2, false, return & "Find Collection: " & debugText & " with  Create: " & enableCreate & " Search: " & enableFindParents & " Sort Rule: " & (count of saSortRule) & " characters")
      if debugLogEnable then L's loqThis(3, false, "SubCollections {" & U's joinListToString(subCollNames, "; ") & "}")
      
      if (kindParentColl_L contains thisCollKind_S) and (subCollNames contains nameTargetColl) then
         tell application "Capture One 12" to tell thisCollRef
            if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
            if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
            if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
            if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album and rules is saRules))
         end tell
      end if
      
      if (1 ≠ (count of refFoundColl)) then
         if enableCreate and (kindParentColl_L contains thisCollKind_S) and ({"album", "smart album"} does not contain thisCollKind_S) then
            if debugLogEnable then L's loqThis(2, false, "Attempting to Create the collection")
            
            set collCreated to false
            tell application "Capture One 12" to tell thisCollRef
               set {ctrCollName, collSearchFail} to {1, false}
               copy (nameTargetColl as string) to nbrTargetCollName
               repeat until ({} = (every collection whose name is nbrTargetCollName)) or collSearchFail
                  set refFoundColl to {}
                  if ("project" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is project))
                  if ("group" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is group))
                  if ("album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is album))
                  if ("smart album" = kindTargetColl) then set refFoundColl to (refFoundColl & (every collection whose name is nameTargetColl and kind is smart album and rules is saRules))
                  if (1 = (count of refFoundColl)) then exit repeat -- found anumbered collection
                  set {nbrTargetCollName, ctrCollName} to {(nameTargetColl & ctrCollName), ctrCollName + 1}
                  if MaxCount < ctrCollName then set collSearchFail to true
               end repeat
               
               if not collSearchFail and (0 = (count of refFoundColl)) then
                  if ("group" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:group, name:nbrTargetCollName}}, true}
                  if ("album" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:album, name:nbrTargetCollName}}, true}
                  if ("project" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:project, name:nbrTargetCollName}}, true}
                  if ("smart album" = kindTargetColl) then set {refFoundColl, collCreated} to {{make new collection with properties {kind:smart album, name:nbrTargetCollName, rules:saRules}}, true}
                  if (1 < ctrCollName) then set debugText to kindTargetColl & " \"" & nbrTargetCollName & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
               end if
            end tell
         end if
      end if
      
      if (1 = (count of refFoundColl)) then
         set {refFoundColl, hasFoundColl, refParentColl} to {(item 1 of refFoundColl), true, thisCollRef}
         if collCreated then
            if debugLogEnable then L's loqThis(2, false, "Created " & debugText)
         else
            if debugLogEnable then L's loqThis(2, false, "Found " & debugText)
         end if
         exit repeat
      end if
   end repeat
   
   if (not hasFoundColl) and enableFindParents then set {refFoundColl, hasFoundColl, refParentColl} to my findCollection((my findParentColl(thisCollRef)), nameTargetColl, kindTargetColl, enableCreate, false, MaxCount, kindParentColl_L, saSortRule)
   
   if not hasFoundColl then L's loqThis(0, false, "The " & kindTargetColl & " \"" & nameTargetColl & "\" is missing")
   return {refFoundColl, hasFoundColl, refParentColl}
   
end findCollection

on findParentColl(thisCollRef)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler to find the parent of a collection
   ## March 23 2019
   global debugLogEnable
   local errorText, errorText1, parentStringList, refPtr, docPtr, docName, parentPtr, parentRefList, startPtr, stopPtr
   if debugLogEnable then L's loqThis(2, false, "Starting Search")
   try
      get || of {thisCollRef}
   on error errorText
      if debugLogEnable then L's loqThis(5, false, "Full error text \"" & errorText & "\"")
   end try
   ## Extract the string between "{" and "}"
   repeat with startPtr from 1 to count of errorText
      if "{" = text startPtr of errorText then exit repeat
   end repeat
   repeat with stopPtr from -1 to -(count of errorText) by -1
      if "}" = text stopPtr of errorText then exit repeat
   end repeat
   set errorText to U's removeLeadingTrailingSpaces((text (startPtr + 1) thru (stopPtr - 1) of errorText))
   ## if script runs as an application "collection" is replaced by "«class COcl»", fix that
   if "«class COcl»" = text 1 thru 12 of errorText then set errorText to U's replaceText(errorText, "«class COcl»", "collection")
   set parentStringList to U's splitStringToList(errorText, "of") -- make a list of references
   if debugLogEnable then L's loqThis(4, false, "Processed error text \"" & errorText & "\"")
   
   repeat with docPtr from (count of parentStringList) to 0 by -1
      try
         if "document" = first word of item docPtr of parentStringList then exit repeat
      end try
   end repeat
   set {docPtr, parentRefList} to {(docPtr + 0), {}}
   if 0 = docPtr then error L's loqqed_Error_Halt5("Didn't find \"document\" in the string \"" & errorText & "\"")
   set docName to U's removeLeadingTrailingSpaces((get item 2 of U's splitStringToList((U's removeLeadingTrailingSpaces((get item docPtr of parentStringList))), "\"")))
   tell application "Capture One 12" to copy (document docName) to beginning of parentRefList
   if debugLogEnable then L's loqThis(5, false, ("Found Document Reference \"" & (get parentStringList's item docPtr) & "\""))
   repeat with parentPtr from 1 to docPtr
      if "collection" = (first word of item parentPtr of parentStringList) then exit repeat
   end repeat
   set parentPtr to parentPtr + 1
   if (parentPtr > docPtr) then error L's loqqed_Error_Halt5("Unable to find starting collection")
   repeat with refPtr from docPtr - 1 to parentPtr by -1
      if debugLogEnable then L's loqThis(5, false, ("Collection Reference \"" & (get parentStringList's item refPtr) & "\""))
      tell application "Capture One 12" to tell (first item of parentRefList) to copy (collection id (get last word of parentStringList's item refPtr)) to beginning of parentRefList
   end repeat
   return parentRefList
end findParentColl


on findBaseName(theString, theMethod_L)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler to parse an image file name
   
   ## Looks long, but execution time is about 20 - 80  microseconds
   ## theMethod_L is a list of Methods. Each Method describes a parsing method (a particular way of parsing a string)
   ## Each Method is a list of Groups, each Group is a record that describes how to find the end of a group of characters.
   ## While a group runs each character is copied from theString to the output until the end is found.
   ## The algorithm starts with Group 1 of Method 1. When a Group ends the next one starts
   ## When there are no Groups left or no Characters left,  the algorithm stops reading characters and returns the output characters.
   ## The Group record: {inpAlp, inpNum, inpSpa, inpSym, inpCnt, inpBck, inpAct}:
   ##  (inpAlp, inpNum, inpSpa, inpSym, inpBck) boolean,  (inpCnt)  integer,    inpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
   #3 Character Type Tests are evaluated first, then Actions
   ## A group ends upon finding Alphabetic characters (if inpAlp true), or Numbers (if inpNum true), or a Space(if inpSpa true),
   ## or Symbols (if inpSym true), or finding more than inpCnt characters (if inpCnt >0).
   ##  If inpBck is true, when a group ends, the last character becomes part of the next group
   ## EG Group3: {inpAlp:false, inpNum:false, inpSpa:false, inpSym:true, inpBck:true, inpCnt:9, inpAct:aList}
   ## Means:  stop if a symbol is found or after the 9th character, and the last character becomes the first character of the next group
   ## Thus up to 9 Numbers, Spaces and Alphabetic characters are accepted, and 9th character or symbol is part of the next group.
   ## if Group 3 is the last Group, then  9th or symbol character (and all subsequent characters) are dropped.
   
   ## inpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character.  inpAct= {} results in no actions
   ##     X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
   ##     X= return triggers the action on a Group Increment from Character Type Tests
   ## Actions (1):  T-Translate Character to "Z" ; G- Switch to Group Z ; M- Switch to Method Z ; I- Increment group by Z;
   ##     B- Drop Back 1 character; J- Jump ahead Z characters, D- Drop trigger Characters, O - Drop Output
   ##    Actions A,B,C,N,S and P set the value of inpAlp, inpBck, inpCnt, inpNum, inpSpa and inpSym
   ## If the Alist has no "z" value: for "I", z is taken as 1; for "J", z is taken as the length of the trigger; for "T" z is taken as "";
   ##    for G, z is taken as "0"; for M, the method is incremented, output and trigger are dropped
   ##    for all other commands z is taken as true when missing
   ## Selecting Group < 1 resets the parser to Group 1 and clears all output. If inpBck is true, the current character is dropped.
   ## Selecting Method 0 triggers a Method reset, output and input are reset, and then the previous method is incremented
   ## Switching to Method whose number does not exist causes the previous output to be cleared, the remaining input characters are accepted
   ## E.G. For inpAct:{{"$","M"},{"-","T"}} in Method 1 --> when an "$" is encountered, start over again with Group 1 of Method 2, also drop every "-"
   ## If there is no Method 2, all remaining characters of the input are taken as output.
   
   global debugLogEnable
   local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, iterationCtr, SafetyLimit
   local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, AlistTriggerId_L, anAlist, theAction, Prefix
   local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, countChars, stringCount
   local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newCharIDs, hasNewChars, nextGroup, nextMethod, nextCharPointer
   local triggerBack1Char, hasPar3, valuePar3, endTriggerPointer, lenActTrigger, actionString, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop, lenActTrigger
   
   
   set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList} to ¬
      {(get id of theString), (get count of theString), 1, (count of lists of theMethod_L), true, {}}
   set {charPointer, iterationCtr, SafetyLimit} to {1, 0, (MethodCount * stringCount * 4)}
   if debugLogEnable then
      L's loqThis(2, false, "Parsing \"" & theString & "\"")
      if (0 < (count of strings of theMethod_L)) then L's loqThis(2, false, ("Using Method " & (string 1 of theMethod_L)))
      L's loqThis(3, false, {"Initialising: stringCount " & stringCount & ",  Method " & nextMethod & ",  MethodCount " & MethodCount})
   end if
   
   repeat while (charPointer ≤ stringCount) and (iterationCtr < SafetyLimit)
      set iterationCtr to iterationCtr + 1
      
      if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, thisMethod} to ¬
         {1, (count of records of theMethod_L's list nextMethod), true, nextMethod}
      if debugLogEnable and triggerMethodInit then L's loqThis(3, false, "Start Method #" & nextMethod & ", Group Count: " & (count of theMethod_L's list nextMethod))
      
      if triggerGroupInit then
         tell theMethod_L's list thisMethod's record nextGroup to set {Prefix, Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, countChars, thisGroup} to ¬
            {its inpPref, its inpAlp, its inpNum, its inpSpa, its inpSym, its inpCnt, (0 < its inpCnt), its inpBck, its inpAct, 0, nextGroup}
         if debugLogEnable then L's loqThis(3, false, "Start Group #" & thisGroup & " ,  Prefix \"" & Prefix & "\",  Alpha " & Alpha & ",  Number " & Numb & ",  Blank " & Blank & ",  Symbol " & Symbol & ",  Max Count " & MaxCnt & ",  has Max Count " & hasMaxCnt & ",  Drop Back Last Character " & backOneOnGrpInc & " ,  ActionList : " & (count of ActionList) & " action lists")
         if (0 < (count of ActionList)) and ("list" ≠ ((class of ActionList's item 1) as text)) then error "Group " & thisGroup & " has an incorrectly formatted Action List"
         set parsedNumList to parsedNumList & (id of Prefix)
      end if
      
      set {triggerBack1Char, triggerGroupInc, triggerGroupInit, triggerMethodInit, validChar, newCharIDs, hasNewChars, hasActionTrigger, theActionTrigger, nextCharJump, hasJump, triggerOutputDrop} to ¬
         {false, false, false, false, true, {}, false, false, missing value, 0, false, false}
      
      set {thisCharsId, countChars, nextCharPointer, lenActTrigger} to {(theCharIDList's item charPointer), (countChars + 1), charPointer, 1}
      if debugLogEnable then L's loqThis(5, false, "Character " & charPointer & " : \"" & (string id (get theCharIDList's item charPointer)) & "\"")
      
      if hasMaxCnt and (countChars ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
         set triggerGroupInc to true
         if debugLogEnable then L's loqThis(3, false, "Count triggers Group Increment")
      else --  check the character type
         if debugLogEnable then L's loqThis(3, false, "Character Type Check")
         if ((thisCharsId ≥ 65) and (thisCharsId ≤ 90)) or ((thisCharsId ≥ 97) and (thisCharsId ≤ 122)) or (thisCharsId ≥ 192) then -- a latin character
            if Alpha then set triggerGroupInc to true
            if debugLogEnable and Alpha then L's loqThis(4, false, "Alpha triggers Group Increment")
         else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
            if Numb then set triggerGroupInc to true
            if debugLogEnable and Numb then L's loqThis(4, false, "Number triggers Group Increment")
         else if thisCharsId = 32 then --- its a space
            if Blank then set triggerGroupInc to true
            if debugLogEnable and Blank then L's loqThis(4, false, "Space triggers Group Increment")
         else if thisCharsId > 32 then -- its a symbol
            if Symbol then set triggerGroupInc to true
            if debugLogEnable and Symbol then L's loqThis(4, false, "Symbol triggers Group Increment")
         end if
      end if
      
      repeat with anAlist in ActionList
         set {AlistTriggerId_L, endTriggerPointer} to {((id of contents of anAlist's item 1) as list), (charPointer + (count of anAlist's item 1) - 1)}
         
         if (endTriggerPointer ≤ stringCount) and ((not hasActionTrigger) or (theActionTrigger = AlistTriggerId_L)) and ¬
            ((triggerGroupInc and ({13} = AlistTriggerId_L)) or (AlistTriggerId_L = (theCharIDList's items charPointer thru endTriggerPointer))) ¬
               then
            
            if debugLogEnable and not hasActionTrigger then L's loqThis(4, false, "Action List is triggered by \"" & (anAlist's item 1) & "\"")
            if not hasActionTrigger then set {hasActionTrigger, theActionTrigger, lenActTrigger} to {true, AlistTriggerId_L, (count of AlistTriggerId_L)}
            
            set {theAction, hasPar3, valuePar3} to {(item 2 of anAlist), (3 ≤ (count of anAlist)), true}
            if hasPar3 then set valuePar3 to (contents of item 3 of anAlist)
            
            if debugLogEnable then
               if hasPar3 then
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\" ; \"" & (contents of item 3 of anAlist) & "\""
               else
                  set actionString to "Action \"" & (contents of item 2 of anAlist) & "\""
               end if
            end if
            
            ## Group 1 - Character Control -  Does Not clear triggerGroupInc
            
            if ("A" = theAction) then
               set Alpha to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpAlp to " & valuePar3)
               
            else if "B" = theAction then --
               set backOneOnGrpInc to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpBck to " & valuePar3)
            else if ("C" = theAction) then
               if (not hasPar3) or (0 = valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {0, false}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to 0")
               else if (0 < valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {valuePar3, true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpCnt to " & valuePar3)
               else if (0 > valuePar3) then
                  set {MaxCnt, hasMaxCnt} to {(MaxCnt - valuePar3), true}
                  if debugLogEnable then L's loqThis(5, false, actionString & " - Increment inpCnt by " & (-valuePar3))
               end if
               
            else if ("N" = theAction) then
               if hasPar3 then set Numb to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpNum to " & valuePar3)
               
            else if ("P" = theAction) then
               set Symbol to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & "- Set inpSym to " & valuePar3)
            else if ("Q" = theAction) then
               if debugLogEnable then L's loqThis(4, false, actionString & "- Quit Actions")
               exit repeat
               
            else if "S" = theAction then
               set Blank to valuePar3
               if debugLogEnable then L's loqThis(5, false, actionString & " - Set inpSpa to " & valuePar3)
               
               ## Group 2 - Character Control -  clears triggerGroupInc on multi character trigger
               
            else if "D" = theAction then --
               set validChar to not valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Action Trigger: " & valuePar3)
               
            else if "O" = theAction then --
               set triggerOutputDrop to valuePar3
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Drop Output characters")
               
            else if "J" = theAction then --
               if not hasPar3 then set valuePar3 to lenActTrigger
               set {nextCharJump, hasJump} to {valuePar3, true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Jump forward " & valuePar3 & " Characters")
               
            else if "T" = theAction then -- translate/drop this character
               if not hasPar3 then set valuePar3 to ""
               set {newCharIDs, hasNewChars} to {(id of valuePar3), true}
               if 1 < lenActTrigger then set triggerGroupInc to false
               if debugLogEnable then L's loqThis(5, false, actionString & " - Translate characters to \"" & valuePar3 & "\"")
               
               ## Group 3 - Group & Method control, clears triggerGroupInc - later
            else if "G" = theAction then -- new group
               if not hasPar3 then set valuePar3 to 0
               set {nextGroup, triggerGroupInit} to {valuePar3, true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Group " & valuePar3)
               
            else if "I" = theAction then -- increment group
               if not hasPar3 then set valuePar3 to 1
               set {nextGroup, triggerGroupInit} to {(thisGroup + valuePar3), true}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Increment Group to " & nextGroup)
               
            else if "M" = theAction then -- set method
               if not hasPar3 then set valuePar3 to (thisMethod + 1)
               set {nextMethod, triggerMethodInit, triggerOutputDrop, validChar} to {valuePar3, true, true, false}
               if debugLogEnable then L's loqThis(5, false, actionString & " - Switch to Method " & valuePar3 & " and drop output and trigger")
               
            else
               if debugLogEnable then L's loqThis(3, false, actionString & " - Command not found ")
            end if
         end if
      end repeat
      
      if (lenActTrigger > 1) then set countChars to countChars - 1 + lenActTrigger
      if hasMaxCnt and (countChars ≥ MaxCnt) then set {triggerGroupInc} to {true, L's loqThis(3, false, "Character count triggers Group Increment")}
      
      if (triggerGroupInit or triggerMethodInit) then set triggerGroupInc to false
      if (triggerGroupInc or triggerGroupInit or triggerMethodInit) and backOneOnGrpInc then set triggerBack1Char to true
      
      if (0 ≥ nextMethod) then
         if debugLogEnable then L's loqThis(3, false, "Executing Method Reset - Increment Method, Clear output, Reset Input Queue")
         set {parsedNumList, nextMethod, triggerMethodInit, nextCharPointer} to {{}, (thisMethod + 1), true, 1, false}
         set {triggerBack1Char, triggerGroupInc, hasJump, hasNewChars, hasMaxCnt} to {false, false, false, false, false}
      else
         if (0 ≥ nextGroup) then
            set {triggerBack1Char, validChar, triggerGroupInc, hasJump, hasNewChars} to {false, false, false, false, false}
            if debugLogEnable then L's loqThis(3, false, "Executing Group Reset - Switch to Group 1, Clear output , dropping back " & (-nextGroup) & " characters")
            if (-1 ≥ nextGroup) then set {nextCharJump, hasJump} to {(nextGroup), true}
            set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
         end if
         
         if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true, L's loqThis(3, false, "Executing Group Increment - Switch to Group " & (thisGroup + 1))}
         
         if debugLogEnable and triggerOutputDrop then L's loqThis(4, false, "Dropped Output")
         if triggerOutputDrop then set parsedNumList to {}
         if triggerBack1Char then
            set {validChar, nextCharPointer} to {false, charPointer}
            if debugLogEnable then L's loqThis(3, false, "Drop Back 1 Character")
         else if (hasJump or hasNewChars) then
            set validChar to false
            if (hasJump and (((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or (0 < nextCharJump)))) then
               set nextCharPointer to (charPointer + nextCharJump)
               if nextCharPointer < 1 then set nextCharPointer to 1
               if hasNewChars and (0 < nextCharJump) and (lenActTrigger > nextCharJump) then set nextCharPointer to (charPointer + lenActTrigger)
            else if hasJump then
               ## do nothing - can't move charPointer back
               if debugLogEnable then L's loqThis(3, false, "Unable to move back " & (-nextCharJump) & " characters - infinite loop")
            else if hasNewChars then -- and not hasJump
               set nextCharPointer to (charPointer + lenActTrigger)
            end if
            if debugLogEnable then L's loqThis(3, false, "Input Moved Forward " & (nextCharPointer - charPointer) & " characters")
         else
            set nextCharPointer to (charPointer + 1)
            if debugLogEnable then L's loqThis(5, false, "Input Moved Forward 1 character")
         end if
         if hasNewChars then set {parsedNumList} to {(parsedNumList & newCharIDs), L's loqThis(4, false, "Added new Characters to the output")}
         if validChar then set end of parsedNumList to thisCharsId
         if debugLogEnable and not validChar then L's loqThis(3, false, "The input character has been dropped")
      end if
      if debugLogEnable then L's loqThis(3, false, "The output is: \"" & (string id parsedNumList) & "\"")
      
      if nextMethod > MethodCount then -- replace the output with  every remaining character
         if debugLogEnable then L's loqThis(3, false, "Got Method " & nextMethod & "  Return remaining characters")
         if charPointer > 1 then set parsedNumList to (theCharIDList's items charPointer thru stringCount)
         if charPointer ≤ 1 then copy theCharIDList to parsedNumList
         exit repeat
      else if (nextGroup > GroupCount) then
         if debugLogEnable then L's loqThis(3, false, "Finished all groups, exitting")
         exit repeat
      end if
      set charPointer to nextCharPointer -- must be last statement in the the loop
   end repeat
   if debugLogEnable and (charPointer > stringCount) then L's loqThis(3, false, "Finished all characters")
   
   return (string id parsedNumList)
end findBaseName

##########################################
## Capture One General Handlers  Version 2019/03/11

on validateCOP5(minCOPversionstr, maxCOPversionstr)
   ## Copyright 2019 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 COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
   local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
   local theAppName, copVersion
   
   tell application "System Events"
      if debugLogEnable then
         L's loqThis(2, false, ("COP Processes:" & (get U's joinListToString((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
         L's loqThis(3, false, ("All Processes: " & (get U's joinListToString((get name of every process whose background only is false), ", "))))
      end if
      set COPProcList to every process whose name begins with "Capture One" and background only is false
      if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("COP is not running"))}
      if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
      set theAppRef to item 1 of COPProcList
      set theAppName to ((get name of theAppRef) as text)
      set copDetailedVersion to get version of my application theAppName
   end tell
   
   tell application "Capture One 12" to set copVersionStr to (get app version)
   set copVersion to (word -1 of copVersionStr)
   
   if debugLogEnable then
      L's loqThis(2, false, ("theAppName: " & theAppName))
      L's loqThis(1, false, copVersionStr)
      L's loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
   end if
   tell U's compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
   if not minVersionPass then return {hasErrors:true, errorText:(get L's loqqed_Error_Halt5(("This Script does not support Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
   if not maxVersionPass then L's loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
   return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
end validateCOP5

on validateCOPdoc5(theDocRef, validDocKindList)
   ## Copyright 2019 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 12" to set theDocRef to get current document
   
   try
      tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   on error errorText number errorNumber
      return {hasErrors:true, errorText:(get L's 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 L's 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 validateCOPcollections5(theDocRef)
   ## Copyright 2019 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, and the top level collections
   global debugLogEnable
   local namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection
   
   tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   set COPDocKind_s to convertKindList(COPDocKind_p)
   
   tell application "Capture One 12" to tell theDocRef
      set selectedCollectionRef to get current collection
      if (missing value = selectedCollectionRef) then
         try
            set current collection to collection "All Images"
         on error
            set current collection to last collection
         end try
         set selectedCollectionRef to get current collection
      end if
      tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
      set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
   end tell
   set countTopCollections to count of namesTopCollections
   
   repeat with collectionCounter from 1 to countTopCollections
      if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
         (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
         set selectedCollectionIndex to collectionCounter
         exit repeat
      end if
   end repeat
   
   local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections
   
   if COPDocKind_s = "catalog" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter - 1
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set selectedCollectionMirroredAtTopLast to ¬
         (selectedCollectionIndex = countTopCollections) and ¬
         ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)
      
      set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}
      
   else if COPDocKind_s = "session" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("favorite" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set countFavoriteCollections to countTopCollections - topUserCollectionIndex
      if 0 = countFavoriteCollections then
         set namesFavoriteCollections to {}
      else
         set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
      end if
      
      set selectedCollectionMirroredAtTopLast to false
   end if
   
   local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
   set selectedCollectionIsUser to ¬
      (selectedCollectionMirroredAtTopLast or ¬
         ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))
   
   if topUserCollectionIndex < bottomUserCollectionIndex then
      set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
      set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
   else
      set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
      set countTopUserCollections to count of namesTopUserCollections
   end if
   
   return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}
   
end validateCOPcollections5

on convertKindList(kind_list)
   ## Copyright 2019 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_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type
   
   set kind_s_list to {}
   set input_is_list to ("list" = (get (class of kind_list) as text))
   if input_is_list then
      if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      repeat with theItem in kind_list
         tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
      end repeat
      if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
   else
      if ("text" = (get (class of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
      if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
      set kind_s_list to {kind_s1}
   end if
   
   set fail_flag to false
   set code_start to -5
   
   set kind_list to {}
   repeat with Kind_s_item in kind_s_list
      if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
      set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
      set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)
      
      if kind_type = "CC" then ## Collection Kinds
         if kind_code = "CCpj" then
            set the end of kind_list to "project"
         else if kind_code = "CCgp" then
            set the end of kind_list to "group"
         else if kind_code = "CCal" then
            set the end of kind_list to "album"
         else if kind_code = "CCsm" then
            set the end of kind_list to "smart album"
         else if kind_code = "CCfv" then
            set the end of kind_list to "favorite"
         else if kind_code = "CCff" then
            set the end of kind_list to "catalog folder"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CL" then ## Layer Kinds
         if kind_code = "CLbg" then
            set the end of kind_list to "background"
         else if kind_code = "CLnm" then
            set the end of kind_list to "adjustment"
         else if kind_code = "CLcl" then
            set the end of kind_list to "clone"
         else if kind_code = "CLhl" then
            set the end of kind_list to "heal"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CR" then ## Watermark Kinds
         if kind_code = "CRWn" then
            set the end of kind_list to "none"
         else if kind_code = "CRWt" then
            set the end of kind_list to "textual"
         else if kind_code = "CRWi" then
            set the end of kind_list to "imagery"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CO" then ## Document Kinds
         if kind_code = "COct" then
            set the end of kind_list to "catalog"
         else if kind_code = "COsd" then
            set the end of kind_list to "session"
         else
            set fail_flag to true
         end if
      else
         set fail_flag to true
      end if
      
      if fail_flag then ¬
         error (get L's loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
   end repeat
   
   if input_is_list then
      return kind_list
   else
      return item 1 of kind_list
   end if
   
end convertKindList
Cheers, Eric
[late 2015 iMac, 4GHz i7, 24GB RAM, external SSDs. GX8, E-M1, GX7, GM5, GM1 ....]
Eric Nepean
 
Posts: 445
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa


Return to Scripting



Who is online

Users browsing this forum: No registered users and 2 guests