Script to copy images by EXIF date of shooting...

Hi :slight_smile:

Maybe it already exists? This will avoid starting from scratch?

My problem is very simple. But I can not solve it only with the Finder search function. (I’ve try with Raw Query, but not nought powerful).

I have thousands of pictures over several months (a Very Long TimeLapse):

  • I give a source folder (with sub folders !)
  • I give a start time in the morning
  • I give an end time in the evening
  • I give an interval X (eg every 6 minutes)
  • I give a destination folder

Then the script starts from the very first picture (from the creation date from EXIF data), and for each day, only includes photos taken between the start hour and end hour. And in this interval, it selects a photo X minutes after the last. If there are none: after X minutes + 1. If there are none: X minutes + 2… until finding a photo.
Thus, it ignores photos too close (less than X minutes between each photo).
Finally, each selected photo is virtually copied to another folder: not a true copy. Not an alias. Just a “hard link”. Thus, it does not take place.

Any suggestion ? Any existing script(s) wich I could base my script on ?

Help ! :smiley:

Vincent.

Hi, there!

I think you should be do some thing:

  1. Read your images files from folders/subfolders with some filters like as:

property rasterTypeIDsList : {"public.png", "com.adobe.photoshop-image", "dyn.ah62d4rv4ge81a65c", "public.tiff"}
property vectorTypeIDsList : {"com.adobe.illustrator.ai-image", "com.adobe.encapsulated-postscript", "com.adobe.pdf"}

  1. Then you need to get EXIF data from your files:
    http://macscripter.net/viewtopic.php?id=44419

  2. Filter selected images from the data obtained from EXIF

  3. Save to selected destination.

But i’m sorry, what is a “hard link”?

Hi,

