Code Critique? "Save graphics to web JPEG" script…

Leaving-out command-line methods (which is still a bit beyond me at the moment), any possibilities for improvements or any “gotchas” I should fix?

Premise of script: take high-res graphics from the design repository (PSD, JPEG, TIFF, EPS, and even AI) and convert them to fit within a predefined space (without padding). Script must handle any combination of files and folders dragged onto it drag-n-drop style.

Going to move this to FaceSpan for a better user input experience (less separate dialogs), but otherwise does everything I need it to that I can think of.

Anyone with PHP experience with graphics would be a plus”we still have a current issue where Photoshop-generated JPEGs sometimes confuse PHP5 (which serves the graphics). Seems PHP5 adheres strictly to the JPEG standards and doesn’t like the “extra” stuff Photoshop stores in the JPEG files (or so the web guy tells me). Annoyingly, sometimes we have to size them with this script, then make a second pass with GIMP to make sure the JPEG files are strict to standards.

--
-- DECLARE PROPERTIES
--
-- debugging on?
property g_debug : false

--basic file path and names
property g_home_folder_path : path to home folder
property g_log_file_name : "Resize Image for Web Log.txt"



--
-- UTILITY HANDLERS
--

--Log Entry Generation
-- With help from StephanK of MacScripter
-- http://bbs.applescript.net/viewtopic.php?pid=76607#p76607
--
on logMe(log_string, indent_level)
	if g_debug is true then --allows turning the debugger on and off so my logMe's can be left in final version
		set log_target to (g_home_folder_path & "Library:Logs:" & g_log_file_name) as text
		try
			set log_file_ref to open for access file log_target with write permission
			repeat indent_level times
				write tab to log_file_ref starting at eof
			end repeat
			write ((log_string as text) & return) to log_file_ref starting at eof
			close access log_file_ref
			return true
		on error
			try
				close access file log_target
			end try
			return false
		end try
	end if
end logMe

--
-- MAIN SCRIPT
--
on open actionItems
	
	-- remove folders from list
	--
	-- courtesy of Adam Bell of MacScripter
	-- http://bbs.applescript.net/viewtopic.php?pid=77739#p77739
	--
	set dropped_items to {}
	
	repeat with current_actionItem in actionItems
		tell application "Finder"
			if (contents of current_actionItem as text) ends with ":" then
				try
					set dropped_items to dropped_items & (files of (entire contents of current_actionItem) as alias list)
				on error -- only one file inside (works around bug)
					set dropped_items to dropped_items & (files of (entire contents of current_actionItem) as alias as list)
				end try
			else
				set dropped_items to dropped_items & current_actionItem
			end if
		end tell
	end repeat
	
	--gather user-entered height, width, and JPEG quality
	set h to display dialog "Height (pixels):" default answer ""
	set new_height to text returned of h
	
	set w to display dialog "Width (pixels):" default answer ""
	set new_width to text returned of w
	
	set j to display dialog "JPEG Quality:" buttons {"Medium", "High", "Maximum"} default button "Maximum"
	set JPEG_quality to button returned of j
	
	set n to display dialog "File Suffix:" default answer "_web"
	set file_suffix to text returned of n
	
	set save_location to choose folder with prompt "Choose a location to save converted files:"
	
	--convert user answer to JPEG quality to Photoshop's numeric notation
	if JPEG_quality = "Medium" then
		set JPEG_quality to 5
	else
		if JPEG_quality = "High" then
			set JPEG_quality to 8
		else
			if JPEG_quality = "Maximum" then
				set JPEG_quality to 12
			end if
		end if
	end if
	
	--step through files
	repeat with i from 1 to number of items of dropped_items
		set parseMeString to (item i of dropped_items) as string
		set parseMeStringPOSIX to POSIX path of parseMeString
		
		--get file name without extension
		tell (info for item i of dropped_items without size) to set {file_name, file_extension} to {name, name extension}
		set file_name to text 1 thru -((count file_extension) + 2) of file_name
		
		--rename file to indicate it was converted by script
		set new_file_name to file_name & file_suffix & ".jpg"
		
		try
			tell application "Adobe Photoshop CS4"
				activate
				open file (parseMeStringPOSIX as text)
				
				--assuming user is using pixels, convert document to same
				set ruler units of settings to pixel units
				
				--fix pixel aspect ratio
				set pixel aspect ratio of current document to 1.0
				
				tell current document
					--get height and width of current document
					set current_height to height
					set current_width to width
					
					--flatten image
					flatten
					
					--make sure is 72 dpi (PHP is very literal about this)
					resize image resolution 72 resample method none
					
					--change to RGB (CMYK not web compatible)
					change mode to RGB
					
				end tell
				
				--remove extra channels
				set safe_channels to {"Red", "Green", "Blue"}
				
				set channel_names to name of channels of current document
				
				repeat with c from 1 to number of items in channel_names
					if (item c of channel_names) is not in safe_channels then
						delete channel (item c of channel_names) of current document
					end if
				end repeat
				
				--figure out reduction percentages
				set height_ratio to new_height / current_height
				set width_ratio to new_width / current_width
				
				--make sure is not enlargement
				if (height_ratio < 1) or (width_ratio < 1) then
					
					--pick out smallest reduction and use that
					if height_ratio ≤ width_ratio then
						resize image current document height new_height resample method bicubic
					else
						if width_ratio < height_ratio then
							resize image current document width new_width resample method bicubic
						end if
					end if
				end if
				
				(* Depricated in favor of letting user choose)
				--create destination folder on Desktop
				tell application "Finder"
					try
						make new folder at (path to desktop) with properties {name:"Images Converted for Web"}
					end try
				end tell
				*)
				
				--create path to destination
				set new_file_location to ((save_location as Unicode text) & new_file_name)
				
				--create reference (required for Photoshop saves)
				set new_file_reference to (a reference to file new_file_location)
				
				--save and close file
				save current document appending lowercase extension as JPEG in new_file_reference with options {embed color profile:false, format options:optimized, quality:JPEG_quality}
				close current document
				
				--set completed file label to green
				tell application "Finder" to set label index of (item i of dropped_items) to "6" as text
				
			end tell
		on error
			activate
			display alert file_name message "Unable to process this file, it will be skipped."
			
			--set uncompleted file label to red
			tell application "Finder" to set label index of (item i of dropped_items) to "2" as text
		end try
	end repeat
