AdobeBridgeCS5SDK

FlashExportToJpeg.jsx

Summary

Sample that shows a Flash Palette in Bridge that exports the current items selected in Bridge to JPEG files. It also adds two menu items to the Window Menu; ShowExportToJPEG and HideExportToJPEG


///////////////////////////////////////////////////////////////////////////
// ADOBE SYSTEMS INCORPORATED
// Copyright 2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the
// terms of the Adobe license agreement accompanying it.  If you have received this file from a
// source other than Adobe, then your use, modification, or distribution of it requires the prior
// written permission of Adobe.
/////////////////////////////////////////////////////////////////////////////

/** 
  @fileoverview Sample that shows a Flash Palette in Bridge that exports the current items selected in Bridge to JPEG files.  It also adds two menu items to the Window Menu; ShowExportToJPEG and HideExportToJPEG
  @class Sample that shows a Flash Palette in Bridge that exports the current items selected in Bridge to JPEG files.  It also adds two menu items to the Window Menu; ShowExportToJPEG and HideExportToJPEG
 
**/
#target bridge

if( app.name == "bridge") {

	var ExportToJpeg = {};

	/**
	 This function will return an array that matches the Metadata template names
	  @type Array
	*/
	ExportToJpeg.getMetadataTemplatesArray = function() {
		var templates = new Array();
		var pathToTemplates = Folder.userData + "/Adobe/XMP/Metadata Templates";
		var templatesFolder = new Folder( pathToTemplates );
		if( templatesFolder.exists ) {
			var xmpFiles = templatesFolder.getFiles("*.xmp");
			for( var x = 0; x < xmpFiles.length; ++x ) {
				// the name will be URL encoded and have an extension.
				// Decode the name and remove the extension.
				var fileName = File.decode(xmpFiles[x].name) ;
				if( fileName.length > 4 ) {
					fileName = fileName.substring( 0, fileName.length - 4 );
				}
				templates.push(fileName);
			}
		}
		return templates;
	}
	/*
		This function is responsible for creating the file name and has
		some special characters that represent the following:
		<ul>
			<li>%N		The name of the original file, w/o Extension</li>
			<li>%E		The orignal file's extension</li>
		</ul>
		@type File
	*/
	ExportToJpeg.generateJpegFileWithUniqueName = function( origFileName, namePattern, parentPath ) {
		var origName = origFileName;
		var origExtension = undefined;
		var baseName = origName;

		if( ! namePattern || namePattern.length < 1 ) {
			namePattern = "%N";
		}
		var lastDot = origName.lastIndexOf (".");
		if( lastDot > 0 ) {
			origExtension = origName.substr (lastDot);
			baseName = origName.substr (0, lastDot);
		}
	
		var newName = namePattern;
		while( newName.indexOf ("%N") > -1 ) {
			newName = newName.replace ("%N", baseName)
		}
		if( origExtension ) {
			while( newName.indexOf ("%E") > -1 ) {
				newName = newName.replace ("%E", baseName)
			}
		}

		// now add the .jpg extension
		var exportedJpeg = undefined;
		var matching_files = -1;
		do {
			++matching_files;
			if( matching_files == 0 ) {
				exportedJpeg = new File( parentPath + "/" + newName + ".jpg" );
			} else {
				exportedJpeg = new File( parentPath + "/" + newName + "_" + matching_files + ".jpg" );
			}
		} while ( exportedJpeg.exists ); // generate a unique file name
		return exportedJpeg;
	}

	// The name of an AppleScript file in the same directory as this JavaScript -- may be used on Mac for extra automation
	var Embed_sRGBProfileAppleScript = new File ( new File( $.fileName).parent.fsName + "/resources/Embed_sRGBProfile.scpt" );

	/**
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	Exports the current document's selection to JPEG files.
	The options arg is a JavaScript object with the following properties.

	bitmapSource 		Either "thumbnail", "preview" or "fullsize"
								Determines which the BitmapData is exported to JPEG,

	maxBitmapSize		A number. The exported bitmap will be resized to be no longer
								than this on its longest side--if necessary.

	rotateBitmap			A boolean. If true and the source file has the tiff:Orientation
								property the bitmap will be rotated before it is exported.
					
	inheritMetadata		A boolean. If true, metadata properties in the original will be copied
								to the JPEG after export. Some metadata, such as camera raw settings
								will not be copied.
					
	templateOption		Either "append" or "replace" -- controls how the metadata template (if any)
								is applied to the JPEG after it is exported.
					
	template				The name of a metadata template to apply to the JPEG after export,
								or undefined if no template is to be applied.
					
	jpegQuality				A number, from 1 to 100, passed to the BitmapData.exportTo() function
								to control the quality of the JPEG exported; 100 is highest quality.
					
	addKeywords			A boolean. If true, keywords in the option parameter "keywords"
								will be added to the exported JPEG.

	keywords				An Array of keywords. If addKeywrds is true
								these keywords will be added to the file.
					
	fileNamePattern		A string passed to the generateJpegFileWithUniqueName() function
								to produce a file name for the exported JPEG.
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
*/
	ExportToJpeg.exportToJpeg = function( doc, options, flashPlayer ) {

		var parentPath = doc.thumbnail.spec.fsName;
		// Workaround for the Desktop node's Thumbnail.spec not returning a real Folder
		if( doc.thumbnail.path == "bridge:special:desktop") { 
			parentPath = "~/Desktop"; 
		}
		
		if( ! ExternalObject.AdobeXMPScript ) {
			ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
		}
				
		app.synchronousMode = true;
		var thumbs =  doc.selections;

		// Iterate through all the selected Thumbnails
		for( var s = 0; s < thumbs.length; ++s ) {
			var thumb = thumbs[s];
			
			if( flashPlayer ) {
				// provide feedback to the user about the process by updating the flash panel.
				var pctComplete = flashPlayer.invokePlayerFunction("setExportProgress", s, thumbs.length, thumb.name );
			}
		
			if( thumb.container ) {
				continue;
			}
			
			try {
				var bitmap;
			
				if( options.bitmapSource == "thumbnail" ) {
					thumb.refresh("thumbnail");
					bitmap = thumb.core.thumbnail.thumbnail;
				} else if (options.bitmapSource == "preview" ) {
					thumb.refresh("preview");
					bitmap = thumb.core.preview.preview;
				} else {
					var bitmap = new BitmapData( thumb.spec );
				}
				if( bitmap ) {
					var resizedBitmap;
					var resized = false;
					var rotated = false;
				
					// BitmapData.resize() returns undefined if the bitmap is already that size or smaller.
					if( options.maxBitmapSize > 0 ) {
						resizedBitmap = bitmap.resize( options.maxBitmapSize );
					}
					if( resizedBitmap != undefined ) {
						bitmap = resizedBitmap;
						resized = true;
					}

					var md = thumb.synchronousMetadata;
					var source = new XMPMeta( md.serialize() );
		
					if( options.rotateBitmap ) {
						var orientation = source.getProperty( XMPConst.NS_TIFF, "Orientation");
						var rotation = 0;
						var orientationVal = parseInt( orientation );
						if( orientationVal ) {
							if( orientationVal == 8 ) {
								bitmap = bitmap.rotate( - 90 );
								rotated = true;
							} else if( orientationVal == 6 ) {
								bitmap = bitmap.rotate(90);
								rotated = true;
							} else if( orientationVal == 3 ) {
								bitmap = bitmap.rotate( 180 );
								rotated = true;
							}
						}
					}
					var exportedJpeg = ExportToJpeg.generateJpegFileWithUniqueName( thumb.name, options.fileNamePattern, parentPath );
					bitmap.exportTo( exportedJpeg );
				
					var dest = new XMPMeta();
					// copy metadata from the original to the exported JPEG, except for those properties that we don't think is necessary or required to copy
					if( options.inheritMetadata )  {
						copyDerrivedProperties( source, dest );
					}
				
					// Set some basic properties in the exported JEPGs metaddata
					dest.setProperty( XMPConst.NS_TIFF, "ImageWidth", bitmap.width.toString() );
					dest.setProperty( XMPConst.NS_TIFF, "ImageHeight", bitmap.height.toString());
					dest.setProperty( XMPConst.NS_EXIF, "PixelXDimension", bitmap.width.toString() );
					dest.setProperty( XMPConst.NS_EXIF, "PixelYDimension", bitmap.height.toString());
				
					/**
					It may or may not be a good idea to set the following Photoshop namespace properties.
					If the file is edited by older or non-Adobe software that change the color mode or color profile,
					the sofware will probably not update these properties, and this metadata will become out of date.
					*/
					//dest.setProperty( XMPConst.NS_PHOTOSHOP, "ICCProfile", "sRGB IEC61966-2.1");
					dest.setProperty( XMPConst.NS_PHOTOSHOP, "ColorMode", "3");
				
					if( rotated && options.inheritMetadata) {
						dest.setProperty( XMPConst.NS_TIFF, "Orientation", "1");
					}

					// This is a derived document -- set the appropriate MediaManagement properties
					var sourceInstanceId;
					if( source.doesPropertyExist("http://ns.adobe.com/xap/1.0/mm/", "InstanceID" ) ){
						sourceInstanceId = source.getProperty( "http://ns.adobe.com/xap/1.0/mm/", "InstanceID" );
					}

					var sourceDocumentId;
					if( source.doesPropertyExist("http://ns.adobe.com/xap/1.0/mm/", "DocumentID" ) ){
						sourceDocumentId = source.getProperty( "http://ns.adobe.com/xap/1.0/mm/", "DocumentID" );
					}

					if( sourceInstanceId || sourceDocumentId ) {
						if( sourceDocumentId ) {
							dest.setStructField( "http://ns.adobe.com/xap/1.0/mm/", 
							"DerivedFrom", 
							"http://ns.adobe.com/xap/1.0/sType/ResourceRef#",
							"documentID",sourceDocumentId );
						}
						if( sourceInstanceId ) {
							dest.setStructField( "http://ns.adobe.com/xap/1.0/mm/", 
							"DerivedFrom", 
							"http://ns.adobe.com/xap/1.0/sType/ResourceRef#",
							"instanceID",sourceInstanceId );
						}
					}
					dest.setProperty( XMPConst.NS_XMP, "CreatorTool", "Adobe Bridge CS5 " + app.version );
					// Set the MetadataDate and ModifyDate to today
					var xmpnow = new XMPDateTime( new Date() );
					dest.setProperty( XMPConst.NS_XMP, "MetadataDate", xmpnow, 0, "XMPDATE");
					dest.setProperty( XMPConst.NS_XMP, "ModifyDate", xmpnow, 0, "XMPDATE");
					if( ! options.inheritMetadata ) {
						// if we inherited a creation date from the source file, don't update it with today's date
						dest.setProperty( XMPConst.NS_XMP, "CreateDate", xmpnow, 0, "XMPDATE");
					}
					// We are exporting a JPEG, set the format metadata
					dest.setProperty( XMPConst.NS_DC, "format", "image/jpeg" );
					if( options.addKeywords ) {
						for( var k = 0; k < options.keywords.length; ++k ) {
							var keyword = options.keywords[k];
							dest.appendArrayItem( "http://purl.org/dc/elements/1.1/", "subject",keyword, 0, XMPConst.PROP_IS_ARRAY);
						}
					}
					var xmpjpeg = new XMPFile( exportedJpeg.fsName, XMPConst.FILE_JPEG, XMPConst.OPEN_FOR_UPDATE );
					xmpjpeg.putXMP( dest );
					xmpjpeg.closeFile();
					
					if( Folder.fs == "Macintosh" && Embed_sRGBProfileAppleScript.exists ) {
						// Build a system call to run the AppleScript to embedd the sRGB profile
						// Be sure to quote the file paths incase they contain spaces
						var systemCommand = "osascript \"" + Embed_sRGBProfileAppleScript.fsName + "\" \"" + exportedJpeg.fsName + "\"";
						// Run the AppleScript
						app.system (systemCommand);
					}
					// Make a Thumbnail object and get metadata synchronously. If the UI has already generated a thumbnail and metadata
					// for the exported file in the background, this will force the latest metadata to appear in the UI.
					var exportedThumb = new Thumbnail( exportedJpeg );
					var exportedMeta = exportedThumb.synchronousMetadata;				
					if( options.template ) {
						exportedMeta.applyMetadataTemplate( options.template, options.templateOption );
					}
										
				}// bitmap != undefined
			} catch( err ) {
					alert("Error Exporting " + thumb.name + " to JPEG." + err );
			}
		} // selection iteration
		if( ExternalObject.AdobeXMPScript ) {
				ExternalObject.AdobeXMPScript.unload();
				ExternalObject.AdobeXMPScript = undefined;
		}
		app.synchronousMode = false;
		if( flashPlayer ) {
			var pctComplete = flashPlayer.invokePlayerFunction("setExportProgress", 100, 100, "Done" );
		}
	}

	/**
		/////////////////////////////////////////////////////////////////////////////////////
		FUNCTIONS FOR MANIPULATING METADATA
		/////////////////////////////////////////////////////////////////////////////////////
	*/
	var contains = function( arr, member ) {
		var r = false;
		for( var i = 0; i < arr.length &! r; ++i  ) {
			r = arr[i] == member;
		}
		return r;
	}

	/**
		Copies all the properties in a namespace from one XMPMeta object to another.
		Properties with names in the array omitProps are not copied.
	*/
	var copyWholeSchema = function( source, dest, namespace, omitProps ) {
		var propIter = source.iterator(XMPConst.ITERATOR_JUST_CHILDREN | XMPConst.ITERATOR_JUST_LEAF_NAME, namespace, "" );
		var prop = propIter.next();
		var prefix = XMPMeta.getNamespacePrefix( namespace );
		while(prop) {		
			var name = prop.path.substring( prefix.length );
			var copy = omitProps == undefined;
			if( ! copy ) {
				copy = !(contains( omitProps, name));
			}
			if( copy ) {
				XMPUtils.duplicateSubtree( source, dest, namespace, prop.path,namespace, prop.path, 0 );
			}
			prop = propIter.next();
		}
	}
	/** 
		Copies the properties whose names occur in the props array in the namespace
		from one XMPMeta object to another.
	*/
	var copyPartOfSchema = function( source, dest, namespace, props ) {
		var propIter = source.iterator(XMPConst.ITERATOR_JUST_CHILDREN | XMPConst.ITERATOR_JUST_LEAF_NAME, namespace, "" );
		var prop = propIter.next();
		var prefix = XMPMeta.getNamespacePrefix( namespace );
		while(prop) {
			var name = prop.path.substring( prefix.length );
			var copy = contains( props, name);
			if( copy ) {
				XMPUtils.duplicateSubtree( source, dest, namespace, prop.path,namespace, prop.path, 0 );
			}
			prop = propIter.next();
		}
	}
	/**
	Copies selected properties from one XMPMeta object to another.
	Avoids copying properties that it is usually not appropritate to copy
	to a file that is exported from an original.
	*/
	var copyDerrivedProperties = function( source, dest ) {
		var schemaIter = source.iterator(XMPConst.ITERATOR_JUST_CHILDREN, "", "" );
		var schema = schemaIter.next();
		while(schema ) {
			// go through all the namespaces and add special handing for known ones
			if (schema.namespace == XMPConst.NS_XMP) {
				copyWholeSchema( source, dest, schema.namespace, ["MetadataDate","ModifyDate"] );
			} else if (schema.namespace == XMPConst.NS_XMP_MM) {
				// skip
			} else if (schema.namespace == XMPConst.NS_CAMERA_RAW) {
				// skip
			} else if (schema.namespace == XMPConst.NS_PHOTOSHOP) {
				// omit this potentially bogus metadata
				copyWholeSchema( source, dest, schema.namespace, ["ColorMode","ICCProfile"] );
			} else if (schema.namespace == XMPConst.NS_TIFF) {
				// only copy shooting metadata, not technical metadata about the file
				copyPartOfSchema( source, dest, schema.namespace, ["Orientation","Make","Model"] );
			} else if (schema.namespace == XMPConst.NS_EXIF) {
				// omit technical metadata about the file
				copyWholeSchema( source, dest, schema.namespace, ["PixelXDimension","PixelYDimension"] );
			} else { // default is to copy them all
				copyWholeSchema( source, dest, schema.namespace );
			}
			schema = schemaIter.next();
		}
	};

	/*
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		FUNCTIONS AND VARIABLES PROVIDING THE FLASH UI
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
	*/

	// This script assumes that the .swf file is in the same folder as the .jsx
	var uiMovie= new File(new File($.fileName).parent.fsName +"/resources/BridgeExportToJPEG.swf");

	/**
			This function creates the TabbedPalette containing a Flash Player for the file BridgeExportToJPEG.swf
	*/
	var showExportToJpegTab = function(doc) {
		var flashTab = new TabbedPalette( doc, "Export To JPEG", "ExportToJPEGTab", "script" );
		var res = "group {													\
									fp: FlashPlayer {										\
										preferredSize: [300, 550],							\
										alignment: ['fill', 'fill']							\
									},														\
						}";
		flashTab.content.add( res );
		flashTab.content.alignChildren = ['fill', 'fill' ];
		flashTab.content.layout.layout( true );
		flashTab.content.children[0].fp.loadMovie (uiMovie);
		flashTab.content.children[0].fp.show();
		flashTab.content.children[0].fp.playMovie();
		flashTab.content.onResizing = function()
		{
			this.layout.resize();
		}
		var  flashPlayer = flashTab.content.children[0].fp;
	
		flashPlayer.documentId = doc.id;
		flashPlayer.jobOptions = {};
	
		flashPlayer.exportToJpeg = function( options ) {
			this.jobOptions = options;
			var jobTask = "runJpegExportJob(" + this.documentId + ");";
			app.scheduleTask (jobTask, 100, false);
		};
		var templates = ExportToJpeg.getMetadataTemplatesArray();
		flashPlayer.invokePlayerFunction("updateTemplatesList", templates );		
	};
	/**
		Used by the getExportToJpegFlashPlayer to check
		if the flash player is already loaded in Bridge
	*/
	var findTabbedPalette = function( doc, paletteId ) {
		var palettes = doc.palettes;
		for( var t = 0; t < palettes.length; ++t ) {
			var next = palettes[t];
			if( next.id == paletteId ) {
				return next;
			}
		}
		return undefined;
	}

	/** Starts the Export Process */
	var runJpegExportJob = function( documentId ) {
		var doc = undefined;
		var allDocs = app.documents;
		for( var d = 0; d < allDocs.length; ++d ) {
				var nextDoc = allDocs[d];
				if( nextDoc.id == documentId ) {
						doc = nextDoc;
						break;
				}
		}
		if( doc ) {
			var flashPlayer = getExportToJpegFlashPlayer( doc );
			var options = flashPlayer.jobOptions;
			ExportToJpeg.exportToJpeg( doc, options, flashPlayer );
		} else {
				throw("runJpegExportJob() could not find a Bridge Document with ID " + documentId );
		}
	}
	/**  
		This function checks the current environment in Bridge to see if the Menu Items
		ShowExportToJPEG and HideExportToJPEG appears in the Application.
		It will return a FlashPlayer if the Tabs are shown in the UI
	*/
	var getExportToJpegFlashPlayer = function( doc ) {
			var tab = findTabbedPalette( doc, "ExportToJPEGTab" );
			if( tab ) {
				var flashPlayer = tab.content.children[0].fp;
				return flashPlayer;
			}
			return undefined;
		}
		/**  Checks if the SgowExportToJPEG menu item already exists in Bridge */
		if( ! MenuElement.find ("ShowExportToJPEG") ) {
			var showExportToJpeg = new MenuElement( "command", "Show Export to JPEG", "-at the end of Window", "ShowExportToJPEG" );
			showExportToJpeg.onSelect = function() {
				var existingTab = findTabbedPalette( app.document, "ExportToJPEGTab");
				if( existingTab ) {
					existingTab.show();
				} else {
					showExportToJpegTab(app.document);
				}
			}
		}
		/** This checks to see if the Menu item HideExportToJPEG already exists in Bridge */
		if( ! MenuElement.find ("HideExportToJPEG") ) {
			var hideExportToJpeg = new MenuElement( "command", "Hide Export to JPEG", "at the end of Window", "HideExportToJPEG" );
			hideExportToJpeg.onSelect = function() {
				var existingTab = findTabbedPalette( app.document, "ExportToJPEGTab");
				if( existingTab ) {
					var  flashPlayer = existingTab.content.children[0].fp;
					existingTab.content.children[0].remove(flashPlayer);
					existingTab.content.children[0].fp = null;
					existingTab.remove();
				}
		}
	}
} 

AdobeBridgeCS5SDK

http://www.adobe.com/devnet/bridge
Documentation generated by JSDoc on Tue Apr 27 10:21:34 2010