Thanks :slight_smile: I’m not enough good in AS to make the script from scratch. That’s why I said “Maybe it already exists? This will avoid starting from scratch?”. But It was not clear. Sorry :wink:
I’ve read your link, but can’t found a start of solution. Because about EXIF, I have to search simply for this entry : kMDItemDateAdded (http://photophindings.blogspot.fr/2012/02/searching-exif-data-with-mac-os-x.html)

About hard link : It’s used for example by TimeMachine to have apparently several identical files, but with only one real data on the drive. http://forums.macnn.com/90/mac-os-x/17854/hard-link-vs-symbolic-link/

I think you need to look here:

http:_//_macosxautomation.com/applescript/learn.html

There alot of AS solutions/samples and other useful informations.

Here a simple AS for recursive read images files.


-- QuickTime supported image formats
property rasterTypeList : {"JPEG", "8BPS", "8BPB", "TIFF", "PNGf"}
property rasterExtensionList : {"jpg", "jpeg", "psd", "psb", "tif", "tiff", "png"}
property rasterTypeIDsList : {"public.png", "public.jpeg", "com.adobe.photoshop-image", "dyn.ah62d4rv4ge81a65c", "public.tiff"}

-- Vector images files formats
property vectorTypeList : {"EPSP", "PDF"}
property vectorExtensionList : {"ai", "eps", "pdf"}
property vectorTypeIDsList : {"com.adobe.illustrator.ai-image", "com.adobe.encapsulated-postscript", "com.adobe.pdf"}

property vectorFilesPath : {} -- vector files paths
property rasterFilesPath : {} -- raster files paths

-- This droplet processes files dropped onto the applet 

on open droppeditems
	my setFilesList(droppeditems)
	
	set vectorFilesCount to the count of the vectorFilesPath
	set rasterFilesCount to the count of the rasterFilesPath
	
	if vectorFilesCount < 1 and rasterFilesCount < 1 then
		-- select nothing
		return
	end if
	
	-- do some stuff here
	
end open

on setFilesList(droppeditems)
	repeat with i from 1 to the count of droppeditems
		set this_item to item i of droppeditems
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else
			try
				set this_extension to the name extension of item_info
			on error
				set this_extension to ""
			end try
			try
				set this_filetype to the file type of item_info
			on error
				set this_filetype to ""
			end try
			try
				set this_typeID to the type identifier of item_info
			on error
				set this_typeID to ""
			end try
			if (folder of the itemInfo is false) and (alias of the itemInfo is false) and ((thisFiletype is in the vectorTypeList) or (thisExtension is in the vectorExtensionList) or (thisTypeID is in vectorTypeIDsList)) then
				copy this_item to end of vectorFilesPath
			else if (folder of the itemInfo is false) and (alias of the itemInfo is false) and ((thisFiletype is in the rasterTypeList) or (thisExtension is in the rasterExtensionList) or (thisTypeID is in rasterTypeIDsList)) then
				copy this_item to end of rasterFilesPath
			end if
		end if
	end repeat
end setFilesList

-- this sub-routine processes folders 
on process_folder(this_folder)
	set these_items to list folder this_folder without invisibles
	repeat with i from 1 to the count of these_items
		set this_item to alias ((this_folder as Unicode text) & (item i of these_items))
		set the item_info to info for this_item
		if folder of the item_info is true then
			process_folder(this_item)
		else
			try
				set this_extension to the name extension of item_info
			on error
				set this_extension to ""
			end try
			try
				set this_filetype to the file type of item_info
			on error
				set this_filetype to ""
			end try
			try
				set this_typeID to the type identifier of item_info
			on error
				set this_typeID to ""
			end try
			if (folder of the itemInfo is false) and (alias of the itemInfo is false) and ((thisFiletype is in the vectorTypeList) or (thisExtension is in the vectorExtensionList) or (thisTypeID is in vectorTypeIDsList)) then
				copy this_item to end of vectorFilesPath
			else if (folder of the itemInfo is false) and (alias of the itemInfo is false) and ((thisFiletype is in the rasterTypeList) or (thisExtension is in the rasterExtensionList) or (thisTypeID is in rasterTypeIDsList)) then
				copy this_item to end of rasterFilesPath
			end if
		end if
	end repeat
end process_folder

Ok… it’s VERY TOO much complicated for me :o :frowning:

So, I’ve found part of solution : I’ve found a way to rename all my photos with EXIF date like “YYYMMDD-hhmmss.jpg”

Then, I’ll put all this renamed photos in one folder.
Then, in lightroom, I’ll remove all “night” pictures.

Thus, I only need a much more simple script to analyse just the name of each file : I need to extract “mm”. If “mm” of a photo is inferior to “mm”+X of the last photo, I add a “red” tag to this wrong photo. The I test the next photo… etc…

Finally, I’ll sort my photo by tags in finder, to select only non-red-tagged pictures.

Is it more simple to find a script near what I need ? :slight_smile:

Ok, good luck :slight_smile:

Well, finally I got into it. I have a stupid bug I think! He told me that my variable “hmancien” is not defined! And yet I well defined in the routine “faisLalisteDesElements” through which one has to pass before you use this variable in the routine “traiteFichier”. I do not understand.

-- fonction de déclanchement de la droplet
on open (LesElementsDeposes)
	activate
	separeDossiersFichiers(LesElementsDeposes) -- Sépare les dossiers des fichiers
end open

-- fonction au lancement si on doubleclic sur la droplet ou si on l'éxécute par le menu script ou l'éditeur de script
on run
	set LesElementsDeposes to choose folder with prompt "Choisissez le dossier à explorer" without invisibles
	separeDossiersFichiers(LesElementsDeposes as list)
end run

-- Traitement des dossiers
on traiteDossier(lElement)
	set CheminDossierChaine to lElement as string
	--	display dialog ("Dossier: " & return & CheminDossierChaine) giving up after 1 -- affiche le nom du dossier
	faisLalisteDesElements(CheminDossierChaine) -- pour explorer le contenu des sous-dossiers
end traiteDossier

-- Separe les dossiers des fichiers
on separeDossiersFichiers(LesElementsASeparer)
	repeat with lElement in LesElementsASeparer -- Pour tous les éléments
		if kind of (info for lElement) is in {"Folder", "Dossier"} then -- si c'est un dossier
			traiteDossier(lElement) -- Action pour le dossier important pour aller chercher son contenu
		else
			traiteFichier(lElement) -- Action sur le fichier
		end if
	end repeat
end separeDossiersFichiers

-- Fais la liste des éléments du dossier -- Garder intact pour l'exploration des sous-dossiers
on faisLalisteDesElements(CheminDossierChaine)
	
	set hmancien to "0" as integer
	
	tell application "Finder"
		set liste to every item of folder CheminDossierChaine -- liste tous les éléments du dossier
		repeat with lElement in liste -- pour chaque élément
			if kind of (lElement as alias) is in {"Folder", "Dossier"} then -- si c'est un dossier
				my traiteDossier(lElement as alias) -- Traite le dossier
			else
				my traiteFichier(lElement as alias) -- Sinon traite le fichier
			end if
		end repeat
	end tell
end faisLalisteDesElements

-- Traitement des fichiers déposé sur cette Droplet.
on traiteFichier(lElement)
	set CheminFichierChaine to lElement as string
	set AppleScript's text item delimiters to {":"}
	set nomfichier to get last text item of CheminFichierChaine
	set heuresminutes to text 10 thru -11 of nomfichier
	set hmactuel to heuresminutes as integer
	--display dialog ("Heure de prise de vue : " & return & heuresminutes) giving up after 1 -- affiche le nom du fichier
	set myPF to POSIX path of CheminFichierChaine
	tell application "Finder"
		set label index of (POSIX file myPF as alias) to 0
	end tell
	if hmactuel > (hmancien + 2) then
		set myPF to POSIX path of CheminFichierChaine
		tell application "Finder"
			set label index of (POSIX file myPF as alias) to 4
		end tell
		set hmancien to hmactuel
	else
		set myPF to POSIX path of CheminFichierChaine
		tell application "Finder"
			set label index of (POSIX file myPF as alias) to 2
		end tell
	end if
	
end traiteFichier

I’ve resolved it by adding this on top of my script :

property hmancien : 0

That works, yes, but it covers up the actual cause of the error. Which will cause you more trouble in the future.

Variables declared in a handler are only known to that handler.
You declare hmancien in handler faisLalisteDesElements, but do not use it there.
It is only used in handler traiteFichier, so it should be declared there.
Delete set hmancien to “0” as integer, and add a line to the top of the 2nd handler: set hmancien to 0.
And delete the property.

Hi, and thanks for your help.

Yes, hmancien seems to be only used in handler traiteFichier. But each time I use traiteFichier, I d’ont want to initialize hmancien to 0. hmancien is used to memorize the time of the last file before the actual file.

Since this code, I’ve worked more. Here my new version : I’ve also discovered that property keep values even after close the app !!! Now I use global. It works now. What do you think ?

global hmancien
global minutesentredeuxphotos
global heuredebutjournee
global heurefinjournee
global DossierHardLinks

-- fonction au lancement / at launch
on run
	set DossierSource to choose folder with prompt "Choisissez le dossier contenant les images" without invisibles
	display dialog "IMPORTANT
- Noms de fichier : YYYYMMDD_hhmmss...
- Tous les fichiers à la racine" buttons {"Quitter", "Continuer"} default button 2
	if the button returned of the result is "Quitter" then
		quit
	end if
	display dialog "Heure de début chaque jour (hhmm)" default answer "0800" buttons {"Continuer"} default button 1
	set heuredeb to (text returned of the result) as string
	set heuredebutjournee to heuredeb as integer
	display dialog "Heure de fin chaque jour (hhmm)" default answer "1700" buttons {"Continuer"} default button 1
	set heurefin to (text returned of the result) as string
	set heurefinjournee to heurefin as integer
	display dialog "Interval minimum entre deux images (mm)" default answer "2" buttons {"Continuer"} default button 1
	set minutesentredeuxphotos to (text returned of the result) as integer
	set NomDossierHardLink to "_Selection_HardLinks_de_" & heuredeb & "_a_" & heurefin & "_interval_mini_" & minutesentredeuxphotos & "_minutes" as string
	tell application "Finder"
		make new folder at DossierSource with properties {name:NomDossierHardLink}
		set DossierHardLinks to (DossierSource & NomDossierHardLink) as string
		set DossierHardLinks to DossierHardLinks as alias
	end tell
	set hmancien to 0
	faisLalisteDesElements(DossierSource as list)
end run

-- Fais la liste des éléments du dossier / List of files in the folder
on faisLalisteDesElements(CheminDossierChaine)
	tell application "Finder"
		set liste to every item of folder CheminDossierChaine -- liste tous les éléments du dossier
		repeat with lElement in liste -- pour chaque élément
			if kind of (lElement as alias) is in {"Folder", "Dossier"} then -- si c'est un dossier, ne fait rien
				
			else
				my traiteFichier(lElement as alias) -- Sinon traite le fichier
			end if
		end repeat
	end tell
end faisLalisteDesElements

-- Traitement des fichiers / For each file
on traiteFichier(lElement)
	set CheminFichierChaine to lElement as string
	set AppleScript's text item delimiters to {":"}
	set nomfichier to get last text item of CheminFichierChaine
	set heuresminutes to text 10 thru 13 of nomfichier
	set hmactuel to heuresminutes as integer
	--display dialog ("Heure de prise de vue : " & return & heuresminutes) giving up after 1 -- affiche le nom du fichier
	set myPF to POSIX path of CheminFichierChaine
	--tell application "Finder"
	--	set label index of (POSIX file myPF as alias) to 0
	--end tell
	if hmactuel > (hmancien + minutesentredeuxphotos) and hmactuel > heuredebutjournee and hmactuel < heurefinjournee then
		set myPF to POSIX path of CheminFichierChaine
		--tell application "Finder"
		--	set label index of (POSIX file myPF as alias) to 6
		--end tell
		set trg_pp to quoted form of (POSIX path of DossierHardLinks)
		set src_pp to quoted form of (POSIX path of CheminFichierChaine)
		do shell script "ln " & src_pp & space & trg_pp
		set hmancien to hmactuel
	else
		set myPF to POSIX path of CheminFichierChaine
		if hmactuel < hmancien then set hmancien to 0
		--tell application "Finder"
		--	set label index of (POSIX file myPF as alias) to 2
		--end tell
	end if
	
end traiteFichier

Don’t! Don’t use globals unless there’s a good reason.

  • you have to keep track of their value at all times
  • you will use a variable locally, having forgotten that it’s a global as well, and your script will misbehave
  • as soon as you start reusing code similar problems will multiply
  • and so on

And, as I did say earlier, making a variable global may cover up the actual problem that you thought to solve.
The correct way is to pass variables to handlers:
Change this

on traiteFichier(lElement)

to this

on traiteFichier(lElement, DossierHardLinks)

and you will have passed the value of DossierHardLinks to the handler - no need for the global.

A few other things:

  • you don’t need the run statements
  • text returned is already text, no need for as string
  • as string is “old style”, it should be as text

I recommend reading the stuff in the Unscripted section, and getting a book.

Try to set them to initial values when start.



property yourVariableOne : missing value
property yourVariableTwo : 0 as integer
property yourVariableThree : {}

on run

set my yourVariableOne to missing value 
set my yourVariableTwo to 0 as integer
set my yourVariableThree to {}

-- some stuff goes here

end run