(* 
GaMapICC v0.1

Droplet to convert TIFF images using Argyll's adaptive gamut mapping.
Copyright (C) 2008  Klaus Karcher

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*)

property version : 0.1

property env : "export ARGYLL_REV_CACHE_MULT=1; "
property debugpdisplay : false -- display debug dialogs
property debugcopy : false -- copy commands to clipboard for debuging
property logCommands : true -- write commands to logfile
property stickyEndNotification : true -- shall the last notification stay on screen?
property keepSequenceGamut : true -- shall the gamut file for a sequence profile be kept?

--- properties below will be changed at runtime

property destPR : "" -- destination profile
property destPRna : "" -- internal name of destination profile
property destVC : "" -- destination viewing condition (e.g " -dpp")
property destOpts : "" --  CMYK output related options for collink
property pfi : 100 -- Popularity filtering

property profs : {} -- list of known profiles
property vcs : {} -- list of collated viewing conditions

property vcl : {"pp - Practical Reflection Print", "pe - Print evaluation environment", "mt - Monitor in typical work environment", "mb - Bright monitor in bright work environment", "md - Monitor in darkened work environment", "jm - Projector in dim environment", "jd - Projector in dark environment", "pcd - Photo CD - original scene outdoors", "ob - Original scene - Bright Outdoors", "cx - Cut Sheet Transparencies on a viewing box"}

global tmpPath
global logFile

on run
	set ex to false
	tell application "Finder"
		try
			set ex to exists ((my destPR) as POSIX file)
		end try
	end tell
	if ex then
		display dialog "Current destination profile is:" & return & my destPR buttons {"Maintenance", "Change Profile", "OK"} default button 3 with title "Preferences"
		set br to button returned of result
		if br is "Change Profile" then
			my chooseProfile()
		else if br is "Maintenance" then
			display dialog "Maintenance" buttons {"Open cache folder", "Review viewing condition assignments", "Cancel"} with title "Maintenance"
			set br to button returned of result
			if br is "Open cache folder" then
				set cachePath to alias (((path to library folder from user domain) as text) & "Caches:" & text 1 thru -5 of (name of (info for (path to me))))
				tell application "Finder"
					activate
					open cachePath
				end tell
			else if br is "Review viewing condition assignments" then
				set l to {}
				repeat with i from 1 to count of profs
					set l to l & {(item i of profs & ": " & item i of vcs)}
				end repeat
				
				set dc to choose from list l with prompt "Review viewing condition assignments:" with title "Review assignments" OK button name "Delete assignment" with multiple selections allowed without empty selection allowed
				if class of dc is not list then error number -128
				set profs2 to {}
				set vcs2 to {}
				repeat with i from 1 to count of profs
					if dc does not contain item i of l then
						set profs2's end to item i of profs
						set vc2's end to item i of vcs
					end if
				end repeat
				set my profs to profs2
				set my vcs to vcs2
			end if
		end if
	else
		my chooseProfile()
	end if
end run

on open myItems
	activate me
	tell application "ColorSyncScripting" to launch
	tell application "ColorSyncScripting" to set quit delay to 30
	set ptm to POSIX path of (path to me)
	
	set ex to false
	tell application "Finder"
		try
			set ex to exists ((my destPR) as POSIX file)
		end try
	end tell
	if not ex then
		my chooseProfile()
	end if
	set oprofq to (the quoted form of POSIX path of (my destPR))
	
	set filelist to my filelistFromSelection(myItems)
	
	-- create temp folder
	set tmpPath to (POSIX path of (path to temporary items) & text 1 thru -5 of (name of (info for (path to me))) & (random number from 100000 to 999999) as text) & "/"
	set theCommand to "mkdir -p " & the quoted form of tmpPath
	
	set logFile to POSIX file (POSIX path of (path to library folder from user domain) & "Logs/" & text 1 thru -5 of (name of (info for (path to me))) & ".log")
	
	display dialog "Select gamut mapping strategy:" buttons {"Source Profile specific", "Sequence specific", "Image specific"} --default button 3
	set bu to button returned of result
	if bu is "Image specific" or bu is "Sequence specific" then
		if bu is "Image specific" then
			set ism to true
			set ssm to false
			set startloop to false
			set readytext to "Image specific"
		else
			set ism to false
			set ssm to true
			set startloop to true
			set readytext to "Sequence specific"
		end if
		display dialog "Set up Gamut Filtering: Percentage of colors used to constitute gamut surface" default answer (my pfi as text)
		set my pfi to (text returned of result) as number
		if pfi > 0 and pfi < 100 then
			set pifi to " -f " & (my pfi as text)
		else
			set pifi to ""
		end if
		
		my dss(theCommand)
	else
		set ism to false
		set ssm to false
		set startloop to false
		set readytext to "Source Profile specific"
	end if
	
	set readytext to "Ready to start " & readytext & " transformation to \"" & my destPRna & "\" with" & destVC
	if ism or ssm then
		set readytext to readytext & " and " & (my pfi as text) & "% gamut filtering?"
	else
		set readytext to readytext & "?"
	end if
	display dialog readytext
	
	-- process files
	set converted to 0
	repeat with thisFile in items of filelist
		set cont to true
		
		set {cont, fna, ext, pna, iopts} to my handleFile(thisFile, cont)
		if cont then
			--  check source viewing conditions
			if my profs does not contain pna then
				my addVC(pna)
			end if
			
			set pfileq to (the quoted form of POSIX path of ((thisFile) as text))
			-- source viewing condition for tiffgamut + collink
			set cvc to my getVC(pna)
			set cvc to " -c" & cvc
			
			-- name of deviceLink profile:
			set dlpname to pna & cvc & " " & my destPRna & my destVC
			set dlpname to my fnr(":", "_", dlpname) & ".icc"
			set iprofq to (the quoted form of POSIX path of (thisFile))
			
			if ism then
				-- image-specific gamut mapping
				-- calculate source gamut
				set theCommand to (the quoted form of (ptm & "Contents/Resources/tiffgamut") & " -ir -pj" & pifi & cvc & " " & pfileq & " " & pfileq)
				my dss(theCommand)

				-- create image-specific device link profile in temp folder
				set linkprofq to (the quoted form of (tmpPath & dlpname))
				set pfo to my getPFolderPOSIX(POSIX path of (thisFile))
				set gamutq to the quoted form of ((pfo & fna & ".gam") as text)
				my growlify("Creating temporary profile for image \"" & fna & "\".")
				set theCommand to (the quoted form of (ptm & "Contents/Resources/collink") & my destOpts & iopts & cvc & my destVC & " -ip -G " & gamutq & " " & iprofq & " " & oprofq & " " & linkprofq)
				my dss(theCommand)
				
				-- convert file using DLP from temp folder
				set oname to fna & "@" & my destPRna & "(is" & (my pfi as text) & ")." & ext
				set oname to my fnr(":", "_", oname)
				set pfo to my getPFolderPOSIX(POSIX path of (thisFile))
				set ofileq to the quoted form of (pfo & oname)
				my growlify("converting image " & oname)
				set theCommand to (the quoted form of (ptm & "Contents/Resources/cctiff") & " -d " & oprofq & " " & linkprofq & " " & iprofq & " " & ofileq)
				my dss(theCommand)
				
				-- remove gamut file				
				my dss("rm " & gamutq)
				-- remove DLP
				my dss("rm " & linkprofq)

			else if ssm then
				-- sequence specific gamut mapping
				if startloop then
					-- we are in the first loop of  sequence specific gamut mapping. 
					-- i.e. we have to collect all tiff paths for tiffgamut,
					-- check if all tiffs are all in the same colorspace
					-- create the gam file and DLP 
					-- and we have to transform the first image
					
					set startloop to false
					
					-- start second loop to collect tiffs
					set cont2 to true
					set tiffgamutfiles to pfileq
					my growlify("Collecting squence")
					repeat with thatFile in filelist
						set {cont2, fna2, ext2, pna2, iopts2} to my handleFile(thatFile, cont2)
						if cont2 then
							set pfileq2 to (the quoted form of POSIX path of ((thatFile) as text))
							if pna2 ≠ pna then
								-- profile mismatch
								set theCommand to "rm -rf " & the quoted form of tmpPath
								my dss(theCommand)
								error "Profile mismatch: file \"" & fna2 & " is not " & pna
							end if
							set tiffgamutfiles to tiffgamutfiles & " " & pfileq2
							set pfo to thatFile
						end if
					end repeat
					set pfo to my getPFolderPOSIX(POSIX path of (pfo))
					set gamutq to the quoted form of ((pfo & fna2 & ".gam") as text)
					set datestr to do shell script "date \"+%y-%m-%d_%H-%M-%S\""
					
					-- name for sequence profile
					set seqprofq to the quoted form of (pfo & "seqProf" & datestr & "(" & (my pfi as text) & ").icc")
					
					-- target name for gamut file 
					set sepgamq to the quoted form of (pfo & "seqProf" & datestr & "(" & (my pfi as text) & ").gam")
					
					-- calculate gamut union
					my growlify("Calculating gamut union")
					set theCommand to (the quoted form of (ptm & "Contents/Resources/tiffgamut") & " -ir -pj" & pifi & cvc & " " & tiffgamutfiles)
					my dss(theCommand)
					
					-- spotlight comment for profile
					set profComment to theCommand
					
					-- rename gamut file
					set theCommand to ("mv " & gamutq & " " & sepgamq)
					my dss(theCommand)

					-- create scene-specific device link profile
					my growlify("Creating profile for image sequence " & seqprofq & ".")
					set theCommand to (the quoted form of (ptm & "Contents/Resources/collink") & my destOpts & iopts & cvc & my destVC & " -ip -G " & sepgamq & " " & iprofq & " " & oprofq & " " & seqprofq)
					my dss(theCommand)
					set profComment to profComment & "; " & theCommand
					
					tell me to set the clipboard to profComment
					try
						tell application "Finder"
							if (count of profComment) > 750 then
								set comment of file ((text 2 thru -2 of seqprofq as POSIX file) as alias) to "... " & (text -746 thru -1 of profComment)
							else
								set comment of file ((text 2 thru -2 of seqprofq as POSIX file) as alias) to profComment
							end if
						end tell
					end try
					
					-- remove gamut file				
					if not keepSequenceGamut then
						do shell script "rm " & gamutq
					end if
					-- convert first file using secene-specific DLP
					set oname to fna & "@seqProf" & datestr & "(" & (my pfi as text) & ")." & ext
					set oname to my fnr(":", "_", oname)
					set pfo to my getPFolderPOSIX(POSIX path of (thisFile))
					set ofileq to the quoted form of (pfo & oname)
					my growlify("converting image " & oname)
					set theCommand to (the quoted form of (ptm & "Contents/Resources/cctiff") & " -d " & oprofq & " " & seqprofq & " " & iprofq & " " & ofileq)
					my dss(theCommand)
				else
					-- we are in the sequence specific gamut mapping loop
					-- and just have to convert the file with the 
					-- sequence-specific device link profile
					
					set oname to fna & "@sp" & datestr & "." & ext
					set oname to my fnr(":", "_", oname)
					set pfo to my getPFolderPOSIX(POSIX path of (thisFile))
					set ofileq to the quoted form of (pfo & oname)
					my growlify("converting image " & oname)
					set theCommand to (the quoted form of (ptm & "Contents/Resources/cctiff") & " -d " & oprofq & " " & seqprofq & " " & iprofq & " " & ofileq)
					my dss(theCommand)
				end if
			else
				-- source profile specific gamut mapping
				-- lookup cache
				set cachePath to POSIX path of (path to library folder from user domain) & "Caches/" & text 1 thru -5 of (name of (info for (path to me))) & "/"
				try
					set hit to alias ((path to library folder from user domain as text) & "Caches:" & text 1 thru -5 of (name of (info for (path to me))) & ":" & dlpname)
				on error
					set hit to ""
				end try
				
				if hit = "" then
					-- no cache hit
					-- create cache folder when necessary 
					set theCommand to "mkdir -p " & the quoted form of cachePath
					my dss(theCommand)
					
					-- create dlp in cache folder
					set iprofq to (the quoted form of POSIX path of (thisFile))
					set linkprofq to (the quoted form of (cachePath & dlpname))
					my growlify("Creating device link profile \"" & dlpname & "\".")
					set theCommand to (the quoted form of (ptm & "Contents/Resources/collink") & my destOpts & iopts & cvc & my destVC & " -ip -G " & iprofq & " " & oprofq & " " & linkprofq)
					my dss(theCommand)
				else
					-- DLP already exists
					set linkprofq to (the quoted form of (cachePath & dlpname))
				end if
				
				-- convert image with DLP from cache folder
				set oname to fna & "@" & my destPRna & "." & ext
				set oname to my fnr(":", "_", oname)
				set pfo to my getPFolderPOSIX(POSIX path of (thisFile))
				set ofileq to the quoted form of (pfo & oname)
				set msg to "Converting image \"" & oname & "\""
				if hit ≠ "" then
					set msg to msg & " with cached profile."
				else
					set msg to msg & "."
				end if
				my growlify(msg)
				
				set theCommand to (the quoted form of (ptm & "Contents/Resources/cctiff") & " -d " & oprofq & " " & linkprofq & " " & iprofq & " " & ofileq)
				my dss(theCommand)
				
			end if
			
			-- embed destination profie
			-- set theCommand to "sips --embedProfile " & oprofq & " " & ofileq
			-- my dss(theCommand)
			
			set converted to converted + 1
		end if
	end repeat
	
	-- delete temp folder
	set theCommand to "rm -rf " & the quoted form of tmpPath
	my dss(theCommand)
	
	-- display end notification
	set msg to displayed name of (info for (path to me)) & " is finished. " & converted & " image(s) have been converted."
	set ssc to the quoted form of (ptm & "Contents/Resources/growlnotify") & " -m" & the quoted form of msg
	if stickyEndNotification then
		set ssc to ssc & " -s"
	end if
	try
		set noti to true
		set res to do shell script (ssc & " 2>&1")
		if res ≠ "" then set noti to false
	on error
		set noti to false
	end try
	try
		if not noti then display dialog msg buttons {"OK"} default button 1 giving up after 300
	end try
