Moving selecting images into new folder and add it

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

Moving selecting images into new folder and add it

Postby FL_ » Sat Feb 16, 2019 10:39 am

Hi,
I don't have any experience with scripting and wonder if this is something I could eventually do:

I often shoot Panos (among other things).

All files are stored in folders on my hard-disk named with date and location. Importing form memory card is done via C1.

One of the first things I do after importing is selecting all images of an individual of pano-shoot, generate & add a sub-folder under the image folder via C1 named pano_1, pano_2,.. and then move the selected images into that subfolder.

With sometimes 20..30 panos this gets quite tedious. Any ideas whether this is scriptable and how to get started?

Best
Frank
FL_
 
Posts: 127
Joined: Mon May 18, 2015 10:11 pm

Re: Moving selecting images into new folder and add it

Postby Eric Nepean » Sun Feb 17, 2019 3:31 am

I'm working on a utility to do that kind of thing, almost finished.

Can you share the following information:
What is the min, typical and max time interval between images in one Panorama set? How many images in a Pano set?

Would a script recognise the differences between images in one Panaorama set and the next (or images not in a Panaorama set) by the time interval or by some other way? If by time interval, what do you think a good threshold would be?

Is there other EXIF data in the files that could be used to separate one sequence from another? If so, what kind of camera body are you using?

Do you shoot RAW and JPEG, or RAW only, or ??

My script organises images into a sequence, by time interval, and by parsing the file names. It then assigns a name to the sequence. The information is stored in IPTC metadata fields of the variants. I will "hijack" one of the two following sets of IPTC fields:

Option 1:
-- Sequence ID: <--> "contact city" {Base name of first Image in the sequence - i.e. not including the extension}
-- Sequence Count: <--> "contact state" {1,2,3,4 ....}
-- Sequence Total: <--> "contact postal code"
-- Sequence Type: <--> "contact country" {Bracket, Burst, Pano}

Option 2:
-- Sequence ID: <--> "image city" {Base name of first Image in the sequence - i.e. not including the extension}
-- Sequence Count: <--> "image state" {1,2,3,4 ....}
-- Sequence Type: <--> "image country" {Bracket, Burst, Pano}
-- Sequence Total: <--> "image country code"

Once the Sequnce ID is in the Metadata of every variant, then its not really necessary to create albums for each sequence, since Filter tool will easily select the images by Sequence ID.

Or is it so? I have some handlers already that create projects and albums and move images into them, could easily be adapted.
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: Moving selecting images into new folder and add it

Postby FL_ » Sun Feb 17, 2019 10:57 am

Hi Eric,

sounds interesting:

What is the min, typical and max time interval between images in one Panorama set? How many images in a Pano set?


I normally shoot single row. Time between images is typ. "exposure time + 5s". Exposure time is normally less than 1s but sometimes goes up to 10s. Typically I have 12 images in one row. If I do multiple rows the time for changes rows and doing the next shot is somewhat like 10s.

Would a script recognise the differences between images in one Panaorama set and the next (or images not in a Panaorama set) by the time interval or by some other way? If by time interval, what do you think a good threshold would be?


I put a (nearly) black images in between Panos. Otherwise I would expect to have at least half a minute to a minute between two different panos. So for me 30 seconds would be a good threshold.

Is there other EXIF data in the files that could be used to separate one sequence from another? If so, what kind of camera body are you using?


Nikon. Unfortunately there is no easy way of separating sequences.

Do you shoot RAW and JPEG, or RAW only, or ??

RAW only

My script organises images into a sequence, by time interval, and by parsing the file names. It then assigns a name to the sequence. The information is stored in IPTC metadata fields of the variants. I will "hijack" one of the two following sets of IPTC fields:

This would work. However I would still prefer moving the files into folders on the hard disk as I use this new folder as a "base folder" for the pano with subfolders for JPEGs and TIFFs respectively. The pano-file (ptgui or Autopano) goes into the base folder and the pano itself into the folder above where all the images from that shooting are kept. In doing so I can easily safe space (delete no longer needed jpg and especially tiff) or delete the complete pano.

Or is it so? I have some handlers already that create projects and albums and move images into them, could easily be adapted.

Would probably a good starting point for me discovering scripting.

I have another task that might be doable via scripting, i.e. calculating the min(minimum level) and max(maximum level) for the level tool over all images of a pano and then set this for all images.

Best
Frank
FL_
 
Posts: 127
Joined: Mon May 18, 2015 10:11 pm

Re: Moving selecting images into new folder and add it

Postby Eric Nepean » Sun Feb 17, 2019 6:29 pm

Hi Frank
EDITTED
Nice to meet someone who is interested in scripting. Your way of thinking seems methodical, and that is necessary fr scripting.

Then I will share my work, it now has unfinished loose ends that means it is not quite ready for general distribution, but for your level of interest I think it's quite OK.

At this point, I am just printing a report of how thee images are sorted and grouped. I want to be quite sure that is quite OK before I let the script change Metadata or modify the catalog.

Note that I have divided the script into functions, these are grouped as follows:
  • Main Part of the script - setup and finish
  • Main Handler - the algorithm itself
  • Script Specific handlers
  • Capture One Handlers
  • Logging handlers
  • Utilities

In order to understand how this works, I suggest that you start with the main handler and the script specific handlers. Ignore for now Logging handlers and Capture One handlers and the main part of the script.

One thing to note is that every Applescript Event (Applescript telling an Application like "Capture One" to get data or do something) takes significant time, many of these will slow down the script. So I try to write the script so that it gets as much data as possible from Capture One with one "get" request. For example, this means that instead of asking for data from just one variant, I ask for data from all selected variants or all variants. This is faster if you have more than 5 variants or so.

There is commented out code that shows how to break up the functions into script library. As a beginner, this is not a good idea. (But you are likely not my only reader). Once you have many scripts that use the same handlers, then Script Libraries start to make a lot of sense. Debugging a handler in a Scrriptt Library is really difficult, so it has to be thoroughly debugged before you put it into a Library Script.

You are free to use any part of this, if you publish it would appreciate it if you would say that you based your work on mine.

Do ask questions if you have them.

I have to break up the script into parts, or it gets too long for one posting. Just copy the parts, one after another, into a Script Editor file.

The first part
EDITTED
I realised that the image name handling is very specific to my file name strategy. Added more generic options.

