Last active
March 8, 2016 16:25
-
-
Save JamoCA/cc6f76263a98501d37ba to your computer and use it in GitHub Desktop.
ColdFusion ExifTool.CFC update to enable gathering all metadata in a single call w/option to save result as JSON file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
@name: ExifTool.cfc | |
@originalauthor: Steven Erat | |
@originaldate: March 2011 | |
@version: 1.0 | |
@contact: [email protected], http://www.talkingtree.com/ | |
@Forked: 9/17/2015 by James Moberg; http://SunStarMedia.com https://about.me/jamesmoberg | |
http://gamesover2600.tumblr.com/post/129293628174/exiftoolcfc-update | |
New getInfo() function to get a struct of all meta data. | |
Added logJSON option to save ExifTool JSON output to same filepath. | |
NOTE: I changed default Windows EXE path to "C:\CFusionExtra\exiftool\exiftool.exe" | |
NOTE: The parameter name is "imagefilepath", but it can actually retrieve metadata for 160+ filetypes | |
http://www.sno.phy.queensu.ca/~phil/exiftool/#supported | |
AllMetadata = ExifTool.getInfo("#AbsoluteFilePath#"); | |
@purpose: Get and Set image XMP metadata including EXIF and IPTC. | |
Improves upon the image metadata accessors built into ColdFusion 8 & 9, and adds new image metadata mutator (sets metadata) since none exist in those versions of ColdFusion | |
Extends ImageGetIPTCMetadata() & ImageGetEXIFMetadata(), and provides access to additional XMP tags. | |
@requires: Install the ExifTool on the ColdFusion server, then set exifTool path below. See http://www.sno.phy.queensu.ca/~phil/exiftool/ for details and documentation. | |
@usage: create) <cfset exifTool = createObject("component","com.stevenerat.util.ExifTool")> | |
usage 1a) <cfset metadataAll = exifTool.getExifData("#imageFilePath#").init()> | |
usage 1b) <cfset metadataAll = exifTool.getExifData("#imageFilePath#").init("C:\Path\To\ExifTool.exe")> | |
usage 2) <cfset metadataTag = exifTool.getExifTag("#imageFilePath#","description")> | |
usage 3) <cfset tags = {}> | |
<cfset tags['headline'] = "A NEW HEADLINE"> | |
<cfset tags['description'] = "A NEW DESCRIPTION"> | |
<cfset exifTool.setExifData(imageFilePath="#imageFilePath#",tags=tags)> | |
@support: Tested on Windows 7 and Mac OS X with ColdFusion 8.01 and ColdFusion 9.01 using Image::ExifTool 8.48 | |
@caveat: Use at your own risk. I assume no responsibility for mishaps, lost data, lost homework, girlfriend leaving you, etc. | |
@notes: The tag 'keyword' is complicated. Using [-xmp:keyword=foo, bar, baz] will actually set //pdf:Keywords, but in neither the ExifTool nor ColdFusion will give you //pdf:Keywords. | |
If, however, you use tag 'subject' [-xmp:subject=foo, bar, baz], then in Photoshop the IPTC 'keyword' field will show the change. In raw XMP, this corresponds to //dc:subject/rdf:Bag/rdf:li. | |
So the lesson is that when you want to set the image keywords, use the tag 'subject'. | |
Also, tag 'headline' exists in 2 different namespaces. ColdFusion sees one namespace, ExifTool sees another. Use the exifToolWinsConflict flag to set which one wins. See below & demo. | |
---> | |
<cfcomponent displayname="Image Metadata Utility" output="false" hint="Custom enhancement for ColdFusion's built-in EXIF and IPTC metadata functions. Wrapper for ExifTool commandline utility. Can read AND write all XMP metadata."> | |
<cfproperty name="variables.exifTool" type="string" default=""> | |
<cfscript> | |
variables.validReadExtensions = "3FR,3G2,3GP2,3GP,3GPP,AA,AAX,ACR,AFM,ACFM,AMFM,AI,AIT,AIFF,AIF,AIFC,APE,ARW,ASF,AVI,BMP,DIB,BTF,CHM,COS,CR2,CRW,CIFF,CS1,DCM,DC3,DIC,DICM,DCP,DCR,DFONT,DIVX,DJVU,DJV,DNG,DOC,DOT,DOCX,DOCM,DOTX,DOTM,DPX,DR4,DSS,DS2,DYLIB,DV,DVB,EIP,EPS,EPSF,PS,EPUB,ERF,EXE,DLL,EXIF,EXR,EXV,F4A,F4B,F4P,F4V,FFF,FFF,FLA,FLAC,FLV,FPF,FPX,GIF,GZ,GZIP,HDP,WDP,JXR,HDR,HTML,HTM,XHTML,ICC,ICM,ICS,ICAL,IDML,IIQ,IND,INDD,INDT,INX,ITC,J2C,JPC,JP2,JPF,J2K,JPM,JPX,JPEG,JPG,JPE,K25,KDC,KEY,KTH,LA,LFP,LFR,LNK,M2TS,MTS,M2T,TS,M4A,M4B,M4P,M4V,MEF,MIE,MIFF,MIF,MKA,MKV,MKS,MOBI,AZW,AZW3,MODD,MOI,MOS,MOV,QT,MP3,MP4,MPC,MPEG,MPG,M2V,MPO,MQV,MRW,MXF,NEF,NMBTEMPLATE,NRW,NUMBERS,ODB,ODC,ODF,ODG,,ODI,ODP,ODS,ODT,OFR,OGG,OGV,ORF,OTF,PAC,PAGES,PCD,PDB,PRC,PDF,PEF,PFA,PFB,PFM,PGF,PICT,PCT,PLIST,PMP,PNG,JNG,MNG,PPM,PBM,PGM,PPT,PPS,POT,POTX,POTM,PPSX,PPSM,PPTX,PPTM,PSD,PSB,PSP,PSPIMAGE,QTIF,QTI,QIF,RA,RAF,RAM,RPM,RAR,RAW,RAW,RIFF,RIF,RM,RV,RMVB,RSRC,RTF,RW2,RWL,RWZ,SEQ,SO,SR2,SRF,SRW,SVG,SWF,THM,THMX,TIFF,TIF,TTF,TTC,TORRENT,VCF,VCARD,VOB,VRD,VSD,WAV,WEBM,WEBP,WMA,WMV,WV,X3F,XCF,XLS,XLT,XLSX,XLSM,XLSB,XLTX,XLTM,XMP,ZIP"; | |
variables.IPTCTagList = "Additional Model Information,Artwork or Object in the Image,Author,By-line (or Author),By-line Title (sometimes listed as Author position),Caption,Caption Writer(s),Category,City (legacy),City (of Location Created),City (of Location Shown),Contact Info,Copyright Notice,Copyright Notice (of Artwork/Object),Copyright Status,Copyright Owner (PLUS),Copyright URL,Country (legacy),Country Code (legacy),Country Code (of Location Created),Country Code (of Location Shown),Country Name (of Location Created),Country Name (of Location Shown),Creator,Creator (of Artwork/Object),Creator's Job Title,Credit Line,Date Created,Date Created (of Artwork/Object),Description,Description wr,Description Writer,Document title,Digital Source Type,Event,Featured Organisation Code,Featured Organisation Name,Headline,Image Creator (PLUS),Image Registry Entry,Image Supplier ID (PLUS),Image Supplier Name (PLUS),Instructions,Intellectual Genre,IPTC Subject Code,IPTC Scene,Keywords,Location,Licensor (PLUS),Location in which the image was created,Location Shown in the Image,Max Avail Width/Height,Minor Model Age Disclosure (PLUS),Model Age,Model Release Identifier(s) (PLUS),Model Release Status (PLUS),Object Name,Original Transmission Reference,Person Shown in the Image,Photographer,Provider,Property Release Identifier(s) (PLUS),Property Release Status (PLUS),Province/State,Province/State (of Location Shown),Province/State (of Location Created),Registry Item Identifier (of Image Registry Entry),Registry Organisation Identifier (of Image Registry Entry),Rights Usage Terms,Source,Source (of Artwork/Object),Source Inventory Number (of Artwork/Object),Special Instructions,State/Province (legacy),Sublocation,Sublocation (of Location Created),Sublocation (of Location Shown),Supplemental Categories,Supplier's Image ID (PLUS),Title,Title (if used in Photo Mechanic),Title (of Artwork/Object),Transmission Reference,Urgency,World Region (of Location Created),World Region (of Location Shown),Writer/Editor"; | |
variables.TagMap = {"City"="IPTC:City,XMP-photoshop:City,XMP-iptcExt:LocationShownCity,CurrentIPTCDigest,IPTCDigest", | |
"Copyright"="EXIF:Copyright,IPTC:CopyrightNotice,XMP-dc:Rights,CurrentIPTCDigest,IPTCDigest", | |
"Country"="IPTC:Country-PrimaryLocationName,XMP-photoshop:Country,XMP-iptcExt:LocationShownCountryName,CurrentIPTCDigest,IPTCDigest", | |
"CreateDate"="EXIF:CreateDate,EXIF:SubSecTimeDigitized,IPTC:DigitalCreationDate,IPTC:DigitalCreationTime,XMP-xmp:CreateDate,CurrentIPTCDigest,IPTCDigest", | |
"Creator"="EXIF:Artist,IPTC:By-line,XMP-dc:Creator,CurrentIPTCDigest,IPTCDigest", | |
"DateTimeOriginal"="EXIF:DateTimeOriginal,EXIF:SubSecTimeOriginal,IPTC:DateCreated,IPTC:TimeCreated,XMP-photoshop:DateCreated,CurrentIPTCDigest,IPTCDigest", | |
"Description"="EXIF:ImageDescription,IPTC:Caption-Abstract,XMP-dc:Description,CurrentIPTCDigest,IPTCDigest", | |
"Keywords"="IPTC:Keywords,XMP-dc:Subject,CurrentIPTCDigest,IPTCDigest", | |
"Location"="IPTC:Sub-location,XMP-iptcCore:Location,XMP-iptcExt:LocationShownSublocation,CurrentIPTCDigest,IPTCDigest", | |
"ModifyDate"="EXIF:ModifyDate,EXIF:SubSecTime,XMP-xmp:ModifyDate,CurrentIPTCDigest,IPTCDigest", | |
"Orientation"="EXIF:Orientation", | |
"Rating"="XMP-xmp:Rating", | |
"State"="IPTC:Province-State,XMP-photoshop:State,XMP-iptcExt:LocationShownProvinceState,CurrentIPTCDigest,IPTCDigest"}; | |
function makeVarName(string) { | |
var U=trim(string); | |
U = replace(string,"'","","all"); | |
U = trim(ReReplaceNoCase(U, "<[^>]*>", "", "ALL")); | |
U = ReplaceList(U, "À,Á,Â,Ã,Ä,Å,Æ,È,É,Ê,Ë,Ì,Í,Î,Ï,Ð,Ñ,Ò,Ó,Ô,Õ,Ö,Ø,Ù,Ú,Û,Ü,Ý,à,á,â,ã,ä,å,æ,è,é,ê,ë,ì,í,î,ï,ñ,ò,ó,ô,õ,ö,ø,ù,ú,û,ü,ý, ,&", "A,A,A,A,A,A,AE,E,E,E,E,I,I,I,I,D,N,O,O,O,O,O,0,U,U,U,U,Y,a,a,a,a,a,a,ae,e,e,e,e,i,i,i,i,n,o,o,o,o,o,0,u,u,u,u,y, , "); | |
U = trim(rereplace(U ,"[[:punct:]]"," ","all")); | |
U = rereplace(U ,"[[:space:]]+","!","all"); | |
U = ReReplace(U, "[^a-zA-Z0-9!]", "", "ALL"); | |
U = trim(rereplace(U ,"!+","","all")); | |
return U; | |
} | |
</cfscript> | |
<cffunction name="init" access="public" returntype="ExifTool" output="false" hint="Initialize cfc by setting path to ExifTool"> | |
<cfargument name="exifToolPath" required="false" type="string" default="" hint="Optional parameter to specify full file path to the ExifTool"> | |
<cfset setExifToolPath(arguments.exifToolPath)> | |
<cfreturn this> | |
</cffunction> | |
<!--- setExifToolPath ---> | |
<cffunction name="setExifToolPath" access="public" returntype="void" output="false" hint="Set path to ExifTool for this platform"> | |
<cfargument name="ExifToolFilePath" required="false" type="string" default="" hint="Optional parameter to specify full file path to the ExifTool"> | |
<cfif LEN(arguments.ExifToolFilePath)> | |
<cfset variables.exifTool = arguments.ExifToolFilePath> | |
<cfelseif Findnocase("Windows", server.os.name)> | |
<cfset variables.exifTool = "C:\CFusionExtra\exiftool\exiftool.exe"> | |
<cfelse> | |
<cfset variables.exifTool = "/usr/bin/exiftool"> | |
</cfif> | |
<cfif NOT fileExists(variables.exifTool)> | |
<cfthrow type="Custom" message="ExifTool Not Found" detail="Expected to find #variables.exifTool# but it does not exist or cannot be accessed by the server"> | |
</cfif> | |
</cffunction> | |
<!--- getExifData ---> | |
<cffunction name="getExifData" access="public" returntype="struct" output="true" hint="Returns all XMP metadata for EXIF and IPTC types."> | |
<cfargument name="imageFilePath" required="true" type="string"> | |
<cfargument name="logJSON" required="false" type="string" default="false" hint="Generates a .JSON file using the same path and filename."> | |
<cfset var response = {error="", errordetail="", result={}, commandline=""}> | |
<cfset var options = ["-a", "-G1", "-json", "-scanForXMP", "-b", "-L", "-m", "-c ""%.6f %.8f""", "-d ""%d/%m/%Y %H:%M:%S"""]> | |
<!--- "-xmp:all", "-json", ---> | |
<cfset var temp = ""> | |
<CFIF NOT isboolean(arguments.logJSON)> | |
<CFSET arguments.logJSON = 0> | |
</CFIF> | |
<cfset arrayAppend(options, arguments.imageFilePath)> | |
<cfset response.commandline = variables.exifTool & " " & ArrayToList(Options, " ")> | |
<CFIF not Listfindnocase(validReadExtensions, ListLast(arguments.imageFilePath, "."))> | |
<cfset response.error = "Unsupported File Extension"> | |
<cfset response.errordetail = "The file extension #ListLast(arguments.imageFilePath, '.')# is not supported."> | |
<cfelseif fileExists(arguments.imageFilePath)> | |
<cflock name="ImageMetadataAccessLock#hash(arguments.imageFilePath)#" type="exclusive" timeout="60" throwontimeout="true"> | |
<cfexecute name="#variables.exifTool#" arguments="#ArrayToList(options,' ')#" variable="local.exifToolResult" timeout="30"/> | |
</cflock> | |
<!--- Debugging | |
<cfset temp = listLast(arguments.imageFilePath,".")> | |
<cffile action="WRITE" file="#replace(arguments.imageFilePath, temp, 'txt')#" output="#local.exifToolResult#"> | |
---> | |
<cfif isJSON(local.exifToolResult)> | |
<cfset local.exifToolResult = DeserializeJSON(local.exifToolResult)> | |
<cfif isArray(local.exifToolResult)> | |
<cfset local.exifToolResult = local.exifToolResult[1]> | |
</cfif> | |
<CFLOOP COLLECTION="#local.exifToolResult#" ITEM="local.i"> | |
<CFSET locale.group = makeVarName(ListFirst(local.i,":"))> | |
<CFSET locale.keyname = makeVarName(ListRest(local.i,":"))> | |
<CFIF NOT StructKeyExists(Response.Result, locale.group)> | |
<CFSET Response.Result["#locale.group#"] = {}> | |
</CFIF> | |
<CFSET Response.Result["#locale.group#"][locale.keyname] = local.exifToolResult[local.i]> | |
</CFLOOP> | |
<cfelse> | |
<cfset response.error = "No EXIF/Metadata."> | |
<cfset response.errorDetail = local.exifToolResult> | |
</cfif> | |
<cfif yesNoFormat(arguments.logJSON)> | |
<cfset temp = listLast(arguments.imageFilePath,".")> | |
<cffile action="WRITE" file="#replace(arguments.imageFilePath, temp, 'json')#" output="#SerializeJSON(response)#"> | |
</cfif> | |
<cfelse> | |
<cfset response.error = "Image Not Found"> | |
<cfset response.errordetail = "The image file at #arguments.imageFilePath# does not exist or cannot be accessed by the server."> | |
</cfif> | |
<cfreturn response> | |
</cffunction> | |
<!--- getExifTag ---> | |
<cffunction name="getExifTag" access="public" returntype="struct" output="false" hint="Returns a single XMP metadata tag value for EXIF or IPTC types."> | |
<cfargument name="imageFilePath" required="true" type="string"> | |
<cfargument name="tag" required="true" type="string"> | |
<cfargument name="logJSON" required="false" type="string" default="false" hint="Generates a .JSON file using the same path and filename."> | |
<cfset var response = {error="", errordetail="", result={}, commandline=""}> | |
<cfset var ExifData = {}> | |
<CFIF NOT isboolean(arguments.logJSON)> | |
<CFSET arguments.logJSON = 0> | |
</CFIF> | |
<cfset ExifData = getExifData(imageFilePath=arguments.imageFilePath, logJSON=arguments.logJSON)> | |
<cfset response = ExifData> | |
<CFIF isStruct(ExifData.result)> | |
<CFSET ExifData = StructFindKey(ExifData.Result, arguments.tag, "all")> | |
<CFSET Response.Result = []> | |
<CFLOOP FROM="1" TO="#ArrayLen(ExifData)#" INDEX="local.i"> | |
<CFSET ArrayAppend(Response.Result, {"group" = listFirst(ExifData[local.i].Path,"."), "value" = ExifData[local.i].value})> | |
</CFLOOP> | |
<cfelse> | |
<CFSET Response.Result = []> | |
<CFSET Response.Error = "Exif Tag not found"> | |
<CFSET Response.ErrorDetail = "The image file at #arguments.imageFilePath# does not have the metadata key #arguments.tag#"> | |
</CFIF> | |
<cfreturn Response> | |
</cffunction> | |
<!--- setExifTag ---> | |
<cffunction name="setExifTag" access="public" returntype="struct" output="false" hint="Set the values for XMP metadata tags."> | |
<cfargument name="imageFilePath" required="true" type="string"> | |
<cfargument name="tags" required="true" type="struct" hint="A struct containing the tag names and tag values. Struct may contain 1 or more tag name key/value pairs."> | |
<cfset var local = {}> | |
<cfset var response = {commandline="", result={}, error="", errorDetail=""}> | |
<cfif fileexists(arguments.imageFilePath)> | |
<cfset local.args = ["-overwrite_original", "-json"]> | |
<cfloop collection="#arguments.tags#" item="local.tag"> | |
<CFIF Find(":", local.tag) AND listfindnocase("-xmp,-all,-exif,-iptc,-id3", ListFirst(local.tag,":"))> | |
<CFSET local.tempTag = local.tag> | |
<CFELSE> | |
<!--- <CFSET local.tempTag = "-xmp:#local.tag#"> ---> | |
<CFSET local.tempTag = "-all:#local.tag#"> | |
</CFIF> | |
<cfset arrayAppend(local.args, "#local.tempTag#=""#arguments.tags[local.tag]#""")> | |
<!--- Update IPTC Tag to (if tag name matches) ---> | |
<CFIF NOT Find(":", local.tag) AND ListFindnocase(variables.IPTCTagList, local.tag)> | |
<CFSET local.tempTag = "-IPTC:#local.tag#"> | |
<cfset arrayAppend(local.args, "#local.tempTag#=""#arguments.tags[local.tag]#""")> | |
</CFIF> | |
</cfloop> | |
<cfset arrayAppend(local.args, imageFilePath)> | |
<cfset response.commandline = variables.exifTool & " " & ArrayToList(local.args, " ")> | |
<cflock name="ImageMetadataAccessLock#hash(imageFilePath)#" type="exclusive" timeout="60" throwontimeout="true"> | |
<cfexecute name="#variables.exifTool#" arguments="#ArrayToList(local.args, ' ')#" timeout="30" variable="local.exifToolResult"/> | |
</cflock> | |
<CFIF NOT LEN(local.exifToolResult)> | |
<cfset response.Result = arguments.tags> | |
<CFELSE> | |
<cfset response.error = "Error Updating file"> | |
<cfset response.errorDetail = "There was an error updating the keys #StructKeyList(arguments.tags)# for image file located at #arguments.imageFilePath#."> | |
</cfif> | |
<cfelse> | |
<cfset response.error = "Image Not Found"> | |
<cfset response.errorDetail = "The image file at #arguments.imageFilePath# does not exist or cannot be accessed by the server."> | |
</cfif> | |
<cfreturn response> | |
</cffunction> | |
<!--- getExifData ---> | |
<cffunction name="getInfo" access="public" returntype="struct" output="true" hint="Returns basic Image Info (width, height, colormode, etc)"> | |
<cfargument name="imageFilePath" required="true" type="string"> | |
<cfargument name="logJSON" required="false" type="string" default="false" hint="Generates a .JSON file using the same path and filename."> | |
<cfset var response = {error="", errordetail="", result={colormode="", colorspace="", width=0, height=0}, commandline=""}> | |
<cfset var options = ["-json", "-m", "-ColorMode", "-b", "-filesize##", "-ColorSpaceTags", "-Common"]> | |
<cfset var temp = ""> | |
<CFIF NOT isboolean(arguments.logJSON)> | |
<CFSET arguments.logJSON = 0> | |
</CFIF> | |
<cfset arrayAppend(options, arguments.imageFilePath)> | |
<cfset response.commandline = variables.exifTool & " " & ArrayToList(Options, " ")> | |
<CFIF not Listfindnocase(validReadExtensions, ListLast(arguments.imageFilePath, "."))> | |
<cfset response.error = "Unsupported File Extension"> | |
<cfset response.errordetail = "The file extension #ListLast(arguments.imageFilePath, '.')# is not supported."> | |
<cfelseif fileExists(arguments.imageFilePath)> | |
<cflock name="ImageMetadataAccessLock#hash(arguments.imageFilePath)#" type="exclusive" timeout="60" throwontimeout="true"> | |
<cfexecute name="#variables.exifTool#" arguments="#ArrayToList(options,' ')#" variable="local.exifToolResult" timeout="30"/> | |
</cflock> | |
<!--- Debugging | |
<cfset temp = listLast(arguments.imageFilePath,".")> | |
<cffile action="WRITE" file="#replace(arguments.imageFilePath, temp, 'txt')#" output="#local.exifToolResult#"> | |
---> | |
<cfif isJSON(local.exifToolResult)> | |
<cfset local.exifToolResult = DeserializeJSON(local.exifToolResult)> | |
<cfif isArray(local.exifToolResult)> | |
<cfset Response.Result = local.exifToolResult[1]> | |
<cfif StructKeyExists(Response.Result, "ImageSize") AND NOT StructKeyExists(Response.Result, "width")> | |
<cfset Response.Result["Width"] = ListFirst(Response.Result.ImageSize ,"x")> | |
<cfset Response.Result["Height"] = ListRest(Response.Result.ImageSize ,"x")> | |
</cfif> | |
<cfset StructDelete(Response.Result, "ICC_Profile")> | |
<cfset StructDelete(Response.Result, "ImageSize")> | |
<cfif not StructKeyExists(Response.Result, "ColorSpace")> | |
<cfset Response.Result["ColorSpace"] = "Undefined"> | |
</cfif> | |
<cfif not StructKeyExists(Response.Result, "ColorMode")> | |
<cfset Response.Result["ColorMode"] = "Undefined"> | |
</cfif> | |
</cfif> | |
<cfelse> | |
<cfset response.error = "No EXIF/Metadata."> | |
<cfset response.errorDetail = local.exifToolResult> | |
</cfif> | |
<cfif yesNoFormat(arguments.logJSON)> | |
<cfset temp = listLast(arguments.imageFilePath,".")> | |
<cffile action="WRITE" file="#replace(arguments.imageFilePath, temp, 'json')#" output="#SerializeJSON(response)#"> | |
</cfif> | |
<cfelse> | |
<cfset response.error = "Image Not Found"> | |
<cfset response.errordetail = "The image file at #arguments.imageFilePath# does not exist or cannot be accessed by the server."> | |
</cfif> | |
<cfreturn response> | |
</cffunction> | |
</cfcomponent> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment