PluginProbe
Comments Extra Fields For Post,Pages and CPT / 4.4
Comments Extra Fields For Post,Pages and CPT v4.4
trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 2.2 2.3 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 5.0 5.1 5.2
wp-comment-fields / js / exif.js

exif.js in Comments Extra Fields For Post,Pages and CPT 4.4, at js/exif.js

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