end open

Hi.
Could the extra data be a color profile? If you can’t isolate the source of the problem, you may be better off using an app other than PS to do the saving. You don’t want to resave JPEGS”ever”due to generation loss.

Regarding the code: I think you might be able to nix the channel riddance portion, altogether, if a save channels boolean exists for this format; I know it exists for other formats, but I can’t check this, as I don’t have CS4.
The Finder text coercions to the label index are not needed, as that’s a number.

  tell application "Finder" to set label index of (item i of dropped_items) to 2

Rather than use PS’s save command, use its export command, which is similar to using export for web in the UI. You’ll get jpegs without all the metadata and stuff that way.

Already covered during the save by embed color profile:false. It’s possible in previous iterations that was the problem. I’ve eliminated alot more extraneous stuff since the first version. For example PHP5 does not like it when the dpi is anything other than 72, so that has to be explicitly set.

Well aware of this. However, when you’re going from 5000 pixels to 400 pixels, generational loss is moot (blur induced by the extreme reduction more than compensates).

I actually had thought of adding an Unsharp Mask after the reduction, but then I’d have to write a routine that decided when to apply it (i.e. if the reduction is below “X” threshold, apply Unsharp Mask). Or worse, tiered values based on a range of reductions. But too much testing would be involved, and in the end, the extra sharpness is not required for most uses. Maybe in the future, or when there are enough complaints.

I looked at the dictionary and it says it isn’t an option. Tried anyway, but it doesn’t work, and the “save copy” dialog box pops-up when alpha channels exist. So apparently they have to be removed ahead of the save. :frowning:

Good catch.fixed.

THANKS!

Photoshop CS4 doesn’t have an Export command for JPEGs (under Export anyway).

I did try the “Save for Web Export,” since in theory it should create more “clean” JPEGs (without all the Adobe extraneous stuff), but after a couple hours of staring at the dictionary and randomly trying things I just couldn’t get it to cooperate. Even the ScriptListener wasn’t much help in figuring out the mechanics of it. Possible it is some syntax thing (took me a while to figure out how to do the options for Save As).