end open

on chooseProfile()
	tell application "ColorSyncScripting"
		set pl to get name of every profile
	end tell
	set pl2 to {}
	repeat with i from 1 to count of pl
		if pl2 does not contain item i of pl then
			set pl2 to pl2 & item i of pl
		end if
	end repeat
	set pr to choose from list pl2 with prompt "Choose destination profile:" with title "Choose Profile" without multiple selections allowed and empty selection allowed
	if class of pr is not list then error number -128
	set pr to item 1 of pr
	tell application "ColorSyncScripting"
		set pl to location of every profile whose name is pr
	end tell
	set pl2 to {}
	repeat with i from 1 to count of pl
		set pl2 to pl2 & POSIX path of (item i of pl)
	end repeat
	if (count of pl) > 1 then
		set pr2 to choose from list pl2 with prompt "Choose destination profile:" with title "Choose Profile" without multiple selections allowed and empty selection allowed
		if class of pr2 is not list then
			error number -128
		else
			set pr2 to item 1 of pr2
		end if
	else
		set pr2 to item 1 of pl2
	end if
	tell application "ColorSyncScripting"
		tell (first profile whose location is (POSIX file pr2))
			if color space of it is CMYK then
				set my destOpts to " -ke"
			else
				set my destOpts to ""
			end if
		end tell
	end tell
	set my destVC to " -d" & my getVC(pr)
	set my destPR to pr2 as text
	set my destPRna to pr
