exif.js 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058
  1. (function() {
  2. var debug = false;
  3. var root = this;
  4. var EXIF = function(obj) {
  5. if (obj instanceof EXIF) return obj;
  6. if (!(this instanceof EXIF)) return new EXIF(obj);
  7. this.EXIFwrapped = obj;
  8. };
  9. if (typeof exports !== 'undefined') {
  10. if (typeof module !== 'undefined' && module.exports) {
  11. exports = module.exports = EXIF;
  12. }
  13. exports.EXIF = EXIF;
  14. } else {
  15. root.EXIF = EXIF;
  16. }
  17. var ExifTags = EXIF.Tags = {
  18. // version tags
  19. 0x9000 : "ExifVersion", // EXIF version
  20. 0xA000 : "FlashpixVersion", // Flashpix format version
  21. // colorspace tags
  22. 0xA001 : "ColorSpace", // Color space information tag
  23. // image configuration
  24. 0xA002 : "PixelXDimension", // Valid width of meaningful image
  25. 0xA003 : "PixelYDimension", // Valid height of meaningful image
  26. 0x9101 : "ComponentsConfiguration", // Information about channels
  27. 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel
  28. // user information
  29. 0x927C : "MakerNote", // Any desired information written by the manufacturer
  30. 0x9286 : "UserComment", // Comments by user
  31. // related file
  32. 0xA004 : "RelatedSoundFile", // Name of related sound file
  33. // date and time
  34. 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated
  35. 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally
  36. 0x9290 : "SubsecTime", // Fractions of seconds for DateTime
  37. 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
  38. 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
  39. // picture-taking conditions
  40. 0x829A : "ExposureTime", // Exposure time (in seconds)
  41. 0x829D : "FNumber", // F number
  42. 0x8822 : "ExposureProgram", // Exposure program
  43. 0x8824 : "SpectralSensitivity", // Spectral sensitivity
  44. 0x8827 : "ISOSpeedRatings", // ISO speed rating
  45. 0x8828 : "OECF", // Optoelectric conversion factor
  46. 0x9201 : "ShutterSpeedValue", // Shutter speed
  47. 0x9202 : "ApertureValue", // Lens aperture
  48. 0x9203 : "BrightnessValue", // Value of brightness
  49. 0x9204 : "ExposureBias", // Exposure bias
  50. 0x9205 : "MaxApertureValue", // Smallest F number of lens
  51. 0x9206 : "SubjectDistance", // Distance to subject in meters
  52. 0x9207 : "MeteringMode", // Metering mode
  53. 0x9208 : "LightSource", // Kind of light source
  54. 0x9209 : "Flash", // Flash status
  55. 0x9214 : "SubjectArea", // Location and area of main subject
  56. 0x920A : "FocalLength", // Focal length of the lens in mm
  57. 0xA20B : "FlashEnergy", // Strobe energy in BCPS
  58. 0xA20C : "SpatialFrequencyResponse", //
  59. 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
  60. 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
  61. 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
  62. 0xA214 : "SubjectLocation", // Location of subject in image
  63. 0xA215 : "ExposureIndex", // Exposure index selected on camera
  64. 0xA217 : "SensingMethod", // Image sensor type
  65. 0xA300 : "FileSource", // Image source (3 == DSC)
  66. 0xA301 : "SceneType", // Scene type (1 == directly photographed)
  67. 0xA302 : "CFAPattern", // Color filter array geometric pattern
  68. 0xA401 : "CustomRendered", // Special processing
  69. 0xA402 : "ExposureMode", // Exposure mode
  70. 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual
  71. 0xA404 : "DigitalZoomRation", // Digital zoom ratio
  72. 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
  73. 0xA406 : "SceneCaptureType", // Type of scene
  74. 0xA407 : "GainControl", // Degree of overall image gain adjustment
  75. 0xA408 : "Contrast", // Direction of contrast processing applied by camera
  76. 0xA409 : "Saturation", // Direction of saturation processing applied by camera
  77. 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera
  78. 0xA40B : "DeviceSettingDescription", //
  79. 0xA40C : "SubjectDistanceRange", // Distance to subject
  80. // other tags
  81. 0xA005 : "InteroperabilityIFDPointer",
  82. 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image
  83. };
  84. var TiffTags = EXIF.TiffTags = {
  85. 0x0100 : "ImageWidth",
  86. 0x0101 : "ImageHeight",
  87. 0x8769 : "ExifIFDPointer",
  88. 0x8825 : "GPSInfoIFDPointer",
  89. 0xA005 : "InteroperabilityIFDPointer",
  90. 0x0102 : "BitsPerSample",
  91. 0x0103 : "Compression",
  92. 0x0106 : "PhotometricInterpretation",
  93. 0x0112 : "Orientation",
  94. 0x0115 : "SamplesPerPixel",
  95. 0x011C : "PlanarConfiguration",
  96. 0x0212 : "YCbCrSubSampling",
  97. 0x0213 : "YCbCrPositioning",
  98. 0x011A : "XResolution",
  99. 0x011B : "YResolution",
  100. 0x0128 : "ResolutionUnit",
  101. 0x0111 : "StripOffsets",
  102. 0x0116 : "RowsPerStrip",
  103. 0x0117 : "StripByteCounts",
  104. 0x0201 : "JPEGInterchangeFormat",
  105. 0x0202 : "JPEGInterchangeFormatLength",
  106. 0x012D : "TransferFunction",
  107. 0x013E : "WhitePoint",
  108. 0x013F : "PrimaryChromaticities",
  109. 0x0211 : "YCbCrCoefficients",
  110. 0x0214 : "ReferenceBlackWhite",
  111. 0x0132 : "DateTime",
  112. 0x010E : "ImageDescription",
  113. 0x010F : "Make",
  114. 0x0110 : "Model",
  115. 0x0131 : "Software",
  116. 0x013B : "Artist",
  117. 0x8298 : "Copyright"
  118. };
  119. var GPSTags = EXIF.GPSTags = {
  120. 0x0000 : "GPSVersionID",
  121. 0x0001 : "GPSLatitudeRef",
  122. 0x0002 : "GPSLatitude",
  123. 0x0003 : "GPSLongitudeRef",
  124. 0x0004 : "GPSLongitude",
  125. 0x0005 : "GPSAltitudeRef",
  126. 0x0006 : "GPSAltitude",
  127. 0x0007 : "GPSTimeStamp",
  128. 0x0008 : "GPSSatellites",
  129. 0x0009 : "GPSStatus",
  130. 0x000A : "GPSMeasureMode",
  131. 0x000B : "GPSDOP",
  132. 0x000C : "GPSSpeedRef",
  133. 0x000D : "GPSSpeed",
  134. 0x000E : "GPSTrackRef",
  135. 0x000F : "GPSTrack",
  136. 0x0010 : "GPSImgDirectionRef",
  137. 0x0011 : "GPSImgDirection",
  138. 0x0012 : "GPSMapDatum",
  139. 0x0013 : "GPSDestLatitudeRef",
  140. 0x0014 : "GPSDestLatitude",
  141. 0x0015 : "GPSDestLongitudeRef",
  142. 0x0016 : "GPSDestLongitude",
  143. 0x0017 : "GPSDestBearingRef",
  144. 0x0018 : "GPSDestBearing",
  145. 0x0019 : "GPSDestDistanceRef",
  146. 0x001A : "GPSDestDistance",
  147. 0x001B : "GPSProcessingMethod",
  148. 0x001C : "GPSAreaInformation",
  149. 0x001D : "GPSDateStamp",
  150. 0x001E : "GPSDifferential"
  151. };
  152. // EXIF 2.3 Spec
  153. var IFD1Tags = EXIF.IFD1Tags = {
  154. 0x0100: "ImageWidth",
  155. 0x0101: "ImageHeight",
  156. 0x0102: "BitsPerSample",
  157. 0x0103: "Compression",
  158. 0x0106: "PhotometricInterpretation",
  159. 0x0111: "StripOffsets",
  160. 0x0112: "Orientation",
  161. 0x0115: "SamplesPerPixel",
  162. 0x0116: "RowsPerStrip",
  163. 0x0117: "StripByteCounts",
  164. 0x011A: "XResolution",
  165. 0x011B: "YResolution",
  166. 0x011C: "PlanarConfiguration",
  167. 0x0128: "ResolutionUnit",
  168. 0x0201: "JpegIFOffset", // When image format is JPEG, this value show offset to JPEG data stored.(aka "ThumbnailOffset" or "JPEGInterchangeFormat")
  169. 0x0202: "JpegIFByteCount", // When image format is JPEG, this value shows data size of JPEG image (aka "ThumbnailLength" or "JPEGInterchangeFormatLength")
  170. 0x0211: "YCbCrCoefficients",
  171. 0x0212: "YCbCrSubSampling",
  172. 0x0213: "YCbCrPositioning",
  173. 0x0214: "ReferenceBlackWhite"
  174. };
  175. var StringValues = EXIF.StringValues = {
  176. ExposureProgram : {
  177. 0 : "Not defined",
  178. 1 : "Manual",
  179. 2 : "Normal program",
  180. 3 : "Aperture priority",
  181. 4 : "Shutter priority",
  182. 5 : "Creative program",
  183. 6 : "Action program",
  184. 7 : "Portrait mode",
  185. 8 : "Landscape mode"
  186. },
  187. MeteringMode : {
  188. 0 : "Unknown",
  189. 1 : "Average",
  190. 2 : "CenterWeightedAverage",
  191. 3 : "Spot",
  192. 4 : "MultiSpot",
  193. 5 : "Pattern",
  194. 6 : "Partial",
  195. 255 : "Other"
  196. },
  197. LightSource : {
  198. 0 : "Unknown",
  199. 1 : "Daylight",
  200. 2 : "Fluorescent",
  201. 3 : "Tungsten (incandescent light)",
  202. 4 : "Flash",
  203. 9 : "Fine weather",
  204. 10 : "Cloudy weather",
  205. 11 : "Shade",
  206. 12 : "Daylight fluorescent (D 5700 - 7100K)",
  207. 13 : "Day white fluorescent (N 4600 - 5400K)",
  208. 14 : "Cool white fluorescent (W 3900 - 4500K)",
  209. 15 : "White fluorescent (WW 3200 - 3700K)",
  210. 17 : "Standard light A",
  211. 18 : "Standard light B",
  212. 19 : "Standard light C",
  213. 20 : "D55",
  214. 21 : "D65",
  215. 22 : "D75",
  216. 23 : "D50",
  217. 24 : "ISO studio tungsten",
  218. 255 : "Other"
  219. },
  220. Flash : {
  221. 0x0000 : "Flash did not fire",
  222. 0x0001 : "Flash fired",
  223. 0x0005 : "Strobe return light not detected",
  224. 0x0007 : "Strobe return light detected",
  225. 0x0009 : "Flash fired, compulsory flash mode",
  226. 0x000D : "Flash fired, compulsory flash mode, return light not detected",
  227. 0x000F : "Flash fired, compulsory flash mode, return light detected",
  228. 0x0010 : "Flash did not fire, compulsory flash mode",
  229. 0x0018 : "Flash did not fire, auto mode",
  230. 0x0019 : "Flash fired, auto mode",
  231. 0x001D : "Flash fired, auto mode, return light not detected",
  232. 0x001F : "Flash fired, auto mode, return light detected",
  233. 0x0020 : "No flash function",
  234. 0x0041 : "Flash fired, red-eye reduction mode",
  235. 0x0045 : "Flash fired, red-eye reduction mode, return light not detected",
  236. 0x0047 : "Flash fired, red-eye reduction mode, return light detected",
  237. 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode",
  238. 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
  239. 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
  240. 0x0059 : "Flash fired, auto mode, red-eye reduction mode",
  241. 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode",
  242. 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode"
  243. },
  244. SensingMethod : {
  245. 1 : "Not defined",
  246. 2 : "One-chip color area sensor",
  247. 3 : "Two-chip color area sensor",
  248. 4 : "Three-chip color area sensor",
  249. 5 : "Color sequential area sensor",
  250. 7 : "Trilinear sensor",
  251. 8 : "Color sequential linear sensor"
  252. },
  253. SceneCaptureType : {
  254. 0 : "Standard",
  255. 1 : "Landscape",
  256. 2 : "Portrait",
  257. 3 : "Night scene"
  258. },
  259. SceneType : {
  260. 1 : "Directly photographed"
  261. },
  262. CustomRendered : {
  263. 0 : "Normal process",
  264. 1 : "Custom process"
  265. },
  266. WhiteBalance : {
  267. 0 : "Auto white balance",
  268. 1 : "Manual white balance"
  269. },
  270. GainControl : {
  271. 0 : "None",
  272. 1 : "Low gain up",
  273. 2 : "High gain up",
  274. 3 : "Low gain down",
  275. 4 : "High gain down"
  276. },
  277. Contrast : {
  278. 0 : "Normal",
  279. 1 : "Soft",
  280. 2 : "Hard"
  281. },
  282. Saturation : {
  283. 0 : "Normal",
  284. 1 : "Low saturation",
  285. 2 : "High saturation"
  286. },
  287. Sharpness : {
  288. 0 : "Normal",
  289. 1 : "Soft",
  290. 2 : "Hard"
  291. },
  292. SubjectDistanceRange : {
  293. 0 : "Unknown",
  294. 1 : "Macro",
  295. 2 : "Close view",
  296. 3 : "Distant view"
  297. },
  298. FileSource : {
  299. 3 : "DSC"
  300. },
  301. Components : {
  302. 0 : "",
  303. 1 : "Y",
  304. 2 : "Cb",
  305. 3 : "Cr",
  306. 4 : "R",
  307. 5 : "G",
  308. 6 : "B"
  309. }
  310. };
  311. function addEvent(element, event, handler) {
  312. if (element.addEventListener) {
  313. element.addEventListener(event, handler, false);
  314. } else if (element.attachEvent) {
  315. element.attachEvent("on" + event, handler);
  316. }
  317. }
  318. function imageHasData(img) {
  319. return !!(img.exifdata);
  320. }
  321. function base64ToArrayBuffer(base64, contentType) {
  322. contentType = contentType || base64.match(/^data\:([^\;]+)\;base64,/mi)[1] || ''; // e.g. 'data:image/jpeg;base64,...' => 'image/jpeg'
  323. base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
  324. var binary = atob(base64);
  325. var len = binary.length;
  326. var buffer = new ArrayBuffer(len);
  327. var view = new Uint8Array(buffer);
  328. for (var i = 0; i < len; i++) {
  329. view[i] = binary.charCodeAt(i);
  330. }
  331. return buffer;
  332. }
  333. function objectURLToBlob(url, callback) {
  334. var http = new XMLHttpRequest();
  335. http.open("GET", url, true);
  336. http.responseType = "blob";
  337. http.onload = function(e) {
  338. if (this.status == 200 || this.status === 0) {
  339. callback(this.response);
  340. }
  341. };
  342. http.send();
  343. }
  344. function getImageData(img, callback) {
  345. function handleBinaryFile(binFile) {
  346. var data = findEXIFinJPEG(binFile);
  347. img.exifdata = data || {};
  348. var iptcdata = findIPTCinJPEG(binFile);
  349. img.iptcdata = iptcdata || {};
  350. if (EXIF.isXmpEnabled) {
  351. var xmpdata= findXMPinJPEG(binFile);
  352. img.xmpdata = xmpdata || {};
  353. }
  354. if (callback) {
  355. callback.call(img);
  356. }
  357. }
  358. if (img.src) {
  359. if (/^data\:/i.test(img.src)) { // Data URI
  360. var arrayBuffer = base64ToArrayBuffer(img.src);
  361. handleBinaryFile(arrayBuffer);
  362. } else if (/^blob\:/i.test(img.src)) { // Object URL
  363. var fileReader = new FileReader();
  364. fileReader.onload = function(e) {
  365. handleBinaryFile(e.target.result);
  366. };
  367. objectURLToBlob(img.src, function (blob) {
  368. fileReader.readAsArrayBuffer(blob);
  369. });
  370. } else {
  371. var http = new XMLHttpRequest();
  372. http.onload = function() {
  373. if (this.status == 200 || this.status === 0) {
  374. handleBinaryFile(http.response);
  375. } else {
  376. throw "Could not load image";
  377. }
  378. http = null;
  379. };
  380. http.open("GET", img.src, true);
  381. http.responseType = "arraybuffer";
  382. http.send(null);
  383. }
  384. } else if (self.FileReader && (img instanceof self.Blob || img instanceof self.File)) {
  385. var fileReader = new FileReader();
  386. fileReader.onload = function(e) {
  387. if (debug) console.log("Got file of length " + e.target.result.byteLength);
  388. handleBinaryFile(e.target.result);
  389. };
  390. fileReader.readAsArrayBuffer(img);
  391. }
  392. }
  393. function findEXIFinJPEG(file) {
  394. var dataView = new DataView(file);
  395. if (debug) console.log("Got file of length " + file.byteLength);
  396. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  397. if (debug) console.log("Not a valid JPEG");
  398. return false; // not a valid jpeg
  399. }
  400. var offset = 2,
  401. length = file.byteLength,
  402. marker;
  403. while (offset < length) {
  404. if (dataView.getUint8(offset) != 0xFF) {
  405. if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(offset));
  406. return false; // not a valid marker, something is wrong
  407. }
  408. marker = dataView.getUint8(offset + 1);
  409. if (debug) console.log(marker);
  410. // we could implement handling for other markers here,
  411. // but we're only looking for 0xFFE1 for EXIF data
  412. if (marker == 225) {
  413. if (debug) console.log("Found 0xFFE1 marker");
  414. return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
  415. // offset += 2 + file.getShortAt(offset+2, true);
  416. } else {
  417. offset += 2 + dataView.getUint16(offset+2);
  418. }
  419. }
  420. }
  421. function findIPTCinJPEG(file) {
  422. var dataView = new DataView(file);
  423. if (debug) console.log("Got file of length " + file.byteLength);
  424. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  425. if (debug) console.log("Not a valid JPEG");
  426. return false; // not a valid jpeg
  427. }
  428. var offset = 2,
  429. length = file.byteLength;
  430. var isFieldSegmentStart = function(dataView, offset){
  431. return (
  432. dataView.getUint8(offset) === 0x38 &&
  433. dataView.getUint8(offset+1) === 0x42 &&
  434. dataView.getUint8(offset+2) === 0x49 &&
  435. dataView.getUint8(offset+3) === 0x4D &&
  436. dataView.getUint8(offset+4) === 0x04 &&
  437. dataView.getUint8(offset+5) === 0x04
  438. );
  439. };
  440. while (offset < length) {
  441. if ( isFieldSegmentStart(dataView, offset )){
  442. // Get the length of the name header (which is padded to an even number of bytes)
  443. var nameHeaderLength = dataView.getUint8(offset+7);
  444. if(nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
  445. // Check for pre photoshop 6 format
  446. if(nameHeaderLength === 0) {
  447. // Always 4
  448. nameHeaderLength = 4;
  449. }
  450. var startOffset = offset + 8 + nameHeaderLength;
  451. var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
  452. return readIPTCData(file, startOffset, sectionLength);
  453. break;
  454. }
  455. // Not the marker, continue searching
  456. offset++;
  457. }
  458. }
  459. var IptcFieldMap = {
  460. 0x78 : 'caption',
  461. 0x6E : 'credit',
  462. 0x19 : 'keywords',
  463. 0x37 : 'dateCreated',
  464. 0x50 : 'byline',
  465. 0x55 : 'bylineTitle',
  466. 0x7A : 'captionWriter',
  467. 0x69 : 'headline',
  468. 0x74 : 'copyright',
  469. 0x0F : 'category'
  470. };
  471. function readIPTCData(file, startOffset, sectionLength){
  472. var dataView = new DataView(file);
  473. var data = {};
  474. var fieldValue, fieldName, dataSize, segmentType, segmentSize;
  475. var segmentStartPos = startOffset;
  476. while(segmentStartPos < startOffset+sectionLength) {
  477. if(dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos+1) === 0x02){
  478. segmentType = dataView.getUint8(segmentStartPos+2);
  479. if(segmentType in IptcFieldMap) {
  480. dataSize = dataView.getInt16(segmentStartPos+3);
  481. segmentSize = dataSize + 5;
  482. fieldName = IptcFieldMap[segmentType];
  483. fieldValue = getStringFromDB(dataView, segmentStartPos+5, dataSize);
  484. // Check if we already stored a value with this name
  485. if(data.hasOwnProperty(fieldName)) {
  486. // Value already stored with this name, create multivalue field
  487. if(data[fieldName] instanceof Array) {
  488. data[fieldName].push(fieldValue);
  489. }
  490. else {
  491. data[fieldName] = [data[fieldName], fieldValue];
  492. }
  493. }
  494. else {
  495. data[fieldName] = fieldValue;
  496. }
  497. }
  498. }
  499. segmentStartPos++;
  500. }
  501. return data;
  502. }
  503. function readTags(file, tiffStart, dirStart, strings, bigEnd) {
  504. var entries = file.getUint16(dirStart, !bigEnd),
  505. tags = {},
  506. entryOffset, tag,
  507. i;
  508. for (i=0;i<entries;i++) {
  509. entryOffset = dirStart + i*12 + 2;
  510. tag = strings[file.getUint16(entryOffset, !bigEnd)];
  511. if (!tag && debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
  512. tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
  513. }
  514. return tags;
  515. }
  516. function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
  517. var type = file.getUint16(entryOffset+2, !bigEnd),
  518. numValues = file.getUint32(entryOffset+4, !bigEnd),
  519. valueOffset = file.getUint32(entryOffset+8, !bigEnd) + tiffStart,
  520. offset,
  521. vals, val, n,
  522. numerator, denominator;
  523. switch (type) {
  524. case 1: // byte, 8-bit unsigned int
  525. case 7: // undefined, 8-bit byte, value depending on field
  526. if (numValues == 1) {
  527. return file.getUint8(entryOffset + 8, !bigEnd);
  528. } else {
  529. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  530. vals = [];
  531. for (n=0;n<numValues;n++) {
  532. vals[n] = file.getUint8(offset + n);
  533. }
  534. return vals;
  535. }
  536. case 2: // ascii, 8-bit byte
  537. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  538. return getStringFromDB(file, offset, numValues-1);
  539. case 3: // short, 16 bit int
  540. if (numValues == 1) {
  541. return file.getUint16(entryOffset + 8, !bigEnd);
  542. } else {
  543. offset = numValues > 2 ? valueOffset : (entryOffset + 8);
  544. vals = [];
  545. for (n=0;n<numValues;n++) {
  546. vals[n] = file.getUint16(offset + 2*n, !bigEnd);
  547. }
  548. return vals;
  549. }
  550. case 4: // long, 32 bit int
  551. if (numValues == 1) {
  552. return file.getUint32(entryOffset + 8, !bigEnd);
  553. } else {
  554. vals = [];
  555. for (n=0;n<numValues;n++) {
  556. vals[n] = file.getUint32(valueOffset + 4*n, !bigEnd);
  557. }
  558. return vals;
  559. }
  560. case 5: // rational = two long values, first is numerator, second is denominator
  561. if (numValues == 1) {
  562. numerator = file.getUint32(valueOffset, !bigEnd);
  563. denominator = file.getUint32(valueOffset+4, !bigEnd);
  564. val = new Number(numerator / denominator);
  565. val.numerator = numerator;
  566. val.denominator = denominator;
  567. return val;
  568. } else {
  569. vals = [];
  570. for (n=0;n<numValues;n++) {
  571. numerator = file.getUint32(valueOffset + 8*n, !bigEnd);
  572. denominator = file.getUint32(valueOffset+4 + 8*n, !bigEnd);
  573. vals[n] = new Number(numerator / denominator);
  574. vals[n].numerator = numerator;
  575. vals[n].denominator = denominator;
  576. }
  577. return vals;
  578. }
  579. case 9: // slong, 32 bit signed int
  580. if (numValues == 1) {
  581. return file.getInt32(entryOffset + 8, !bigEnd);
  582. } else {
  583. vals = [];
  584. for (n=0;n<numValues;n++) {
  585. vals[n] = file.getInt32(valueOffset + 4*n, !bigEnd);
  586. }
  587. return vals;
  588. }
  589. case 10: // signed rational, two slongs, first is numerator, second is denominator
  590. if (numValues == 1) {
  591. return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset+4, !bigEnd);
  592. } else {
  593. vals = [];
  594. for (n=0;n<numValues;n++) {
  595. vals[n] = file.getInt32(valueOffset + 8*n, !bigEnd) / file.getInt32(valueOffset+4 + 8*n, !bigEnd);
  596. }
  597. return vals;
  598. }
  599. }
  600. }
  601. /**
  602. * Given an IFD (Image File Directory) start offset
  603. * returns an offset to next IFD or 0 if it's the last IFD.
  604. */
  605. function getNextIFDOffset(dataView, dirStart, bigEnd){
  606. //the first 2bytes means the number of directory entries contains in this IFD
  607. var entries = dataView.getUint16(dirStart, !bigEnd);
  608. // After last directory entry, there is a 4bytes of data,
  609. // it means an offset to next IFD.
  610. // If its value is '0x00000000', it means this is the last IFD and there is no linked IFD.
  611. return dataView.getUint32(dirStart + 2 + entries * 12, !bigEnd); // each entry is 12 bytes long
  612. }
  613. function readThumbnailImage(dataView, tiffStart, firstIFDOffset, bigEnd){
  614. // get the IFD1 offset
  615. var IFD1OffsetPointer = getNextIFDOffset(dataView, tiffStart+firstIFDOffset, bigEnd);
  616. if (!IFD1OffsetPointer) {
  617. // console.log('******** IFD1Offset is empty, image thumb not found ********');
  618. return {};
  619. }
  620. else if (IFD1OffsetPointer > dataView.byteLength) { // this should not happen
  621. // console.log('******** IFD1Offset is outside the bounds of the DataView ********');
  622. return {};
  623. }
  624. // console.log('******* thumbnail IFD offset (IFD1) is: %s', IFD1OffsetPointer);
  625. var thumbTags = readTags(dataView, tiffStart, tiffStart + IFD1OffsetPointer, IFD1Tags, bigEnd)
  626. // EXIF 2.3 specification for JPEG format thumbnail
  627. // If the value of Compression(0x0103) Tag in IFD1 is '6', thumbnail image format is JPEG.
  628. // Most of Exif image uses JPEG format for thumbnail. In that case, you can get offset of thumbnail
  629. // by JpegIFOffset(0x0201) Tag in IFD1, size of thumbnail by JpegIFByteCount(0x0202) Tag.
  630. // Data format is ordinary JPEG format, starts from 0xFFD8 and ends by 0xFFD9. It seems that
  631. // JPEG format and 160x120pixels of size are recommended thumbnail format for Exif2.1 or later.
  632. if (thumbTags['Compression']) {
  633. // console.log('Thumbnail image found!');
  634. switch (thumbTags['Compression']) {
  635. case 6:
  636. // console.log('Thumbnail image format is JPEG');
  637. if (thumbTags.JpegIFOffset && thumbTags.JpegIFByteCount) {
  638. // extract the thumbnail
  639. var tOffset = tiffStart + thumbTags.JpegIFOffset;
  640. var tLength = thumbTags.JpegIFByteCount;
  641. thumbTags['blob'] = new Blob([new Uint8Array(dataView.buffer, tOffset, tLength)], {
  642. type: 'image/jpeg'
  643. });
  644. }
  645. break;
  646. case 1:
  647. console.log("Thumbnail image format is TIFF, which is not implemented.");
  648. break;
  649. default:
  650. console.log("Unknown thumbnail image format '%s'", thumbTags['Compression']);
  651. }
  652. }
  653. else if (thumbTags['PhotometricInterpretation'] == 2) {
  654. console.log("Thumbnail image format is RGB, which is not implemented.");
  655. }
  656. return thumbTags;
  657. }
  658. function getStringFromDB(buffer, start, length) {
  659. var outstr = "";
  660. for (n = start; n < start+length; n++) {
  661. outstr += String.fromCharCode(buffer.getUint8(n));
  662. }
  663. return outstr;
  664. }
  665. function readEXIFData(file, start) {
  666. if (getStringFromDB(file, start, 4) != "Exif") {
  667. if (debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
  668. return false;
  669. }
  670. var bigEnd,
  671. tags, tag,
  672. exifData, gpsData,
  673. tiffOffset = start + 6;
  674. // test for TIFF validity and endianness
  675. if (file.getUint16(tiffOffset) == 0x4949) {
  676. bigEnd = false;
  677. } else if (file.getUint16(tiffOffset) == 0x4D4D) {
  678. bigEnd = true;
  679. } else {
  680. if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
  681. return false;
  682. }
  683. if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
  684. if (debug) console.log("Not valid TIFF data! (no 0x002A)");
  685. return false;
  686. }
  687. var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
  688. if (firstIFDOffset < 0x00000008) {
  689. if (debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset+4, !bigEnd));
  690. return false;
  691. }
  692. tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
  693. if (tags.ExifIFDPointer) {
  694. exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
  695. for (tag in exifData) {
  696. switch (tag) {
  697. case "LightSource" :
  698. case "Flash" :
  699. case "MeteringMode" :
  700. case "ExposureProgram" :
  701. case "SensingMethod" :
  702. case "SceneCaptureType" :
  703. case "SceneType" :
  704. case "CustomRendered" :
  705. case "WhiteBalance" :
  706. case "GainControl" :
  707. case "Contrast" :
  708. case "Saturation" :
  709. case "Sharpness" :
  710. case "SubjectDistanceRange" :
  711. case "FileSource" :
  712. exifData[tag] = StringValues[tag][exifData[tag]];
  713. break;
  714. case "ExifVersion" :
  715. case "FlashpixVersion" :
  716. exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
  717. break;
  718. case "ComponentsConfiguration" :
  719. exifData[tag] =
  720. StringValues.Components[exifData[tag][0]] +
  721. StringValues.Components[exifData[tag][1]] +
  722. StringValues.Components[exifData[tag][2]] +
  723. StringValues.Components[exifData[tag][3]];
  724. break;
  725. }
  726. tags[tag] = exifData[tag];
  727. }
  728. }
  729. if (tags.GPSInfoIFDPointer) {
  730. gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
  731. for (tag in gpsData) {
  732. switch (tag) {
  733. case "GPSVersionID" :
  734. gpsData[tag] = gpsData[tag][0] +
  735. "." + gpsData[tag][1] +
  736. "." + gpsData[tag][2] +
  737. "." + gpsData[tag][3];
  738. break;
  739. }
  740. tags[tag] = gpsData[tag];
  741. }
  742. }
  743. // extract thumbnail
  744. tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd);
  745. return tags;
  746. }
  747. function findXMPinJPEG(file) {
  748. if (!('DOMParser' in self)) {
  749. // console.warn('XML parsing not supported without DOMParser');
  750. return;
  751. }
  752. var dataView = new DataView(file);
  753. if (debug) console.log("Got file of length " + file.byteLength);
  754. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  755. if (debug) console.log("Not a valid JPEG");
  756. return false; // not a valid jpeg
  757. }
  758. var offset = 2,
  759. length = file.byteLength,
  760. dom = new DOMParser();
  761. while (offset < (length-4)) {
  762. if (getStringFromDB(dataView, offset, 4) == "http") {
  763. var startOffset = offset - 1;
  764. var sectionLength = dataView.getUint16(offset - 2) - 1;
  765. var xmpString = getStringFromDB(dataView, startOffset, sectionLength)
  766. var xmpEndIndex = xmpString.indexOf('xmpmeta>') + 8;
  767. xmpString = xmpString.substring( xmpString.indexOf( '<x:xmpmeta' ), xmpEndIndex );
  768. var indexOfXmp = xmpString.indexOf('x:xmpmeta') + 10
  769. //Many custom written programs embed xmp/xml without any namespace. Following are some of them.
  770. //Without these namespaces, XML is thought to be invalid by parsers
  771. xmpString = xmpString.slice(0, indexOfXmp)
  772. + 'xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" '
  773. + 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
  774. + 'xmlns:tiff="http://ns.adobe.com/tiff/1.0/" '
  775. + 'xmlns:plus="http://schemas.android.com/apk/lib/com.google.android.gms.plus" '
  776. + 'xmlns:ext="http://www.gettyimages.com/xsltExtension/1.0" '
  777. + 'xmlns:exif="http://ns.adobe.com/exif/1.0/" '
  778. + 'xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" '
  779. + 'xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" '
  780. + 'xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" '
  781. + 'xmlns:xapGImg="http://ns.adobe.com/xap/1.0/g/img/" '
  782. + 'xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" '
  783. + xmpString.slice(indexOfXmp)
  784. var domDocument = dom.parseFromString( xmpString, 'text/xml' );
  785. return xml2Object(domDocument);
  786. } else{
  787. offset++;
  788. }
  789. }
  790. }
  791. function xml2json(xml) {
  792. var json = {};
  793. if (xml.nodeType == 1) { // element node
  794. if (xml.attributes.length > 0) {
  795. json['@attributes'] = {};
  796. for (var j = 0; j < xml.attributes.length; j++) {
  797. var attribute = xml.attributes.item(j);
  798. json['@attributes'][attribute.nodeName] = attribute.nodeValue;
  799. }
  800. }
  801. } else if (xml.nodeType == 3) { // text node
  802. return xml.nodeValue;
  803. }
  804. // deal with children
  805. if (xml.hasChildNodes()) {
  806. for(var i = 0; i < xml.childNodes.length; i++) {
  807. var child = xml.childNodes.item(i);
  808. var nodeName = child.nodeName;
  809. if (json[nodeName] == null) {
  810. json[nodeName] = xml2json(child);
  811. } else {
  812. if (json[nodeName].push == null) {
  813. var old = json[nodeName];
  814. json[nodeName] = [];
  815. json[nodeName].push(old);
  816. }
  817. json[nodeName].push(xml2json(child));
  818. }
  819. }
  820. }
  821. return json;
  822. }
  823. function xml2Object(xml) {
  824. try {
  825. var obj = {};
  826. if (xml.children.length > 0) {
  827. for (var i = 0; i < xml.children.length; i++) {
  828. var item = xml.children.item(i);
  829. var attributes = item.attributes;
  830. for(var idx in attributes) {
  831. var itemAtt = attributes[idx];
  832. var dataKey = itemAtt.nodeName;
  833. var dataValue = itemAtt.nodeValue;
  834. if(dataKey !== undefined) {
  835. obj[dataKey] = dataValue;
  836. }
  837. }
  838. var nodeName = item.nodeName;
  839. if (typeof (obj[nodeName]) == "undefined") {
  840. obj[nodeName] = xml2json(item);
  841. } else {
  842. if (typeof (obj[nodeName].push) == "undefined") {
  843. var old = obj[nodeName];
  844. obj[nodeName] = [];
  845. obj[nodeName].push(old);
  846. }
  847. obj[nodeName].push(xml2json(item));
  848. }
  849. }
  850. } else {
  851. obj = xml.textContent;
  852. }
  853. return obj;
  854. } catch (e) {
  855. console.log(e.message);
  856. }
  857. }
  858. EXIF.enableXmp = function() {
  859. EXIF.isXmpEnabled = true;
  860. }
  861. EXIF.disableXmp = function() {
  862. EXIF.isXmpEnabled = false;
  863. }
  864. EXIF.getData = function(img, callback) {
  865. if (((self.Image && img instanceof self.Image)
  866. || (self.HTMLImageElement && img instanceof self.HTMLImageElement))
  867. && !img.complete)
  868. return false;
  869. if (!imageHasData(img)) {
  870. getImageData(img, callback);
  871. } else {
  872. if (callback) {
  873. callback.call(img);
  874. }
  875. }
  876. return true;
  877. }
  878. EXIF.getTag = function(img, tag) {
  879. if (!imageHasData(img)) return;
  880. return img.exifdata[tag];
  881. }
  882. EXIF.getIptcTag = function(img, tag) {
  883. if (!imageHasData(img)) return;
  884. return img.iptcdata[tag];
  885. }
  886. EXIF.getAllTags = function(img) {
  887. if (!imageHasData(img)) return {};
  888. var a,
  889. data = img.exifdata,
  890. tags = {};
  891. for (a in data) {
  892. if (data.hasOwnProperty(a)) {
  893. tags[a] = data[a];
  894. }
  895. }
  896. return tags;
  897. }
  898. EXIF.getAllIptcTags = function(img) {
  899. if (!imageHasData(img)) return {};
  900. var a,
  901. data = img.iptcdata,
  902. tags = {};
  903. for (a in data) {
  904. if (data.hasOwnProperty(a)) {
  905. tags[a] = data[a];
  906. }
  907. }
  908. return tags;
  909. }
  910. EXIF.pretty = function(img) {
  911. if (!imageHasData(img)) return "";
  912. var a,
  913. data = img.exifdata,
  914. strPretty = "";
  915. for (a in data) {
  916. if (data.hasOwnProperty(a)) {
  917. if (typeof data[a] == "object") {
  918. if (data[a] instanceof Number) {
  919. strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
  920. } else {
  921. strPretty += a + " : [" + data[a].length + " values]\r\n";
  922. }
  923. } else {
  924. strPretty += a + " : " + data[a] + "\r\n";
  925. }
  926. }
  927. }
  928. return strPretty;
  929. }
  930. EXIF.readFromBinaryFile = function(file) {
  931. return findEXIFinJPEG(file);
  932. }
  933. if (typeof define === 'function' && define.amd) {
  934. define('exif-js', [], function() {
  935. return EXIF;
  936. });
  937. }
  938. }.call(this));