Anyone have any known working examples? I’d really like to give the “Save for Web Export” idea another whirl.

THANKS!

Kevin, as you are processing PSD & TIFF that can include spot channels you may want to check the type of the extra channels before removal? I know I have to do this if kind is selected area ofr masked area then remove if spot then merge. You also have bicubic smoother and sharper as downsample methods may save using USM. I generally use sharper for internet use. SFW needs a work around in my CS2 to compile the code but works. Might be fixed in yours?

Some of our packaging mechanicals have spot color bump plates, but that’s not the source of the images for the web”those are always photographed elements (RGB or CMYK). Good thing to watch for though, for some folks.

I’ll keep that in mind in the future. And again, I’d be tempted to write code that matched a downsample method to the amount of reduction being performed. Bicubic is the default, which is “good enough for now.” I’d also prefer the control available using Unsharp Mask. But I’ll keep both methods in mind if I ever get to that stage of fine-tuning. :slight_smile:

Can you provide a sample “Save for Web” code line for me to tinker with? Preferrably one with all the relative JPEG options selected? I simply may have been having syntax issues, but hunting for proper syntax blindly gets old fast. :rolleyes:

THANKS! :smiley:

Kevin, Im only CS2 and when this version of the app came out SFW had been rushed in and is probably the cause of many of the issues with this. You would need to create you own options and test to see if things have been fixed. SFW options are like any other property list {someProperty:SomeValue}

I used to keep this:

– For compiling C&P this in file type: «class fltp»:JPEG

As a line of syntax in my scripts so I could cut and paste to where ‘as:JPEG’ should be in the property list.
This has to be re-done with every edit and re-compile (RPITA).
However this does NOT want to work on my Leopard Intel box?

In my CS2 scripting guide the properties listed look more like there JavaScript counter parts ie:
‘includeProfile’ & ‘matteColor’ I could NOT get some of these to work as AppleScript app word pairs no matter what?

I for the most part now use JavaScript because of scriptlistener & bridge.

I can still use scriptlistener output to use in script here is a recorded SFW JPEG.

Without you app version I would NOT know if this would even work.

set SWF_JPEG to "" & ¬
	"function sfwJPEG() {
function cTID(s) { return app.charIDToTypeID(s); };
function sTID(s) { return app.stringIDToTypeID(s); };
var desc3 = new ActionDescriptor();
var desc4 = new ActionDescriptor();
desc4.putEnumerated( cTID('Op  '), cTID('SWOp'), cTID('OpSa') );
desc4.putEnumerated( cTID('Fmt '), cTID('IRFm'), cTID('JPEG') );
desc4.putBoolean( cTID('Intr'), false );
desc4.putInteger( cTID('Qlty'), 50 ); // Quality
desc4.putInteger( cTID('QChS'), 0 );
desc4.putInteger( cTID('QCUI'), 0 );
desc4.putBoolean( cTID('QChT'), false );
desc4.putBoolean( cTID('QChV'), false );
desc4.putBoolean( cTID('Optm'), true ); // Optimized
desc4.putInteger( cTID('Pass'), 1 );
desc4.putDouble( cTID('blur'), 0.000000 ); // Blur
desc4.putBoolean( cTID('EICC'), true ); // Profile
desc4.putBoolean( cTID('Mtt '), false ); // Matte
desc4.putInteger( cTID('MttR'), 255 );
desc4.putInteger( cTID('MttG'), 255 );
desc4.putInteger( cTID('MttB'), 255 );
desc4.putBoolean( cTID('SHTM'), false );
desc4.putBoolean( cTID('SImg'), true );
desc4.putBoolean( cTID('SSSO'), false );
var list1 = new ActionList();
desc4.putList( cTID('SSLt'), list1 );
desc4.putBoolean( cTID('DIDr'), true );
desc4.putPath( cTID('In  '), new File( '/Users/marklarsen/Desktop/untitled folder 5' ) ); // Just put parent folder here
desc3.putObject( cTID('Usng'), sTID('SaveForWeb'), desc4 );
executeAction( cTID('Expr'), desc3, DialogModes.NO );
};
sfwJPEG();" -- Function call