end chooseProfile

on addVC(na)
	set vc to choose from list my vcl with prompt "Select viewing conditions for \"" & na & "\":" with title "Select viewing conditions" without multiple selections allowed and empty selection allowed
	if class of vc is not list then error number -128
	set vc to item 1 of vc
	set my profs to my profs & {na}
	set my vcs to my vcs & {(text 1 thru ((offset of " " in vc) - 1) of vc)}
end addVC

on getVC(na)
	if my profs contains na then
		set vc to item (my list_position(na, my profs)) of my vcs
		return vc
	else
		my addVC(na)
		my getVC(na)
	end if
end getVC

--returns dragged files and files inside dragged folders
on filelistFromSelection(theselection)
	set filelist to {} -- as alias list
	repeat with thisItem in (theselection as list)
		tell application "Finder"
			set cl to class of item thisItem
		end tell
		if cl is «class cfol» then
			tell application "Finder"
				set sl to (every item of folder (thisItem)) as alias list
			end tell
			set filelist to filelist & filelistFromSelection(sl)
		else
			set filelist to filelist & thisItem
		end if
	end repeat
	
	return filelist
end filelistFromSelection

on list_position(this_item, this_list)
	repeat with i from 1 to count of this_list
		if item i of this_list is this_item then return i
	end repeat
	return 0