Code: Select all
(**** Description
 A Script to extract Image Sequence Information for selected Capture One Variants
 The script can handle images with one variant or multiple variants
 The script can handle RAW-JPG image pairs, and even RAW-JPG-TIFF image triplets
 The script finds sequences based on the time interval between images
 This time interval is configurable. For automatic burst and bracketting, 1 second is suitable.
 For user controlled sequencing, depends how fast you work. 20s? 60s? 180s?
 The script has a GUI that allows the user to configure settings
 Version 1217, January 30th , 2019
 !! No Warranty !! Support is best effort
 Eric Valk, Ottawa, Canada
 
 **** PLanning
 This version only discovers and reports sequences based on intervals.
 The next version will write this into variant Metadata, hijacking some of the IPTC metadata fields
 I also have prototypes that use EXIFtool to get more information about brackts and bursts from the image file.
 
 *** Usage
 In OSX's Script Editor open a new (blank) script window
 Copy and paste this script into the window
 Save the script, perhaps to your ~/Documents/Scripts folder
 Open the Script Editor log window, and select the messages tab
 In Capture One, Select only a small number of variants, from any collection
 Run the script
 
This script does not write or delete any information in the COP Catalog or Session or the image file

*** Notes
 This script has a GUI that allows the user to configure settings without editing the script
 The user may change the amount of reporting by setting the "debugLogLevel" and "ResultsFileMaxDebug"
 The GUI also allows the user to set the filtering options
 If you find that you have some favorite settings, you can configure them as default at the beginning of the script.
 If you are having some issues, then set debugLogLevel to 4 and send me the results from Script Editors log window, or Text Edit.
 Don't try to change the GUI code unless you are an experienced Apple Script programmer.
*)
use AppleScript version "2.5"
use scripting additions

## To use the General Handlers as Script Libraries, comment the lines below
global U, L, C
set U to me
set L to me
set C to me
set theMainScript to me

## To use the General Handlers as Script Libraries, uncomment the lines below
## use U : script "Utilities_1215"  -- refers to ~/Library/Scripts/Utilities_1215.sctpd
## use L : script "Loqqing_1220"  -- refers to ~/Library/Scripts/Loqqing_1220.sctpd
## use C : script "CaptureOne_1220"  -- refers to ~/Library/Scripts/CaptureOne_1220.sctpd

## set L's theMainScript to me
## set C's theMainScript to me
## if false then true -- change this to trigger a compile of this script. Use when a Library Script file has been uodated

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 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
set Loqqing's enableNotifications to false --               (true/false)  - enable notifications of errors and exceptions
set Loqqing's notificationsMaxDebug to 0 --            0...6  suggest not more than 1
set Loqqing's ResultsFileMaxDebug to 2 --            0...6  suggest not more than 2
set enableFastGui to true --                   (true/false)

set enableIntervalSurvey to true -- reports all images and intervals, for debugging
set maxSequenceInterval to 500
set enableSequenceReport to true
set enableWriteToMetadata to false
set enableHueristicBracket to true
set aeBracketStep to 6 -- EV increment for detecting exposure bracketting. 6 enables increments of 1/6 of a stop, good for both ⅓ stop bracketting and ½ stop bracketting
set enableJpegSequence to true
set enableRawSequence to true
set enabledRawFileTypeList to {"RW2", "ORF", "ARW"}
set enableTiffSequence to true
set enableImageGrouping to false
set bnGroup1 to {bnpAlp:false, bnpNum:false, bnpSpa:true, bnpSym:true, bnpBck:false, bnpCnt:4, bnpAct:{}} -- Group 1: Alpha and Numbers OK, stop on Space or symbol or 4 characters
set bnGroup2 to {bnpAlp:true, bnpNum:false, bnpSpa:true, bnpSym:true, bnpBck:false, bnpCnt:3, bnpAct:{{"$", "R"}}} -- Group 2: Only Numbers OK, stop on Alpha or Space or Symbol or 3 characters. Reset if a "$" is found
set bnGroup3 to {bnpAlp:true, bnpNum:false, bnpSpa:true, bnpSym:true, bnpBck:true, bnpCnt:5, bnpAct:{{"$", "R"}}} -- Group 2: Only Numbers OK, stop on Alpha or Space or Symbol or 5 characters. Reset if a "$" is found

#### Edits. Line below is very specific to my file name strategy. Added more generic options
## set basicNameParse to setIdBnParsingMethod({{bnGroup1, bnGroup2, bnGroup3}})

set bnGroup4 to {bnpAlp:false, bnpNum:false, bnpSpa:false, bnpSym:false, bnpBck:false, bnpCnt:12, bnpAct:{}} -- Group 1: take the first 12 characters of any type
set bnGroup5 to {bnpAlp:false, bnpNum:false, bnpSpa:true, bnpSym:true, bnpBck:true, bnpCnt:5, bnpAct:{}} -- Group 2: take the next 5 numbers or alphabetic characters, stop on a Symbol or Space
##set basicNameParse to setIdBnParsingMethod({{bnGroup4, bnGroup5}})

set bnGroup6 to {bnpAlp:false, bnpNum:false, bnpSpa:true, bnpSym:false, bnpBck:true, bnpCnt:5, bnpAct:{{"-", "G"}}} -- Group 2: take the next 5 characters, stop on a Space or if a "-" is found
## set basicNameParse to setIdBnParsingMethod({{bnGroup4, bnGroup6}})

set bnGroup7 to {bnpAlp:false, bnpNum:false, bnpSpa:false, bnpSym:false, bnpBck:false, bnpCnt:0, bnpAct:{}} -- Group 1: take all characters of any type
set basicNameParse to setIdBnParsingMethod({{bnGroup7}}) --  this doesn't do any name parsing at all.

## Choose lists for GUI
## The GUI allows you to change the default by choosing from a choose list
## Put your favorites in the default list, other commonly used values in the choose list
set chooseRawFileExt_L to {"RW2", "ORF", "ARW", "NEF", "CR2"}

## ***** 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 gateResultsDialog to true
set Loqqing's gateResultsByClipboard to true
set Loqqing's gateResultsDialog to true

## Reporting methods which are NOT valid for this script
-- disable the reporting method
set Loqqing's gateResultsNotification to false
set gateResultsInCollection to false
-- disable user control
set enableResultsInCollection to false
set Loqqing's enableResultsByNotifications to false
##

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_EXIF_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 (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 -- not global
tell C's validateCOPdoc5(COPDocRef, {"Session", "Catalog"})
   if its hasErrors then error (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

if countSelectedVariants = 0 then L's loqqed_Error_Halt5(true, "No images selected")

L's loq_Results5(-1, false, ("Started from: " & parent_name & "  Action: Identify Image Sequences"))


mainHandler()

L's loq_Results5(-1, true, (return & "*** " & Script_Title & " is Done ***"))

## Arrange the windows to show results on top
tell application "System Events" to set frontmost of process theAppName to true

if Loqqing's enableResultsFile then
   tell application "System Events" to set frontmost of process "TextEdit" to true
else
   tell application "System Events" to set frontmost of process parent_name to true
end if

return finalCleanup()

###########################################################################################################################
## Script Specific Handlers

on mainHandler()
   global debugLogEnable, basicNameParse, aeBracketStep, maxSequenceInterval, enableIntervalSurvey, enableSequenceReport
   local piName, piId, piExtensionList, piDateList, piVarRefList, varNameList, piIsoList, piApertureList, piShutterList
   set debugLogEnable to false
   tell getAllVariantData() to set {piName, piId, piExtensionList, piDateList, piVarRefList, varNameList, piIsoList, piApertureList, piShutterList} to ¬
      {its piName, its piId, its piExtensionList, its piDateList, its piVarRefList, its varNameList, its piIsoList, its piApertureList, its piShutterList}
   
   local *censored word*, sortedExtensionsList, extensionCount
   set *censored word* to U's summarizeList(piExtensionList, 0, false)
   set sortedExtensionsList to makeExtensionsList(*censored word*)
   set extensionCount to count of sortedExtensionsList's item 1
   if 0 = extensionCount then error (L's loqqed_Error_Halt5("The selected variants have no file extensions that are enabled. File extensions are: \"" & U's joinListToString(*censored word*, "\", \"") & "\""))
   
   local currentImageID, prevImageDate, prevImageDateIsSequence, prevImageGroupHasDate, prevImageDateIsSequence, hasSecondaryImage, imageSequenceInterval, prevImageGroupDate
   set currentImageID to 0
   set prevImageGroupDate to missing value
   set prevImageGroupHasDate to false
   set prevImageDateIsSequence to false
   set imageSequenceInterval to missing value
   set hasSecondaryImage to missing value
   set evScale to 0.69314718056 / aeBracketStep -- round ev offsets to 1/6 of a stop, for both ⅓ stop bracketting and ½ stop bracketting
   
   global maxSequenceInterval, countSelectedVariants
   local indxVariant, hasImageDate, varImageDate, thisImageDateIsSequence, validExt, varExtID, varExtPrio
   
   set indxVariant to 0
   repeat countSelectedVariants times
      set indxVariant to indxVariant + 1
      if indxVariant > countSelectedVariants then exit repeat
      set {validExt, varExtID, varExtPrio} to findExtensionsID((piExtensionList's item indxVariant), sortedExtensionsList)
      if debugLogEnable then log {"Main Extension Check", "Valid", validExt, "ID", varExtID, "Prio", varExtPrio, "indxVariant", indxVariant}
      if validExt then
         
         local varImageDate, hasImageDate
         try
            set varImageDate to piDateList's item indxVariant as date
            set hasImageDate to true
            if debugLogEnable then log {"Main Date Check", hasImageDate}
         on error
            if prevImageGroupHasDate then error "variant sorting issue"
            set hasImageDate to false
            if debugLogEnable then log {"Main Date Check", hasImageDate}
            set imageSequenceInterval to missing value
            set prevImageGroupDate to missing value
            set prevImageGroupHasDate to false
         end try
         
         if hasImageDate then
            local thisImageDateIsSequence
            if prevImageGroupHasDate then
               set imageSequenceInterval to (varImageDate - prevImageGroupDate)
            end if
            
            local seqPrimaryImg_L, seqSecondaryImg_L, seqEV_L, dateGrpPrimaryImg_L, dateGrpSecondaryImg_L, dateGrpBn_L, dateGrpNameLen_L, dateGrpEv_L, dateGrpExtID_L, dateGrpExtPrio_L
            if (not prevImageGroupHasDate) or (imageSequenceInterval > 0) then
               ## New image
               ## New Date
               
               if debugLogEnable then
                  if prevImageGroupHasDate then
                     log {"Main ", "prevImageGroupHasDate", prevImageGroupHasDate, "imageSequenceInterval", imageSequenceInterval, "prevImageDateIsSequence", prevImageDateIsSequence}
                  else
                     log {"Main ", "prevImageGroupHasDate", prevImageGroupHasDate, "prevImageDateIsSequence", prevImageDateIsSequence}
                  end if
               end if
               
               if prevImageDateIsSequence then
                  if debugLogEnable then log {"Main ", "recordDateGroup"}
                  recordDateGroup given seqPrimary_L:seqPrimaryImg_L, seqSecondary_L:seqSecondaryImg_L, seqEV_L:seqEV_L, seqBN_L:seqBN_L, dateGrpPrimary_L:dateGrpPrimaryImg_L, dateGrpSecondary_L:dateGrpSecondaryImg_L, dateGrpBn_L:dateGrpBn_L, dateGrpEv_L:dateGrpEv_L, Intervals:imageSequenceInterval, Interval_L:seqInterval_L
               end if
               
               if (not prevImageGroupHasDate) or ((not enableIntervalSurvey) and (imageSequenceInterval > maxSequenceInterval)) then -- report the sequence and start over
                  if prevImageDateIsSequence then -- previous sequence has ended, report it
                     if debugLogEnable then log {"Main ", "reportSequence"}
                     if (2 < (count of seqPrimaryImg_L)) then reportSequence given PrimaryImage_L:seqPrimaryImg_L, SecondaryImage_L:seqSecondaryImg_L, ImageEV_L:seqEV_L, seqBN_L:seqBN_L, Intervals:seqInterval_L, piName:piName, piVarRefList:piVarRefList, varNameList:varNameList
                  end if
                  if debugLogEnable then log {"Main ", "initialise sequence variables"}
                  set {seqPrimaryImg_L, seqSecondaryImg_L, seqEV_L, seqBN_L, seqInterval_L} to {{}, {}, {}, {}, {}} -- initialise sequence variables
                  set {thisImageDateIsSequence, indxVariant, varImageDate} to findSequenceStart(indxVariant, varImageDate, piDateList, (sortedExtensionsList's item 1), piExtensionList)
                  if not thisImageDateIsSequence then exit repeat -- did not find another sequence start
                  set {validExt, varExtID, varExtPrio} to findExtensionsID((piExtensionList's item indxVariant), sortedExtensionsList)
               end if
               
               ## start new date group
               if debugLogEnable then log {"Main ", "start new date group"}
               set {dateGrpPrimaryImg_L, dateGrpSecondaryImg_L, dateGrpBn_L, dateGrpNameLen_L, dateGrpEv_L, dateGrpExtID_L, dateGrpExtPrio_L} to ¬
                  {{}, {}, {}, {}, {}, {}, {}}
               
               ## start new image group
               if debugLogEnable then log {"Main ", "start new image group"}
               set {end of dateGrpPrimaryImg_L, end of dateGrpSecondaryImg_L} to {indxVariant, {}}
               set {end of dateGrpBn_L, end of dateGrpNameLen_L} to {(parseBaseName(varNameList's item indxVariant, basicNameParse)), (count of varNameList's item indxVariant)}
               set {end of dateGrpExtID_L, end of dateGrpExtPrio_L} to {varExtID, varExtPrio}
               set end of dateGrpEv_L to missing value
               copy (piId's item indxVariant as integer) to currentImageID
               copy {varImageDate, true, thisImageDateIsSequence} to {prevImageGroupDate, prevImageGroupHasDate, prevImageDateIsSequence}
               set {igPtr, hasSecondaryImage} to {1, false} -- maintain consistency
               
            else
               ## Existing Date
               if debugLogEnable then log {"Main ", "Existing Date"}
               local varImageID, varBn, varNameLength, varExtID, varExtPrio, igPtr
               set varImageID to (piId's item indxVariant as integer)
               if varImageID ≠ currentImageID then --Second or higher Variant ignored
                  if debugLogEnable then log {"Main ", "Not a variant - New image, get data"}
                  copy varImageID to currentImageID -- CO reports all variants of an image in sequence
                  ## New image, get data
                  set {varBn, varNameLength} to {parseBaseName(varNameList's item indxVariant, basicNameParse), (count of varNameList's item indxVariant)}
                  ## Check for basic name match
                  repeat with igPtr from (count of dateGrpBn_L) to 0 by -1
                     if (0 = (contents of igPtr)) or (varBn = (contents of dateGrpBn_L's item igPtr)) then exit repeat
                  end repeat
                  set igPtr to (contents of igPtr) -- dereference
                  
                  if (0 = igPtr) then -- basic name not found, start new image group
                     if debugLogEnable then log {"Main ", "basic name not found, start new image group"}
                     set {end of dateGrpPrimaryImg_L, end of dateGrpSecondaryImg_L} to {indxVariant, {}}
                     set {end of dateGrpBn_L, end of dateGrpNameLen_L} to {varBn, varNameLength}
                     set {end of dateGrpExtID_L, end of dateGrpExtPrio_L} to {varExtID, varExtPrio}
                     set end of dateGrpEv_L to missing value
                     set {igPtr, hasSecondaryImage} to {(count of dateGrpPrimaryImg_L), false} -- maintain consistency
                     
                  else -- add Image to the identified Image group
                     if debugLogEnable then log {"Main ", "add Image to the identified Image group"}
                     set hasSecondaryImage to (0 < (count of dateGrpSecondaryImg_L's item igPtr))
                     if (varExtPrio > (contents of dateGrpExtPrio_L's item igPtr)) or ¬
                        ((varExtPrio = (contents of dateGrpExtPrio_L's item igPtr)) and (varNameLength ≥ (contents of dateGrpNameLen_L's item igPtr))) ¬
                           then -- if inferior (extension priority, name length) add this image as a secondary image
                        if debugLogEnable then log {"Main ", "add this image as a secondary image"}
                        if hasSecondaryImage then
                           set end of dateGrpSecondaryImg_L's item igPtr to indxVariant
                        else
                           set dateGrpSecondaryImg_L's item igPtr to {indxVariant}
                        end if
                     else -- make this the primary image, make the current primary image a secondary image
                        if debugLogEnable then log {"Main ", "add this image as the primary image"}
                        if hasSecondaryImage then
                           copy (dateGrpPrimaryImg_L's item igPtr) to end of dateGrpSecondaryImg_L's item igPtr
                        else
                           copy {dateGrpPrimaryImg_L's item igPtr} to dateGrpSecondaryImg_L's item igPtr
                        end if
                        set (dateGrpPrimaryImg_L's item igPtr) to indxVariant
                        set (dateGrpExtID_L's item igPtr) to varExtID
                        set (dateGrpExtPrio_L's item igPtr) to varExtPrio
                        set (dateGrpNameLen_L's item igPtr) to varNameLength
                     end if
                  end if
               end if
            end if
         end if
      end if
   end repeat
   
   ## handle the end of the list of selected variants
   if prevImageDateIsSequence then
      if debugLogEnable then log {"Main ", "recordDateGroup"}
      recordDateGroup given seqPrimary_L:seqPrimaryImg_L, seqSecondary_L:seqSecondaryImg_L, seqEV_L:seqEV_L, seqBN_L:seqBN_L, dateGrpPrimary_L:dateGrpPrimaryImg_L, dateGrpSecondary_L:dateGrpSecondaryImg_L, dateGrpBn_L:dateGrpBn_L, dateGrpEv_L:dateGrpEv_L, Intervals:missing value, Interval_L:seqInterval_L
   end if
   
   if enableIntervalSurvey or (prevImageDateIsSequence and (2 < (count of seqPrimaryImg_L))) then
      if enableSequenceReport then reportSequence given PrimaryImage_L:seqPrimaryImg_L, SecondaryImage_L:seqSecondaryImg_L, ImageEV_L:seqEV_L, seqBN_L:seqBN_L, Intervals:seqInterval_L, piName:piName, piVarRefList:piVarRefList, varNameList:varNameList
   end if
end mainHandler

on getAllVariantData()
   global debugLogEnable, selectedCollectionRef, enableHueristicBracket
   set {piIsoList, piApertureList, piShutterList} to {{}, {}, {}}
   tell application "Capture One 12"
      tell selectedCollectionRef to set sorting order to by date
      set {piName, piId, piExtensionList, piDateList, piVarRefList} to ¬
         {name, id, extension, EXIF capture date, variants} of (parent image of every variant whose selected is true)
      set {varNameList} to {name} of (every variant whose selected is true)
      if enableHueristicBracket then set {piIsoList, piApertureList, piShutterList} to {EXIF ISO, EXIF aperture, EXIF shutter speed} of (parent image of every variant whose selected is true)
   end tell
   return {piName:piName, piId:piId, piExtensionList:piExtensionList, piDateList:piDateList, piVarRefList:piVarRefList, varNameList:varNameList, piIsoList:piIsoList, piApertureList:piApertureList, piShutterList:piShutterList}
end getAllVariantData

on makeExtensionsList(*censored word*)
   global debugLogEnable, enableRawSequence, enabledRawFileTypeList, enableJpegSequence, enableTiffSequence
   local fileExtensionsList, extPriorityList
   set {fileExtensionsList, extPriorityList} to {{}, {}}
   if enableRawSequence then
      repeat with theFileType in *censored word*
         if enabledRawFileTypeList contains theFileType then
            copy contents of theFileType to end of fileExtensionsList
            copy 1 to end of extPriorityList
         end if
      end repeat
   end if
   if enableJpegSequence and (*censored word* contains "JPG") then ¬
      set {fileExtensionsList, extPriorityList} to {(fileExtensionsList & {"JPG"}), (extPriorityList & {2})}
   if enableTiffSequence then
      if (*censored word* contains "TIF") and (fileExtensionsList does not contain "TIF") then ¬
         set {fileExtensionsList, extPriorityList} to {(fileExtensionsList & {"TIF"}), (extPriorityList & {3})}
      if (*censored word* contains "TIFF") and (fileExtensionsList does not contain "TIFF") then ¬
         set {fileExtensionsList, extPriorityList} to {(fileExtensionsList & {"TIFF"}), (extPriorityList & {3})}
   end if
   if debugLogEnable then log {"Exit makeExtensionsList", "fileExtensionsList", fileExtensionsList, "extPriorityList", extPriorityList}
   return {fileExtensionsList, extPriorityList}
end makeExtensionsList

on findExtensionsID(theExtension, sortedExtensionList)
   global debugLogEnable
   if debugLogEnable then log {"findExtensionsID", "theExtension", theExtension}
   local extCtr
   repeat with extCtr from (count of sortedExtensionList's item 1) to 0 by -1
      if (0 < contents of extCtr) and (theExtension = (contents of sortedExtensionList's item 1's item extCtr)) then exit repeat
   end repeat
   if (0 = (contents of extCtr)) then return {false, missing value, missing value}
   if debugLogEnable then log {"Exit findExtensionsID", "extCtr", (contents of extCtr), "ExtPrio", (contents of sortedExtensionList's item 2's item extCtr)}
   return {true, (contents of extCtr), (contents of sortedExtensionList's item 2's item extCtr)}
end findExtensionsID

on findSequenceStart(prevVarIndx, prevImageDate, piDateList, extensionsList, piExtensionList)
   global debugLogEnable, maxSequenceInterval, enableIntervalSurvey
   local maxIndx, nextImageDate, validExt, varExtID, varExtPrio, varIndx
   
   if debugLogEnable then log {"findSequenceStart", "prevVarIndx", prevVarIndx, "prevImageDate", prevImageDate, "enableIntervalSurvey", enableIntervalSurvey}
   if enableIntervalSurvey then return {true, prevVarIndx, prevImageDate}
   set {maxIndx, varIndx} to {(count of piDateList), (prevVarIndx + 1)}
   if varIndx > maxIndx then return {false, missing value, missing value}
   set validExt to (extensionsList contains (contents of piExtensionList's item varIndx))
   if validExt then set nextImageDate to ((piDateList's item varIndx) as date)
   repeat until (validExt and (nextImageDate - prevImageDate ≤ maxSequenceInterval))
      if validExt then copy {varIndx, nextImageDate} to {prevVarIndx, prevImageDate}
      set varIndx to (varIndx + 1)
      if varIndx ≥ maxIndx then return {false, missing value, missing value}
      set validExt to extensionsList contains (contents of piExtensionList's item varIndx)
      if validExt then copy ((piDateList's item varIndx) as date) to nextImageDate
   end repeat
   if debugLogEnable then log {"Exit findSequenceStart", "Found", true, "varIndx", (varIndx - 1), "Date", prevImageDate}
   return {true, prevVarIndx, prevImageDate}
end findSequenceStart

on recordDateGroup given seqPrimary_L:primary_L, seqSecondary_L:secondary_L, seqEV_L:EV_L, dateGrpPrimary_L:dgPrimary_L, dateGrpSecondary_L:dgSecondary_L, dateGrpBn_L:dgBn_L, dateGrpEv_L:dgEvList, seqBN_L:seqBN_L, Intervals:nextSequenceInterval, Interval_L:seqInterval_L
   global debugLogEnable
   if debugLogEnable then log {"recordDateGroup", "DG Items", (count of dgPrimary_L), "Seq Items", (count of primary_L)}
   
   local dgCtr, sortPtr, sortIndx
   set sortIndx to sortOrder(dgBn_L)
   set dgCount to (count of dgPrimary_L)
   repeat with dgCtr from 1 to dgCount
      set sortPtr to (sortIndx's item dgCtr)
      -- copy {(dgPrimary_L's item sortPtr), (dgSecondary_L's item sortPtr), (dgEvList's item sortPtr), (dgBn_L's item sortPtr)} to {end of primary_L, end of secondary_L, end of EV_L, end of seqBN_L}
      copy (dgPrimary_L's item sortPtr) to end of primary_L
      copy (dgSecondary_L's item sortPtr) to end of secondary_L
      copy (dgEvList's item sortPtr) to end of EV_L
      copy (dgBn_L's item sortPtr) to end of seqBN_L
      if (dgCount < (contents of dgCtr)) then
         set end of seqInterval_L to "0"
      else if (missing value = nextSequenceInterval) then
         set end of seqInterval_L to "No Value "
      else
         set end of seqInterval_L to (nextSequenceInterval as text)
      end if
   end repeat
   if debugLogEnable then log {"Exit recordDateGroup", "Seq Items", (count of primary_L)}
end recordDateGroup

on sortOrder(T)
   ## insertion sort optimised for Applescript. Good for short lists.
   ## will sort numbers, text strings and dates
   ## returns the sort order, not the sorted list
   global debugLogEnable
   local ptrList, Ictr, Jctr, Vpivot
   
   if debugLogEnable then log {"sortOrder", "Item count", (count of T), "Items", T}
   set {ptrList, Ictr} to {{1}, 2}
   repeat while Ictr ≤ (count of T)
      copy (T's item Ictr) to Vpivot
      if Vpivot < (T's item (ptrList's item (Ictr - 1))) then
         copy Ictr - 1 to Jctr
         repeat while (Jctr > 0) and (Vpivot < T's item (ptrList's item Jctr))
            set Jctr to Jctr - 1
         end repeat
         if Jctr > 0 then
            set ptrList to (ptrList's items 1 thru Jctr) & (contents of Ictr) & (ptrList's items (Jctr + 1) thru (Ictr - 1))
         else
            copy (contents of Ictr) to beginning of ptrList
         end if
      else
         copy (contents of Ictr) to end of ptrList
      end if
      set Ictr to Ictr + 1
   end repeat
   if debugLogEnable then log {"Exit sortOrder", "Item count", (count of ptrList), "Items", ptrList}
   return ptrList
end sortOrder

on reportSequence given PrimaryImage_L:PrimaryImage_L, SecondaryImage_L:SecondaryImage_L, ImageEV_L:ImageEV_L, seqBN_L:seqBN_L, Intervals:seqInterval_L, piName:piName, piVarRefList:piVarRefList, varNameList:varNameList
   global debugLogEnable
   local seqReport_S, imageCtr, anIndex, theprefix
   if debugLogEnable then log {"reportSequence", "Seq Items", (count of PrimaryImage_L)}
   
   
   set seqReport_S to "Sequence Name: " & varNameList's item (PrimaryImage_L's item 1)
   set seqReport_S to seqReport_S & return & "Reference Image " & (item (item 1 of PrimaryImage_L) of piName) & " (" & (count of PrimaryImage_L) & " images)"
   
   set seqCtr to 1
   repeat with imageCtr from 1 to (count of PrimaryImage_L)
      set seqReport_S to seqReport_S & return & "Image " & seqCtr & ": " & item (item imageCtr of PrimaryImage_L) of piName
      set seqReport_S to seqReport_S & " [" & (count of (item (item imageCtr of PrimaryImage_L) of piVarRefList)) & " var.]"
      if missing value ≠ (contents of item imageCtr of SecondaryImage_L) then
         if 2 ≥ (count of item imageCtr of SecondaryImage_L) then
            set theprefix to ""
            set seqReport_S to seqReport_S & "   ("
            repeat with anIndex in (item imageCtr of SecondaryImage_L)
               set seqReport_S to seqReport_S & theprefix & item anIndex of piName
               set seqReport_S to seqReport_S & " [" & (count of (item anIndex of piVarRefList)) & " var.]"
               set theprefix to ", "
            end repeat
            set seqReport_S to seqReport_S & ")"
         else
            set theprefix to return & "            "
            set seqReport_S to seqReport_S & theprefix & "Secondary Image List:"
            set theprefix to theprefix & "   - "
            repeat with anIndex in (item imageCtr of SecondaryImage_L)
               set seqReport_S to seqReport_S & theprefix & item anIndex of piName
               set seqReport_S to seqReport_S & " [" & (count of (item anIndex of piVarRefList)) & " var.]"
            end repeat
         end if
      end if
      set seqReport_S to seqReport_S & return & "      Basic Name: \"" & (item imageCtr of seqBN_L) & "\""
      set seqReport_S to seqReport_S & "  Next Interval: " & (item imageCtr of seqInterval_L) & "s"
      set seqCtr to seqCtr + 1
   end repeat
   L's loq_Results5(-1, false, (return & "*** " & seqReport_S))
end reportSequence

on setIdBnParsingMethod(theMethod)
   global debugLogEnable
   local theNewRecord, theMode, theGroup, ActionList, anAlist, theID
   copy theMethod to theNewMethod
   repeat with theMode in theNewMethod
      repeat with theGroup in theMode
         repeat with anAlist in theGroup's bnpAct
            if "text" = (get (class of anAlist's item 1) as text) then copy (id of contents of anAlist's item 1) to anAlist's item 1
         end repeat
      end repeat
   end repeat
   return theNewMethod
end setIdBnParsingMethod

on parseBaseName(theString, theMethod_L)
   ## Looks long, but execution time is about 20 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: {bnpAlp, bnpNum, bnpSpa, bnpSym, bnpCnt, bnpBck, bnpAct}:
   ##  (bnpAlp, bnpNum, bnpSpa, bnpSym, bnpBck) boolean,  (bnpCnt)  integer,    bnpAct a list of Actions {{X,Y,Z},{X,Y,Z},...}
   ## A group ends upon finding Alphabetic characters (if bnpAlp true), or Numbers (if bnpNum true), or a Space(if bnpSpa true),
   ## or Symbols (if bnpSym true), or finding more than bnpCnt characters (if bnpCnt >0).
   ## If bnpBck is true, when a group ends, the last character becomes part of the next group
   ## bnpAct contains Alists {X,Y,Z}, each describes an action to execute when finding some character.  bnpAct= {} results in no actions
   ## X is the character that triggers an action; Y is the Action; Z optionally specifies "how much"
   ## Actions:  T-Translate Character to "Z" ; G- Switch to Group Z ; J- Jump ahead Z characters ; M- Switch to Method Z ; I- Increment group by Z
   ##    Actions A,B,C,N,S and P set the value of bnpAlp, bnpBck, bnpCnt, bnpNum, bnpSpa and bnpSym
   ## If the Alist has no Z value for M, G or I actions, the Method or Group is incremented by 1, and for action "T" the character is dropped
   ## Switching to Group 0 resets the parser to Group 1 and clears all output. If bnpBck is true, the current character is dropped.
   ## Switching to 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
   
   global debugLogEnable
   local theCharIDList, stringCount, thisCharsId, parsedNumList, charPointer, safetyCtr
   local Alpha, Numb, Blank, Symbol, MaxCnt, backOneOnGrpInc, ActionList, anAlist, theAction
   local thisMethod, MethodCount, thisGroup, GroupCount, hasMaxCnt, charCount, stringCount
   local triggerMethodInit, triggerGroupInit, triggerGroupInc, validChar, newChars
   local nextGroup, nextMethod, triggerBack1Char, safetyCtr, SafetyLimit, hasPar3, valuePar3
   
   
   set {theCharIDList, stringCount, nextMethod, MethodCount, triggerMethodInit, parsedNumList, charPointer, safetyCtr, SafetyLimit} to ¬
      {(get id of theString), (count of theString), 1, (count of theMethod_L), true, {}, 1, 0, ((count of theMethod_L) * (count of theString) * 4)}
   repeat while (charPointer ≤ stringCount + 1) and (safetyCtr < SafetyLimit)
      set safetyCtr to safetyCtr + 1
      if charPointer > stringCount then exit repeat
      
      if triggerMethodInit then set {nextGroup, GroupCount, triggerGroupInit, triggerMethodInit, thisMethod} to {1, (count of theMethod_L's item nextMethod), true, false, nextMethod}
      
      if triggerGroupInit then tell theMethod_L's item thisMethod's item nextGroup to set {Alpha, Numb, Blank, Symbol, MaxCnt, hasMaxCnt, backOneOnGrpInc, ActionList, charCount, thisGroup, triggerGroupInit} to ¬
         {its bnpAlp, its bnpNum, its bnpSpa, its bnpSym, its bnpCnt, (0 < its bnpCnt), its bnpBck, its bnpAct, 0, nextGroup, false}
      
      set {triggerBack1Char, triggerGroupInc, validChar, charCount, newChars} to {false, false, true, charCount + 1, ""}
      set thisCharsId to (contents of theCharIDList's item charPointer)
      
      if hasMaxCnt and (charCount ≥ MaxCnt) then -- if the counter has triggered, don't check the character groups
         set triggerGroupInc to true
      else --  checking the character
         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
         else if ((thisCharsId ≥ 48) and (thisCharsId ≤ 57)) then -- a number
            if Numb then set triggerGroupInc to true
         else if thisCharsId = 32 then --- its a space
            if Blank then set triggerGroupInc to true
         else if thisCharsId > 32 then -- its a symbol
            if Symbol then set triggerGroupInc to true
         end if
      end if
      
      if triggerGroupInc and backOneOnGrpInc then set triggerBack1Char to true
      
      repeat with anAlist in ActionList
         if thisCharsId = item 1 of anAlist then
            set {theAction, triggerGroupInc, triggerBack1Char} to {(item 2 of anAlist), false, false}
            if 3 ≤ (count of anAlist) then
               set {valuePar3, hasPar3} to {(contents of item 3 of anAlist), true}
            else
               set {valuePar3, hasPar3} to {missing value, false}
            end if
            if ("A" = theAction) then
               if hasPar3 then set Alpha to (true and valuePar3)
            else if "B" = theAction then --
               if hasPar3 then
                  set backOneOnGrpInc to (true and valuePar3)
               else
                  set triggerBack1Char to true
               end if
            else if ("C" = theAction) then
               if hasPar3 and (0 ≤ valuePar3) then set {MaxCnt, hasMaxCnt} to {valuePar3, (MaxCnt > 0)}
            else if "G" = theAction then -- new group
               if not hasPar3 then set valuePar3 to 1
               set {triggerGroupInit, triggerBack1Char, nextGroup} to {true, (backOneOnGrpInc or triggerBack1Char), valuePar3}
            else if "I" = theAction then -- increment group
               if not hasPar3 then set valuePar3 to 1
               set {triggerGroupInit, triggerBack1Char, nextGroup} to {true, (backOneOnGrpInc or triggerBack1Char), (thisGroup + valuePar3)}
            else if "J" = theAction then --
               if not hasPar3 then set valuePar3 to 1
               if ((thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) or valuePar3) then set charPointer to (charPointer + valuePar3)
            else if "M" = theAction then -- new method
               if not hasPar3 then set valuePar3 to thisMethod + 1
               set {nextMethod, triggerMethodInit, triggerBack1Char} to {valuePar3, true, (backOneOnGrpInc or triggerBack1Char)}
            else if ("N" = theAction) then
               if hasPar3 then set Numb to (true and valuePar3)
            else if ("P" = theAction) then
               if hasPar3 then set Symbol to (true and valuePar3)
            else if "S" = theAction then
               if hasPar3 then set Blank to (true and valuePar3)
            else if "T" = theAction then -- translate/drop this character
               set validChar to false
               if hasPar3 then set parsedNumList to parsedNumList & (get id of valuePar3)
            end if
         end if
      end repeat
      
      if (0 ≥ nextMethod) then set {nextMethod, triggerMethodInit, charPointer} to {(thisMethod + 1), true, 0}
      if (0 ≥ nextGroup) then
         if (-1 ≥ nextGroup) then set {triggerBack1Char, validChar, charPointer} to {false, false, (charPointer - nextGroup - 1)}
         set {parsedNumList, nextGroup, triggerGroupInit} to {{}, 1, true}
      end if
      
      if triggerGroupInc then set {nextGroup, triggerGroupInit} to {thisGroup + 1, true}
      if triggerBack1Char then
         set validChar to false
         if (thisGroup ≠ nextGroup) or (thisMethod ≠ nextMethod) then set charPointer to (charPointer - 1)
      end if
      if validChar then set end of parsedNumList to thisCharsId
      
      if nextMethod > MethodCount then -- replace the output with  every remaining character
         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
         exit repeat
      end if
      set charPointer to charPointer + 1 -- must be last statement in the the loop
   end repeat
   return (string id parsedNumList)
end parseBaseName

on finalCleanup()
   ## clean up the large arrays to avoid a large stack that may prevent AppleScript from saving the script
   ## Nothing here right now because all of the big lists are inside a handler, which will clear the meemory when it exits
   global debugLogEnable, parent_name, Script_Title
end finalCleanup

Last edited by Eric Nepean on Sun Feb 17, 2019 7:35 pm, edited 2 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: Moving selecting images into new folder and add it

Postby Eric Nepean » Sun Feb 17, 2019 6:31 pm

The second part. Copy and paste this into Script Editor's Script Window after the first part.

Code: Select all
###########################################################################################################################
## Capture One General Handlers  Version 2019/01/13

## Uncomment these next lines if to use these handlers as a Script Library file  (".scptd")
## use AppleScript version "2.5"
## use scripting additions
## use U : script "Utilities_1215"
## use L : script "Loqqing_1220"
## property theMainScript : missing value
## if false then true

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 loq_Results5(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 loq_Results5(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 loq_Results5(2, false, ("theAppName: " & theAppName))
      L's loq_Results5(1, false, copVersionStr)
      L's loq_Results5(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 loq_Results5(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
   
   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

###########################################################################################################################
## Logging Handlers  Version 2019/01/13

## Uncomment these next lines if to use these handlers as a Script Library  file  (".scptd")
## use AppleScript version "2.5"
## use scripting additions
## use U : script "Utilities_1215"
## property theMainScript : missing value
## if false then true

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:false, gateResultsByClipboard:true, enableResultsByClipboard:true}
   set Loqqing to Loqqing & {stateResultsByDialog:false, gateResultsDialog:true, enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000}
   set Loqqing to Loqqing & {stateResultsByNotification:false, gateResultsNotification:true, enableResultsByNotifications:false, enableNotifications:true, notificationsMaxDebug:0}
   set Loqqing to Loqqing & {stateResultsByLoq:false, gateParentLoqqing:false, enableResultsByLoq:true, initLoqqing:false, gateLoqqing:true}
   
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 loq_Results5(2, false, ("Result Reported by " & LogMethods_S))
   return LogMethods_S
end InitializeLoqqing5

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

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
      theMainScript's finalCleanup()
   end try
   tell current application to set date_string to (current date) as text
   return (get loq_Results5(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
   on error
      set AppleScript's text item delimiters to astid
   end try
   set AppleScript's text item delimiters to astid
   return theString
end joinListToString

on 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 removeTextFromList(theList, theBadChar)
   ## theBadChar may be a string or character, whereupon that string will be removed
   ## theBadChar may be a list of strings or characters, whereupon those strings will be removed
   
   set theDelim to character id 60000 -- obscure character chosen for the low likelihood of its appearance
   set astid to AppleScript's text item delimiters
   try
      set AppleScript's text item delimiters to theDelim
      set theString to theList as string
      set AppleScript's text item delimiters to theBadChar
      set text_list to every text item of theString
      set AppleScript's text item delimiters to ""
      set cleanText_S to the text_list as string
      set AppleScript's text item delimiters to theDelim
      set theList to text items of cleanText_S
   on error errorText
      set AppleScript's text item delimiters to astid
      log errorText
   end try
   set AppleScript's text item delimiters to astid
   
   return theList
end removeTextFromList

on removeItemFromList(theList, theBadItem)
   ## theBadChar may be a string or character, whereupon that string will be removed
   ## theBadChar may be a list of strings or characters, whereupon those strings will be removed
   local theDelim, theString, text_list, cleanText_S, errorText, astid
   set theDelim1 to character id 60000 -- obscure characters chosen for the low likelihood of its appearance
   set theDelim2 to character id 60001
   set astid to AppleScript's text item delimiters
   try
      set AppleScript's text item delimiters to {theDelim2 & theDelim1}
      set theString to theDelim1 & (theList as string) & theDelim2
      set AppleScript's text item delimiters to theDelim1 & theBadItem & theDelim2
      set text_list to every text item of theString
      set AppleScript's text item delimiters to ""
      set cleanText_S to text_list as string
      if 2 < (count of cleanText_S) then
         set cleanText_S to text 2 thru -2 of (text_list as string)
      else
         set cleanText_S to ""
      end if
      set AppleScript's text item delimiters to {theDelim2 & theDelim1}
      set theList to text items of cleanText_S
   on error errorText
      set AppleScript's text item delimiters to astid
      log errorText
   end try
   set AppleScript's text item delimiters to astid
   
   return theList
end removeItemFromList

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

on getIndexOfStrict(theItem, theList)
   ## based on the idea by Emmanuel Levy, modified to handle embedded non-alphabetic characters correctly
   set theDelim to character id 60000 -- obscure character chosen for the low likelihood of its appearance
   set theReturnToken to character id 60001
   set theSearchItem to return & replaceText(theItem, return, theReturnToken) & return
   set tempString to joinListToString(theList, theDelim)
   set tempString to replaceText(tempString, return, theReturnToken)
   set theSearchList to return & replaceText(tempString, theDelim, return) & return
   try
      -1 + (count (paragraphs of (text 1 thru (offset of theSearchItem in theSearchList) of theSearchList)))
   on error
      0
   end try
end getIndexOfStrict

on roundToQuantum(thisValue, quantum)
   ## Public domain author unknown
   return (round (thisValue / quantum) rounding to nearest) * quantum
end roundToQuantum

on roundDecimals(n, numDecimals)
   ## Nigel Garvey, Macscripter
   set x to 10 ^ numDecimals
   tell n * x to return (it div 0.5 - it div 1) / x
end roundDecimals

on MSduration(firstTicks, lastTicks)
   ## Public domain
   ## returns duration in ms
   ## inputs are durations, in seconds, from GetTick's Now()
   return (round (10000 * (lastTicks - firstTicks)) rounding to nearest) / 10
end MSduration

on GetTick_Now()
   ## From MacScripter Author "Jean.O.matiC"
   ## returns duration in seconds since since 00:00 January 2nd, 2000 GMT, calculated using computer ticks
   script GetTick
      property parent : a reference to current application
      use framework "Foundation" --> for more precise timing calculations
      on Now()
         return (current application's NSDate's timeIntervalSinceReferenceDate) as real
      end Now
   end script
   
   return GetTick's Now()
end GetTick_Now

on noValue()
   ## Matt Nueberg
end noValue

on isARef(objectToBeTested)
   ## Matt Nueberg
   try
      objectToBeTested as reference
      return true
   on error
      return false
   end try
end isARef

on summarizeList(theList, numWords, nullString)
   local safetyCtr, theListLength, summaryList, theItem, newString
   set safetyCtr to 0
   set theListLength to count of theList
   set summaryList to {}
   repeat while 0 < (count of theList)
      set safetyCtr to safetyCtr + 1
      if theListLength < safetyCtr then error
      set theItem to item 1 of theList
      set theList to removeItemFromList(theList, theItem)
      if 0 = (count of theItem) then
         if nullString and summaryList does not contain theItem then copy theItem to end of summaryList
      else if 0 = numWords then
         if summaryList does not contain theItem then copy theItem to end of summaryList
      else if 1 = numWords then
         set newString to word 1 of theItem
         if summaryList does not contain newString then copy newString to end of summaryList
      else
         set lastWord to count of words of theItem
         if numWords < lastWord then set lastWord to numWords
         set newString to joinListToString((words 1 thru lastWord of theItem), " ")
         if summaryList does not contain newString then copy newString to end of summaryList
      end if
   end repeat
   return summaryList
end summarizeList

on ln(x, y)
   ## High speed calculation of the natural log of X using Borchardt's algorithm
   ## Y is approximately the digits of precision
   ## https://math.stackexchange.com/questions/75074/an-alternative-way-to-calculate-logx
   ## Chose this approach as it is faster the do shell Unix call
   
   ## prescaling
   set e3 to 20.085536923188
   set e3i to 0.049787068368
   set v to 0
   
   if x > e3 then
      repeat while x > e3
         set x to x / e3
         set v to v + 3
      end repeat
   else if x < e3i then
      repeat while x < e3i
         set x to x * e3
         set v to v - 3
      end repeat
   end if
   
   set z to 10 ^ -y
   set ak to (1 + x) / 2
   set bk to x ^ 0.5
   set ck to ak + bk
   repeat 100 times -- about 9 - 10 times for 6 figure accuracy
      set ak to (ak + bk) / 2
      set bk to (ak * bk) ^ 0.5
      set w to 1 - ((ak + bk) / ck) -- fractional change
      if w < 0 then set w to -w
      if w < z then exit repeat
      set ck to ak + bk
   end repeat
   
   return v + 2 * (x - 1) / (ak + bk)
end ln
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: Moving selecting images into new folder and add it

Postby FL_ » Tue Feb 19, 2019 7:23 pm

Thanks Eric, very kind!
I will try working with the script over the weekend and provide feedback early next week.

Best
Frank
FL_
 
Posts: 127
Joined: Mon May 18, 2015 10:11 pm

Re: Moving selecting images into new folder and add it

Postby FL_ » Wed Feb 20, 2019 7:28 pm

Hi Eric,
gave it a first try. Got

Expected variable name or property but found “*”.
with the cursor on the first star:
local *censored word*, sortedExtensionsList, extensionCount
Any suggestions?
Best
Frank
FL_
 
Posts: 127
Joined: Mon May 18, 2015 10:11 pm

Re: Moving selecting images into new folder and add it

Postby Eric Nepean » Thu Feb 21, 2019 4:37 am

FL_ wrote:Hi Eric,
gave it a first try. Got

Expected variable name or property but found “*”.
with the cursor on the first star:
local *censored word*, sortedExtensionsList, extensionCount
Any suggestions?
Best
Frank

I see what happened, but why has me mystified. I think it may be website content control software gone bonkers

In my script I use a variable named variant$ExtensionList (with the "$" replaced by "s").

It appears that some automated process in this website replaces every occurence of this string with *censored word*.

I tried to write it on the next line, and when I previewed it, it had been changed to *censored word* :o :o :lol:

In any case, if you use script editor, and change *censored word* to variantExtensionList (or any string which is a legal Applescript variable name) then the script should compile. There are 9 occurrences in all, type "command-F" script editor this brings up the find and replace bar, and from there you can step through each occurence, and you can replace all of them at once.

I raised a ticket with Phase One, and double checked the code that I've posted, replacing *censored word* with variant$ExtensionList ("$" means "s")

it worked for me.
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: Moving selecting images into new folder and add it

Postby FL_ » Thu Feb 28, 2019 7:44 pm

Thanks Eric, that solved it. Now the script is running and I can start playing with it (and provide feedback). Will take some time to work through it as it is quite sophisticated.

Best
Frank
FL_
 
Posts: 127
Joined: Mon May 18, 2015 10:11 pm


Return to Scripting



Who is online

Users browsing this forum: No registered users and 2 guests