tell application "Adobe Photoshop CS2"
	set docRef to current document
	tell docRef
		do javascript SWF_JPEG show debugger on runtime error
		close docRef without saving
	end tell
end tell

If it does then you can edit (hard code the options) in the string or concat multi strings to contain AppleScript variables.

Ugh, so you had to resort to the JavaScript? Unless I get desperate, I think I’ll stick to the AppleScript I have.

I was hoping for something similar to the current “Save As with options”

save current document appending lowercase extension as JPEG in new_file_reference with options {embed color profile:false, format options:optimized, quality:JPEG_quality}

Much easier to cope with. :wink:

I only resort to having ScriptListener-generated JavaScript when I get truly stuck. Which I have done a couple times in Photoshop (my Clorox File Repair app being one of them, it has three simple JavaScripts).

There is a command for “Save for Web” it in the Photoshop dictionary, but I just couldn’t figure out what syntax it is expecting or even if I’m using the proper name for the command. It’s a bit convoluted to me: can’t tell if it’s supposed to be “save for web” or “export as save for web” or “save for web export,” or maybe it’s none of those”much less how to give it parameters. :confused:

It’s there – it’s the equivalent of Save for Web in the UI.

Try this:

export current document in file thePath as save for web with options {class:save for web export options, optimized size:true, quality:qualNum, interlaced:false, blur:0, with profile:true, web format:JPEG}

I thought I had it working but.

–I had to get rid of the “create reference” on the new file (Saves As needs a reference, Export doesn’t like it)
–I had to change the close line to say “without saving” (the Save As method creates a save/copy simultaneously, Export leaves the original kicking around, which I don’t want to save the changes to)
–Changed “with profile:true” to “with profile:false” because I don’t want the ICC profile kicking around

HOWEVER, while it appeared to run, the resulting file was corrupt:

–By default, it wants to load in Illustrator, not Preview or Photoshop
–Forcing it into Photoshop yields an error (JPEG marker segment length is too short), which is usually a corruption issue (like incomplete downloads)

Any ideas? New code is below (commented-out the TRY block so Photoshop errors would be visible, commented-out my original Save As code for comparison/reference).

(I’ll eventually remove the extraneous “prep” routines once I can get the export itself to work, right now I’m keeping them just to have a 1-to-1 comparison to my original method by swapping what I’m commenting-out.)

--
-- DECLARE PROPERTIES
--
-- debugging on?
property g_debug : false

--basic file path and names
property g_home_folder_path : path to home folder
property g_log_file_name : "Resize Image for Web Log.txt"



--
-- UTILITY HANDLERS
--

--Log Entry Generation
-- With help from StephanK of MacScripter
-- http://bbs.applescript.net/viewtopic.php?pid=76607#p76607
--
on logMe(log_string, indent_level)
	if g_debug is true then --allows turning the debugger on and off so my logMe's can be left in final version
		set log_target to (g_home_folder_path & "Library:Logs:" & g_log_file_name) as text
		try
			set log_file_ref to open for access file log_target with write permission
			repeat indent_level times
				write tab to log_file_ref starting at eof
			end repeat
			write ((log_string as text) & return) to log_file_ref starting at eof
			close access log_file_ref
			return true
		on error
			try
				close access file log_target
			end try
			return false
		end try
	end if
end logMe