end list_position

-- find and replace
on fnr(searchstr, replacestr, txt)
	considering case, diacriticals and punctuation
		if txt contains searchstr then
			set olddelims to AppleScript's text item delimiters
			set AppleScript's text item delimiters to {searchstr}
			set txtitems to text items of txt
			set AppleScript's text item delimiters to {replacestr}
			set txt to txtitems as Unicode text
			set AppleScript's text item delimiters to olddelims
		end if
	end considering
	return txt
end fnr

-- get parent folder of posix path as posix path
on getPFolderPOSIX(a)
	set a to (alias (POSIX file a))
	tell application "Finder"
		set a to POSIX path of ((container of item a) as alias)
	end tell
	return a
end getPFolderPOSIX

on handleFile(thisFile, cont)
	tell application "Finder"
		set fna to name of thisFile
	end tell
	my growlify("Processing File \"" & fna & "\".")
	if (name extension of (info for thisFile)) is missing value then
		-- file -bI: image/tiff
		set fbi to do shell script "file -bI " & (the quoted form of POSIX path of (thisFile))
		if fbi does not contain "/tiff" then
			tell me to display dialog fna & ": can't handle " & fbi & " files." buttons {"OK"}
			set cont to false
		end if
		set ext to "tif"
	else
		set ext to name extension of (info for thisFile)
		set fna to text 1 thru ((offset of ("." & ext) in fna) - 1) of fna
		if ext ≠ "tif" and ext ≠ "tiff" and ext ≠ "TIF" and ext ≠ "TIFF" then
			set cont to false
		end if
	end if
	if cont then
		-- get file / profile informations
		try
			tell application "ColorSyncScripting"
				set im to open thisFile
				tell im
					set {pna, dc, cs, ve} to {name, device class, color space, version} of profile 1 of it
					if dc is monitor then
						set dc to "Monitor"
					else if dc is input then
						set dc to "Input"
					else if dc is output then
						set dc to "Output"
					else
						tell me to display dialog fna & ": can't handle " & dc & " profiles." buttons {"OK"}
						set cont to false
					end if
					if cs is CMYK and my destOpts ≠ "" then
						-- input *and* output profile is CMYK
						-- force K only neutral colors to be K only output
						set iopts to " -fk"
					else
						set iopts to ""
					end if
					if cont and (ve ≥ 3) then
						tell me to display dialog fna & ": can't handle ICC v" & ve & " profiles." buttons {"OK"}
						set cont to false
					end if
					
				end tell
				close im
			end tell
		on error
			display dialog fna & ": can't open image or embedded profile"
			try
				tell application "ColorSyncScripting" to close im
			end try
			set cont to false
		end try
	end if
	return {cont, fna, ext, pna, iopts}
end handleFile

on dss(command)
	if my debugpdisplay then display dialog command
	if my debugcopy then tell me to set the clipboard to command
	set da to do shell script "date \"+%y-%m-%d_%H-%M-%S\""
	if my logCommands then
		try
			open for access my logFile with write permission
			write "---------------------" & return & da & return & command & return to my logFile starting at eof
			close access my logFile
		end try
	end if
	try
		set res to ""
		set res to do shell script env & command
		return res
	on error errMsg
		set bugrep to "Please send this bug report to email@hiden." & return & return & da & " command:" & return & env & command & return & return & "result:" & return & res & return & return
		set da to do shell script "date \"+%y-%m-%d_%H-%M-%S\""
		set bugrep to bugrep & da & " errormesssage:" & return & errMsg
		try
			open for access my logFile with write permission
			if not my logCommands then
				write "---------------------" & return & da & return & command & return to my logFile starting at eof
			end if
			write "--- === ERROR === ---" & return & da & return & res & return to my logFile starting at eof
			write "--- Error Message ---" & return & errMsg & return to my logFile starting at eof
			close access my logFile
		end try
		tell me to set the clipboard to bugrep
		error ((name of (info for (path to me))) as text) & " got an error. Details have been copied to the clipboard."
	end try
end dss

on growlify(notice)
	set ptm to POSIX path of (path to me)
	try
		do shell script (the quoted form of (ptm & "Contents/Resources/growlnotify") & " -m" & the quoted form of notice)
	on error
		return false
	end try
	return true
end growlify