Moving selecting images into new folder and add it
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
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
0
-
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.0 -
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 onlyMy 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
Frank0 -
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.
(**** 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 variantsExtensionList, sortedExtensionsList, extensionCount
set variantsExtensionList to U's summarizeList(piExtensionList, 0, false)
set sortedExtensionsList to makeExtensionsList(variantsExtensionList)
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(variantsExtensionList, "", "") & """))
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(variantsExtensionList)
global debugLogEnable, enableRawSequence, enabledRawFileTypeList, enableJpegSequence, enableTiffSequence
local fileExtensionsList, extPriorityList
set {fileExtensionsList, extPriorityList} to {{}, {}}
if enableRawSequence then
repeat with theFileType in variantsExtensionList
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 (variantsExtensionList contains "JPG") then ¬
set {fileExtensionsList, extPriorityList} to {(fileExtensionsList & {"JPG"}), (extPriorityList & {2})}
if enableTiffSequence then
if (variantsExtensionList contains "TIF") and (fileExtensionsList does not contain "TIF") then ¬
set {fileExtensionsList, extPriorityList} to {(fileExtensionsList & {"TIF"}), (extPriorityList & {3})}
if (variantsExtensionList 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 finalCleanup0 -
The second part. Copy and paste this into Script Editor's Script Window after the first part.
###########################################################################################################################
## 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 ln0 -
Thanks Eric, very kind!
I will try working with the script over the weekend and provide feedback early next week.
Best
Frank0 -
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
Frank0 -
[quote="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* 😲 😲 😂
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.0 -
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
Frank0
Post is closed for comments.
Comments
8 comments