--
-- MAIN SCRIPT
--
on open actionItems
	
	-- remove folders from list
	--
	-- courtesy of Adam Bell of MacScripter
	-- http://bbs.applescript.net/viewtopic.php?pid=77739#p77739
	--
	set dropped_items to {}
	
	repeat with current_actionItem in actionItems
		tell application "Finder"
			if (contents of current_actionItem as text) ends with ":" then
				try
					set dropped_items to dropped_items & (files of (entire contents of current_actionItem) as alias list)
				on error -- only one file inside (works around bug)
					set dropped_items to dropped_items & (files of (entire contents of current_actionItem) as alias as list)
				end try
			else
				set dropped_items to dropped_items & current_actionItem
			end if
		end tell
	end repeat
	
	--gather user-entered height, width, and JPEG quality
	set h to display dialog "Height (pixels):" default answer ""
	set new_height to text returned of h
	
	set w to display dialog "Width (pixels):" default answer ""
	set new_width to text returned of w
	
	set j to display dialog "JPEG Quality:" buttons {"Medium", "High", "Maximum"} default button "Maximum"
	set JPEG_quality to button returned of j
	
	set n to display dialog "File Suffix:" default answer "_web"
	set file_suffix to text returned of n
	
	set save_location to choose folder with prompt "Choose a location to save converted files:"
	
	--convert user answer to JPEG quality to Photoshop's numeric notation
	if JPEG_quality = "Medium" then
		set JPEG_quality to 5
	else
		if JPEG_quality = "High" then
			set JPEG_quality to 8
		else
			if JPEG_quality = "Maximum" then
				set JPEG_quality to 12
			end if
		end if
	end if
	
	--step through files
	repeat with i from 1 to number of items of dropped_items
		set parseMeString to (item i of dropped_items) as string
		set parseMeStringPOSIX to POSIX path of parseMeString
		
		--get file name without extension
		tell (info for item i of dropped_items without size) to set {file_name, file_extension} to {name, name extension}
		set file_name to text 1 thru -((count file_extension) + 2) of file_name
		
		--rename file to indicate it was converted by script
		set new_file_name to file_name & file_suffix & ".jpg"
		
		--try
		tell application "Adobe Photoshop CS4"
			activate
			open file (parseMeStringPOSIX as text)
			
			--assuming user is using pixels, convert document to same
			set ruler units of settings to pixel units
			
			--fix pixel aspect ratio
			set pixel aspect ratio of current document to 1.0
			
			tell current document
				--get height and width of current document
				set current_height to height
				set current_width to width
				
				--flatten image
				flatten
				
				--make sure is 72 dpi (PHP is very literal about this)
				resize image resolution 72 resample method none
				
				--change to RGB (CMYK not web compatible)
				change mode to RGB
				
			end tell
			
			--remove extra channels
			set safe_channels to {"Red", "Green", "Blue"}
			
			set channel_names to name of channels of current document
			
			repeat with c from 1 to number of items in channel_names
				if (item c of channel_names) is not in safe_channels then
					delete channel (item c of channel_names) of current document
				end if
			end repeat
			
			--figure out reduction percentages
			set height_ratio to new_height / current_height
			set width_ratio to new_width / current_width
			
			--make sure is not enlargement
			if (height_ratio < 1) or (width_ratio < 1) then
				
				--pick out smallest reduction and use that
				if height_ratio ≤ width_ratio then
					resize image current document height new_height resample method bicubic
				else
					if width_ratio < height_ratio then
						resize image current document width new_width resample method bicubic
					end if
				end if
			end if
			
			(* Depricated in favor of letting user choose)
				--create destination folder on Desktop
				tell application "Finder"
					try
						make new folder at (path to desktop) with properties {name:"Images Converted for Web"}
					end try
				end tell
				*)
			
			--create path to destination
			set new_file_location to ((save_location as Unicode text) & new_file_name)
			
			(* Not needed with Export
			--create reference (required for Photoshop saves)
			set new_file_reference to (a reference to file new_file_location)
			
			--my original save routine
			--
			--save and close file (my original routine
			save current document appending lowercase extension as JPEG in new_file_reference with options {embed color profile:false, format options:optimized, quality:JPEG_quality}
			*)
			
			--save and close file
			--
			--provided by Shane Stanley of MacScripter (http://macscripter.net/viewtopic.php?pid=124262#p124262)
			export current document in file new_file_location with options {class:save for web export options, optimized size:true, quality:JPEG_quality, interlaced:false, blur:0, with profile:false, web format:JPEG}
			close current document without saving
			
			--set completed file label to green
			tell application "Finder" to set label index of (item i of dropped_items) to 6
			
		end tell
		(*
		on error
			activate
			display alert file_name message "Unable to process this file, it will be skipped."
			
			--set uncompleted file label to red
			tell application "Finder" to set label index of (item i of dropped_items) to 2
		end try
		*)
	end repeat
end open

Since no feedback on the issues I was having, decided to keep the current Save As implementation, and it went into FaceSpan today for a UI treatment, along with additional features (like being able to switch to Inches). Works alot cleaner without the “zillion dialog boxes” method raw AppleScript is stuck with. :wink: