1//Javascript EXIF Reader 0.1.6 2//Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/ 3//Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] 4 5var EXIF = (function() { 6 7 var debug = false; 8 9 var ExifTags = { 10 11 // version tags 12 0x9000 : "ExifVersion", // EXIF version 13 0xA000 : "FlashpixVersion", // Flashpix format version 14 15 // colorspace tags 16 0xA001 : "ColorSpace", // Color space information tag 17 18 // image configuration 19 0xA002 : "PixelXDimension", // Valid width of meaningful image 20 0xA003 : "PixelYDimension", // Valid height of meaningful image 21 0x9101 : "ComponentsConfiguration", // Information about channels 22 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel 23 24 // user information 25 0x927C : "MakerNote", // Any desired information written by the manufacturer 26 0x9286 : "UserComment", // Comments by user 27 28 // related file 29 0xA004 : "RelatedSoundFile", // Name of related sound file 30 31 // date and time 32 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated 33 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally 34 0x9290 : "SubsecTime", // Fractions of seconds for DateTime 35 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal 36 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized 37 38 // picture-taking conditions 39 0x829A : "ExposureTime", // Exposure time (in seconds) 40 0x829D : "FNumber", // F number 41 0x8822 : "ExposureProgram", // Exposure program 42 0x8824 : "SpectralSensitivity", // Spectral sensitivity 43 0x8827 : "ISOSpeedRatings", // ISO speed rating 44 0x8828 : "OECF", // Optoelectric conversion factor 45 0x9201 : "ShutterSpeedValue", // Shutter speed 46 0x9202 : "ApertureValue", // Lens aperture 47 0x9203 : "BrightnessValue", // Value of brightness 48 0x9204 : "ExposureBias", // Exposure bias 49 0x9205 : "MaxApertureValue", // Smallest F number of lens 50 0x9206 : "SubjectDistance", // Distance to subject in meters 51 0x9207 : "MeteringMode", // Metering mode 52 0x9208 : "LightSource", // Kind of light source 53 0x9209 : "Flash", // Flash status 54 0x9214 : "SubjectArea", // Location and area of main subject 55 0x920A : "FocalLength", // Focal length of the lens in mm 56 0xA20B : "FlashEnergy", // Strobe energy in BCPS 57 0xA20C : "SpatialFrequencyResponse", // 58 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit 59 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit 60 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 61 0xA214 : "SubjectLocation", // Location of subject in image 62 0xA215 : "ExposureIndex", // Exposure index selected on camera 63 0xA217 : "SensingMethod", // Image sensor type 64 0xA300 : "FileSource", // Image source (3 == DSC) 65 0xA301 : "SceneType", // Scene type (1 == directly photographed) 66 0xA302 : "CFAPattern", // Color filter array geometric pattern 67 0xA401 : "CustomRendered", // Special processing 68 0xA402 : "ExposureMode", // Exposure mode 69 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual 70 0xA404 : "DigitalZoomRation", // Digital zoom ratio 71 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) 72 0xA406 : "SceneCaptureType", // Type of scene 73 0xA407 : "GainControl", // Degree of overall image gain adjustment 74 0xA408 : "Contrast", // Direction of contrast processing applied by camera 75 0xA409 : "Saturation", // Direction of saturation processing applied by camera 76 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera 77 0xA40B : "DeviceSettingDescription", // 78 0xA40C : "SubjectDistanceRange", // Distance to subject 79 80 // other tags 81 0xA005 : "InteroperabilityIFDPointer", 82 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image 83 }; 84 85 var TiffTags = { 86 0x0100 : "ImageWidth", 87 0x0101 : "ImageHeight", 88 0x8769 : "ExifIFDPointer", 89 0x8825 : "GPSInfoIFDPointer", 90 0xA005 : "InteroperabilityIFDPointer", 91 0x0102 : "BitsPerSample", 92 0x0103 : "Compression", 93 0x0106 : "PhotometricInterpretation", 94 0x0112 : "Orientation", 95 0x0115 : "SamplesPerPixel", 96 0x011C : "PlanarConfiguration", 97 0x0212 : "YCbCrSubSampling", 98 0x0213 : "YCbCrPositioning", 99 0x011A : "XResolution", 100 0x011B : "YResolution", 101 0x0128 : "ResolutionUnit", 102 0x0111 : "StripOffsets", 103 0x0116 : "RowsPerStrip", 104 0x0117 : "StripByteCounts", 105 0x0201 : "JPEGInterchangeFormat", 106 0x0202 : "JPEGInterchangeFormatLength", 107 0x012D : "TransferFunction", 108 0x013E : "WhitePoint", 109 0x013F : "PrimaryChromaticities", 110 0x0211 : "YCbCrCoefficients", 111 0x0214 : "ReferenceBlackWhite", 112 0x0132 : "DateTime", 113 0x010E : "ImageDescription", 114 0x010F : "Make", 115 0x0110 : "Model", 116 0x0131 : "Software", 117 0x013B : "Artist", 118 0x8298 : "Copyright" 119 }; 120 121 var GPSTags = { 122 0x0000 : "GPSVersionID", 123 0x0001 : "GPSLatitudeRef", 124 0x0002 : "GPSLatitude", 125 0x0003 : "GPSLongitudeRef", 126 0x0004 : "GPSLongitude", 127 0x0005 : "GPSAltitudeRef", 128 0x0006 : "GPSAltitude", 129 0x0007 : "GPSTimeStamp", 130 0x0008 : "GPSSatellites", 131 0x0009 : "GPSStatus", 132 0x000A : "GPSMeasureMode", 133 0x000B : "GPSDOP", 134 0x000C : "GPSSpeedRef", 135 0x000D : "GPSSpeed", 136 0x000E : "GPSTrackRef", 137 0x000F : "GPSTrack", 138 0x0010 : "GPSImgDirectionRef", 139 0x0011 : "GPSImgDirection", 140 0x0012 : "GPSMapDatum", 141 0x0013 : "GPSDestLatitudeRef", 142 0x0014 : "GPSDestLatitude", 143 0x0015 : "GPSDestLongitudeRef", 144 0x0016 : "GPSDestLongitude", 145 0x0017 : "GPSDestBearingRef", 146 0x0018 : "GPSDestBearing", 147 0x0019 : "GPSDestDistanceRef", 148 0x001A : "GPSDestDistance", 149 0x001B : "GPSProcessingMethod", 150 0x001C : "GPSAreaInformation", 151 0x001D : "GPSDateStamp", 152 0x001E : "GPSDifferential" 153 }; 154 155 var StringValues = { 156 ExposureProgram : { 157 0 : "Not defined", 158 1 : "Manual", 159 2 : "Normal program", 160 3 : "Aperture priority", 161 4 : "Shutter priority", 162 5 : "Creative program", 163 6 : "Action program", 164 7 : "Portrait mode", 165 8 : "Landscape mode" 166 }, 167 MeteringMode : { 168 0 : "Unknown", 169 1 : "Average", 170 2 : "CenterWeightedAverage", 171 3 : "Spot", 172 4 : "MultiSpot", 173 5 : "Pattern", 174 6 : "Partial", 175 255 : "Other" 176 }, 177 LightSource : { 178 0 : "Unknown", 179 1 : "Daylight", 180 2 : "Fluorescent", 181 3 : "Tungsten (incandescent light)", 182 4 : "Flash", 183 9 : "Fine weather", 184 10 : "Cloudy weather", 185 11 : "Shade", 186 12 : "Daylight fluorescent (D 5700 - 7100K)", 187 13 : "Day white fluorescent (N 4600 - 5400K)", 188 14 : "Cool white fluorescent (W 3900 - 4500K)", 189 15 : "White fluorescent (WW 3200 - 3700K)", 190 17 : "Standard light A", 191 18 : "Standard light B", 192 19 : "Standard light C", 193 20 : "D55", 194 21 : "D65", 195 22 : "D75", 196 23 : "D50", 197 24 : "ISO studio tungsten", 198 255 : "Other" 199 }, 200 Flash : { 201 0x0000 : "Flash did not fire", 202 0x0001 : "Flash fired", 203 0x0005 : "Strobe return light not detected", 204 0x0007 : "Strobe return light detected", 205 0x0009 : "Flash fired, compulsory flash mode", 206 0x000D : "Flash fired, compulsory flash mode, return light not detected", 207 0x000F : "Flash fired, compulsory flash mode, return light detected", 208 0x0010 : "Flash did not fire, compulsory flash mode", 209 0x0018 : "Flash did not fire, auto mode", 210 0x0019 : "Flash fired, auto mode", 211 0x001D : "Flash fired, auto mode, return light not detected", 212 0x001F : "Flash fired, auto mode, return light detected", 213 0x0020 : "No flash function", 214 0x0041 : "Flash fired, red-eye reduction mode", 215 0x0045 : "Flash fired, red-eye reduction mode, return light not detected", 216 0x0047 : "Flash fired, red-eye reduction mode, return light detected", 217 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode", 218 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 219 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 220 0x0059 : "Flash fired, auto mode, red-eye reduction mode", 221 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode", 222 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode" 223 }, 224 SensingMethod : { 225 1 : "Not defined", 226 2 : "One-chip color area sensor", 227 3 : "Two-chip color area sensor", 228 4 : "Three-chip color area sensor", 229 5 : "Color sequential area sensor", 230 7 : "Trilinear sensor", 231 8 : "Color sequential linear sensor" 232 }, 233 SceneCaptureType : { 234 0 : "Standard", 235 1 : "Landscape", 236 2 : "Portrait", 237 3 : "Night scene" 238 }, 239 SceneType : { 240 1 : "Directly photographed" 241 }, 242 CustomRendered : { 243 0 : "Normal process", 244 1 : "Custom process" 245 }, 246 WhiteBalance : { 247 0 : "Auto white balance", 248 1 : "Manual white balance" 249 }, 250 GainControl : { 251 0 : "None", 252 1 : "Low gain up", 253 2 : "High gain up", 254 3 : "Low gain down", 255 4 : "High gain down" 256 }, 257 Contrast : { 258 0 : "Normal", 259 1 : "Soft", 260 2 : "Hard" 261 }, 262 Saturation : { 263 0 : "Normal", 264 1 : "Low saturation", 265 2 : "High saturation" 266 }, 267 Sharpness : { 268 0 : "Normal", 269 1 : "Soft", 270 2 : "Hard" 271 }, 272 SubjectDistanceRange : { 273 0 : "Unknown", 274 1 : "Macro", 275 2 : "Close view", 276 3 : "Distant view" 277 }, 278 FileSource : { 279 3 : "DSC" 280 }, 281 282 Components : { 283 0 : "", 284 1 : "Y", 285 2 : "Cb", 286 3 : "Cr", 287 4 : "R", 288 5 : "G", 289 6 : "B" 290 } 291 }; 292 293 function addEvent(element, event, handler) { 294 if (element.addEventListener) { 295 element.addEventListener(event, handler, false); 296 } else if (element.attachEvent) { 297 element.attachEvent("on" + event, handler); 298 } 299 } 300 301 function imageHasData(img) { 302 return !!(img.exifdata); 303 } 304 305 function getImageData(img, callback) { 306 function handleBinaryFile(binFile) { 307 var data = findEXIFinJPEG(binFile); 308 img.exifdata = data || {}; 309 if (callback) { 310 callback.call(img) 311 } 312 } 313 314 if (img instanceof Image) {
315 BinaryAjax(img.src, function(http) { 316 handleBinaryFile(http.binaryResponse); 317 }); 318 } else if (window.FileReader && img instanceof window.File) { 319 var fileReader = new FileReader(); 320 321 fileReader.onload = function(e) { 322 handleBinaryFile(new BinaryFile(e.target.result)); 323 }; 324 325 try{ 326 fileReader.readAsBinaryString(img); 327 } 328 catch(err){ 329 console.log("exif.js error: "+err.message); 330 } 331 332 } 333 } 334 335 function findEXIFinJPEG(file) { 336 if (file.getByteAt(0) != 0xFF || file.getByteAt(1) != 0xD8) { 337 return false; // not a valid jpeg 338 } 339 340 var offset = 2, 341 length = file.getLength(), 342 marker; 343 344 while (offset < length) { 345 if (file.getByteAt(offset) != 0xFF) { 346 if (debug) console.log("Not a valid marker at offset " + offset + ", found: " + file.getByteAt(offset)); 347 return false; // not a valid marker, something is wrong 348 } 349 350 marker = file.getByteAt(offset+1); 351 352 // we could implement handling for other markers here, 353 // but we're only looking for 0xFFE1 for EXIF data 354 355 if (marker == 22400) { 356 if (debug) console.log("Found 0xFFE1 marker"); 357 358 return readEXIFData(file, offset + 4, file.getShortAt(offset+2, true)-2); 359 360 // offset += 2 + file.getShortAt(offset+2, true); 361 362 } else if (marker == 225) { 363 // 0xE1 = Application-specific 1 (for EXIF) 364 if (debug) console.log("Found 0xFFE1 marker"); 365 366 return readEXIFData(file, offset + 4, file.getShortAt(offset+2, true)-2); 367 368 } else { 369 offset += 2 + file.getShortAt(offset+2, true); 370 } 371 372 } 373 374 } 375 376 377 function readTags(file, tiffStart, dirStart, strings, bigEnd) { 378 var entries = file.getShortAt(dirStart, bigEnd), 379 tags = {}, 380 entryOffset, tag, 381 i; 382 383 for (i=0;i<entries;i++) { 384 entryOffset = dirStart + i*12 + 2; 385 tag = strings[file.getShortAt(entryOffset, bigEnd)]; 386 if (!tag && debug) console.log("Unknown tag: " + file.getShortAt(entryOffset, bigEnd)); 387 tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); 388 } 389 return tags; 390 } 391 392 393 function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { 394 var type = file.getShortAt(entryOffset+2, bigEnd), 395 numValues = file.getLongAt(entryOffset+4, bigEnd), 396 valueOffset = file.getLongAt(entryOffset+8, bigEnd) + tiffStart, 397 offset, 398 vals, val, n, 399 numerator, denominator; 400 401 switch (type) { 402 case 1: // byte, 8-bit unsigned int 403 case 7: // undefined, 8-bit byte, value depending on field 404 if (numValues == 1) { 405 return file.getByteAt(entryOffset + 8, bigEnd); 406 } else { 407 offset = numValues > 4 ? valueOffset : (entryOffset + 8); 408 vals = []; 409 for (n=0;n<numValues;n++) { 410 vals[n] = file.getByteAt(offset + n); 411 } 412 return vals; 413 } 414 415 case 2: // ascii, 8-bit byte 416 offset = numValues > 4 ? valueOffset : (entryOffset + 8); 417 return file.getStringAt(offset, numValues-1); 418 419 case 3: // short, 16 bit int 420 if (numValues == 1) { 421 return file.getShortAt(entryOffset + 8, bigEnd); 422 } else { 423 offset = numValues > 2 ? valueOffset : (entryOffset + 8); 424 vals = []; 425 for (n=0;n<numValues;n++) { 426 vals[n] = file.getShortAt(offset + 2*n, bigEnd); 427 } 428 return vals; 429 } 430 431 case 4: // long, 32 bit int 432 if (numValues == 1) { 433 return file.getLongAt(entryOffset + 8, bigEnd); 434 } else { 435 vals = []; 436 for (var n=0;n<numValues;n++) { 437 vals[n] = file.getLongAt(valueOffset + 4*n, bigEnd); 438 } 439 return vals; 440 } 441 442 case 5: // rational = two long values, first is numerator, second is denominator 443 if (numValues == 1) { 444 numerator = file.getLongAt(valueOffset, bigEnd); 445 denominator = file.getLongAt(valueOffset+4, bigEnd); 446 val = new Number(numerator / denominator); 447 val.numerator = numerator; 448 val.denominator = denominator; 449 return val; 450 } else { 451 vals = []; 452 for (n=0;n<numValues;n++) { 453 numerator = file.getLongAt(valueOffset + 8*n, bigEnd); 454 denominator = file.getLongAt(valueOffset+4 + 8*n, bigEnd); 455 vals[n] = new Number(numerator / denominator); 456 vals[n].numerator = numerator; 457 vals[n].denominator = denominator; 458 } 459 return vals; 460 } 461 462 case 9: // slong, 32 bit signed int 463 if (numValues == 1) { 464 return file.getSLongAt(entryOffset + 8, bigEnd); 465 } else { 466 vals = []; 467 for (n=0;n<numValues;n++) { 468 vals[n] = file.getSLongAt(valueOffset + 4*n, bigEnd); 469 } 470 return vals; 471 } 472 473 case 10: // signed rational, two slongs, first is numerator, second is denominator 474 if (numValues == 1) { 475 return file.getSLongAt(valueOffset, bigEnd) / file.getSLongAt(valueOffset+4, bigEnd); 476 } else { 477 vals = []; 478 for (n=0;n<numValues;n++) { 479 vals[n] = file.getSLongAt(valueOffset + 8*n, bigEnd) / file.getSLongAt(valueOffset+4 + 8*n, bigEnd); 480 } 481 return vals; 482 } 483 } 484 } 485 486 487 function readEXIFData(file, start) { 488 if (file.getStringAt(start, 4) != "Exif") { 489 if (debug) console.log("Not valid EXIF data! " + file.getStringAt(start, 4)); 490 return false; 491 } 492 493 var bigEnd, 494 tags, tag, 495 exifData, gpsData, 496 tiffOffset = start + 6; 497 498 // test for TIFF validity and endianness 499 if (file.getShortAt(tiffOffset) == 0x4949) { 500 bigEnd = false; 501 } else if (file.getShortAt(tiffOffset) == 0x4D4D) { 502 bigEnd = true; 503 } else { 504 if (debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"); 505 return false; 506 } 507 508 if (file.getShortAt(tiffOffset+2, bigEnd) != 0x002A) { 509 if (debug) console.log("Not valid TIFF data! (no 0x002A)"); 510 return false; 511 } 512 513 if (file.getLongAt(tiffOffset+4, bigEnd) != 0x00000008) { 514 if (debug) console.log("Not valid TIFF data! (First offset not 8)", file.getShortAt(tiffOffset+4, bigEnd)); 515 return false; 516 } 517 518 tags = readTags(file, tiffOffset, tiffOffset+8, TiffTags, bigEnd); 519 520 if (tags.ExifIFDPointer) { 521 exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd); 522 for (tag in exifData) { 523 switch (tag) { 524 case "LightSource" : 525 case "Flash" : 526 case "MeteringMode" : 527 case "ExposureProgram" : 528 case "SensingMethod" : 529 case "SceneCaptureType" : 530 case "SceneType" : 531 case "CustomRendered" : 532 case "WhiteBalance" : 533 case "GainControl" : 534 case "Contrast" : 535 case "Saturation" : 536 case "Sharpness" : 537 case "SubjectDistanceRange" : 538 case "FileSource" : 539 exifData[tag] = StringValues[tag][exifData[tag]]; 540 break; 541 542 case "ExifVersion" : 543 case "FlashpixVersion" : 544 exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]); 545 break; 546 547 case "ComponentsConfiguration" : 548 exifData[tag] = 549 StringValues.Components[exifData[tag][0]] 550 + StringValues.Components[exifData[tag][1]] 551 + StringValues.Components[exifData[tag][2]] 552 + StringValues.Components[exifData[tag][3]]; 553 break; 554 }
555 tags[tag] = exifData[tag]; 556 } 557 } 558 559 if (tags.GPSInfoIFDPointer) { 560 gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd); 561 for (tag in gpsData) { 562 switch (tag) { 563 case "GPSVersionID" : 564 gpsData[tag] = gpsData[tag][0] 565 + "." + gpsData[tag][1] 566 + "." + gpsData[tag][2] 567 + "." + gpsData[tag][3]; 568 break; 569 } 570 tags[tag] = gpsData[tag]; 571 } 572 } 573 574 return tags; 575 } 576 577 578 function getData(img, callback) { 579 if (img instanceof Image && !img.complete) return false; 580 if (!imageHasData(img)) { 581 getImageData(img, callback); 582 } else { 583 if (callback) { 584 callback.call(img); 585 } 586 } 587 return true; 588 } 589 590 function getTag(img, tag) { 591 if (!imageHasData(img)) return; 592 return img.exifdata[tag]; 593 } 594 595 function getAllTags(img) { 596 if (!imageHasData(img)) return {}; 597 var a, 598 data = img.exifdata, 599 tags = {}; 600 for (a in data) { 601 if (data.hasOwnProperty(a)) { 602 tags[a] = data[a]; 603 } 604 } 605 return tags; 606 } 607 608 function pretty(img) { 609 if (!imageHasData(img)) return ""; 610 var a, 611 data = img.exifdata, 612 strPretty = ""; 613 for (a in data) { 614 if (data.hasOwnProperty(a)) { 615 if (typeof data[a] == "object") { 616 if (data[a] instanceof Number) { 617 strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n"; 618 } else { 619 strPretty += a + " : [" + data[a].length + " values]\r\n"; 620 } 621 } else { 622 strPretty += a + " : " + data[a] + "\r\n"; 623 } 624 } 625 } 626 return strPretty; 627 } 628 629 function readFromBinaryFile(file) { 630 return findEXIFinJPEG(file); 631 } 632 633 634 return { 635 readFromBinaryFile : readFromBinaryFile, 636 pretty : pretty, 637 getTag : getTag, 638 getAllTags : getAllTags, 639 getData : getData, 640 641 Tags : ExifTags, 642 TiffTags : TiffTags, 643 GPSTags : GPSTags, 644 StringValues : StringValues 645 }; 646 647})(); 648
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.