Last active
August 29, 2015 14:01
-
-
Save redaktor/c3683f49cab545e91729 to your computer and use it in GitHub Desktop.
Revisions
-
redaktor revised this gist
May 7, 2014 . No changes.There are no files selected for viewing
-
redaktor revised this gist
May 7, 2014 . 1 changed file with 114 additions and 12 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -316,6 +316,20 @@ ExifImage.prototype.tidyString = function(str) { if (str.toLowerCase() == 'undefined' || str.toLowerCase() == 'unknown') str = ''; return str.trim(); } ExifImage.prototype.pad = function(input, chr, len) { var returnString = input; while (returnString.length < len) { returnString = chr + returnString; } return returnString; } ExifImage.prototype.intArrayToHexString = function(arrayOfInts) { var response = ''; for ( var i in arrayOfInts) { response += ExifImage.prototype.pad(arrayOfInts[i].toString(16), '0', 2); } return response; } ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, isBigEndian, tags) { @@ -434,12 +448,16 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, /* composite values */ var pair = null; switch(entry.tagName){ case 'SerialNumber': pair = self.exifData.image.Make; break; case 'GPSLatitude': pair = self.exifData.gps.GPSLatitudeRef.value; break; case 'GPSLongitude': pair = self.exifData.gps.GPSLongitudeRef.value; break; } entry.value = (pair) ? ref(entry.value, pair) : ref(entry.value); } else if (entry.value in ref){ @@ -935,15 +953,15 @@ ExifImage.TAGS = { ref : { /* helper functions - TODO might go in helper module */ arrToDeg: function(nArr, lRef){ var deg = parseFloat(nArr[0]), m = parseFloat(nArr[1]), s = parseFloat(nArr[2]); if(s==0 && m>0){ var _m = Math.floor(m); s = (m-_m)*60; m = _m; _m = null; } if(typeof deg !== 'number' || typeof m !== 'number' || typeof s !== 'number') return nArr; if (lRef === 'S' || lRef === 'N' || lRef === 'E' || lRef === 'W') { var lInt = (lRef == 'S' || lRef == 'W') ? -1 : 1; var v = (deg+(m/60)+(s/3600)) * lInt; @@ -955,7 +973,14 @@ ExifImage.TAGS = { return [deg, m, s]; }, decToFrac: function(d){ /* TODO - NOTE : some vendors handle infinite values incorrect or have "finetuned" values // needs fix // e.g. Leica exposureCompensation: '-85/256 EV', value: -0.33203125 - preparse toFixed(2) ??? */ if(typeof d == 'number'){ if(d===0) return 0; var pref = (d>0) ? '+' : '-'; var df = 1, top = 1, bot = 1; var limit = 1e5; @@ -980,16 +1005,17 @@ ExifImage.TAGS = { var v = parseInt(vStr); return (typeof v === 'number' && v!=0) ? (v/100).toString() : vStr; }, expoTime : function(t){ if (typeof t === 'number' && t < 0.25001 && t > 0) { return '1/'.concat(Math.floor(0.5 + 1/t)); } return (typeof t === 'number') ? t.toFixed(1).replace(/\.0$/, '') : t.replace(/\.0$/, ''); }, aperture : function(d){ var v = Math.pow(2, (d / 2)); return (typeof v == 'number') ? {description:v, value:d} : d; }, /* helper end */ ExifVersion : function(data){ return ExifImage.TAGS.ref.versions(data); }, @@ -1013,6 +1039,82 @@ ExifImage.TAGS = { } } */ SerialNumber : function(serial, make) { var returnSerial = ExifImage.prototype.tidyString(serial); switch (make) { case "Canon": if (returnSerial.length > 6) { returnSerial = ExifImage.prototype.pad(returnSerial, "0", 10); } else { returnSerial = ExifImage.prototype.pad(returnSerial, "0", 6); } break; case "FUJIFILM": var startOf12CharBlock = returnSerial.lastIndexOf(" ") + 1; if (startOf12CharBlock == -1) { returnSerial + ""; break; } var iDateIndex = startOf12CharBlock + 12; var year = returnSerial.substr(iDateIndex, 2); if (year > 80) { year = "19" + year; } else { year = "20" + year; } var month = returnSerial.substr(iDateIndex + 2, 2); var date = returnSerial.substr(iDateIndex + 4, 2); var lastChunk = returnSerial.substr(iDateIndex + 6, 12); var returnSerial = returnSerial.substr(0, iDateIndex) + " " + year + ":" + month + ":" + date + " " + lastChunk; if (lastChunk.length < 12) { returnSerial = ""; } break; case "Panasonic": var year = String.fromCharCode(serial[3]) + String.fromCharCode(serial[4]); var month = String.fromCharCode(serial[5]) + String.fromCharCode(serial[6]); var date = String.fromCharCode(serial[7]) + String.fromCharCode(serial[8]); var iYear = parseInt(year, 10); var iMonth = parseInt(month, 10); var iDate = parseInt(date, 10); returnSerial = ""; if (isNaN(iYear) || isNaN(iMonth) || isNaN(iDate) || iYear < 0 || iYear > 99 || iMonth < 1 || iMonth > 12 || iDate < 1 || iDate > 31) { // error } else { returnSerial = "(" + String.fromCharCode(serial[0]) + String.fromCharCode(serial[1]) + String.fromCharCode(serial[2]) + ")"; returnSerial += " 20" + year; // year returnSerial += ":" + month; // month returnSerial += ":" + date; // date returnSerial += " no. " + String.fromCharCode(serial[9]) + String.fromCharCode(serial[10]) + String.fromCharCode(serial[11]) + String.fromCharCode(serial[12]); // id } break; case "Pentax": if (returnSerial.length != 7) { returnSerial = ""; } break; } return returnSerial.trim(); }, Thresholding : { 1: 'No dithering or halftoning', 2: 'Ordered dither or halftone', @@ -1456,7 +1558,7 @@ ExifImage.TAGS = { 2: 'Auto bracket', 3: 'Samsung EX/NX specific' }, /* TODO DNG - own namespace ? - needs fix - DNG reader for headers 0xc612: { Name: 'DNGVersion', Notes: 'tags 0xc612-0xc7b5 are used in DNG images unless otherwise noted', -
redaktor revised this gist
May 7, 2014 . No changes.There are no files selected for viewing
-
redaktor revised this gist
May 7, 2014 . No changes.There are no files selected for viewing
-
redaktor revised this gist
May 7, 2014 . 1 changed file with 2 additions and 18 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -7,30 +7,14 @@ var fs = require('fs'), # This allows conditional replacement with 'exiftool -TAG-= -TAG=VALUE'. # - also removed are any other trailing whitespace characters --- SubIFD for RAWs --- ThumbnailOffset --- ISO 0x8827 / 0x8833 - is it a difference? --- # handle maker notes as a conditional list 0x927c: \@Image::ExifTool::MakerNotes::Main, 0xa432: { #24 Name: 'LensInfo', -
redaktor revised this gist
May 6, 2014 . 1 changed file with 65 additions and 56 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -277,50 +277,62 @@ ExifImage.prototype.extractExifData = function (data, start, length) { // Look for Makernote data in the Exif IFD, check which type of proprietary // Makernotes the image contains, load the respective functionality and // start the extraction // check explicitly for the getString method in case somehow this isn't // a buffer. Found this in an image in the wild var makerNoteValue = this.exifData.exif[ExifImage.TAGS.exif[0x927C]]; var delMakerNoteBuffer = false; if (typeof makerNoteValue != 'undefined') { if (typeof makerNoteValue.getString == 'undefined' && typeof makerNoteValue.length != 'undefined') { // assume we can convert to buffer (we can do arrays and strings) makerNoteValue = new Buffer(makerNoteValue); } if (typeof makerNoteValue.getString != 'undefined') { // Check the header to see what kind of Makernote we are dealing with if (makerNoteValue.getString(0, 7) === 'OLYMP\x00\x01' || makerNoteValue.getString(0, 7) === 'OLYMP\x00\x02') { this.extractMakernotes = require('./makernotes/olympus').extractMakernotes; } else if (makerNoteValue.getString(0, 7) === 'AGFA \x00\x01') { this.extractMakernotes = require('./makernotes/agfa').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === 'EPSON\x00\x01\x00') { this.extractMakernotes = require('./makernotes/epson').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === 'FUJIFILM') { this.extractMakernotes = require('./makernotes/fujifilm').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === 'SANYO') { this.extractMakernotes = require('./makernotes/sanyo').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === 'Nikon') { this.extractMakernotes = require('./makernotes/nikon').extractMakernotes; } else if (makerNoteValue.getString(0, 4) === '%\u0000\u0001\u0000') { this.extractMakernotes = require('./makernotes/canon').extractMakernotes; } else { // Makernotes are available but the format is not recognized so // an error message is pushed instead, this ain't the best // solution but should do for now this.exifData.makernote['error'] = makerNoteValue.getString(0, 5).concat('...: Unable to extract Makernote information as it is in an unsupported or unrecognized format.'); } if (typeof this.exifData.makernote['error'] == 'undefined') { this.exifData.makernote = this.extractMakernotes(data, self.makernoteOffset, tiffOffset); delMakerNoteBuffer = true; } } } // TODO - lang.exists if(delMakerNoteBuffer === true && 'MakerNote' in this.exifData.exif && Buffer.isBuffer(this.exifData.exif.MakerNote)) delete this.exifData.exif.MakerNote; return this.exifData; }; ExifImage.prototype.tidyString = function(str) { if (typeof str === 'undefined') str = ''; str = str + ''; str = str.replace(/[^a-z0-9 \-\/\.\(\)\:\;\,\©\@\\]/gi, ''); str = str.replace(/^\s+|\s+$/g, ''); // trim if (str.toLowerCase() == 'undefined' || str.toLowerCase() == 'unknown') str = ''; return str.trim(); } ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, isBigEndian, tags) { @@ -337,15 +349,6 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, value : [] } entry.tagId = entry.tag.getShort(0, isBigEndian); // The tagId may correspond to more then one tagName so check which @@ -379,7 +382,7 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; entry.value = data.getString(entry.valueOffset, entry.components); if (entry.value[entry.value.length - 1] === '\u0000') // Trim null terminated strings entry.value = this.tidyString(entry.value.substring(0, entry.value.length - 1)); break; case 0x0003: // unsigned short, 2 byte per component @@ -440,7 +443,7 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, // If the value array has only one element we don't need an array if (entry.value.length == 1) entry.value = entry.value[0]; // Is there a string match if (entry.tagName in ExifImage.TAGS.ref){ var ref = ExifImage.TAGS.ref[entry.tagName]; if (typeof ref === 'function'){ @@ -969,20 +972,22 @@ ExifImage.TAGS = { }, decToFrac: function(d){ if(typeof d == 'number'){ var pref = (d>0) ? '+' : '-'; var df = 1, top = 1, bot = 1; var limit = 1e5; var _d = Math.abs(d); while (df != _d && limit-- > 0) { if (df < _d) { top += 1; } else { bot += 1; top = parseInt(_d * bot, 10); } df = top / bot; } _d = null; return {description:pref+top.toString().concat('/', bot, ' EV'), value:d}; } return d; }, @@ -1059,7 +1064,7 @@ ExifImage.TAGS = { 1: 'None', 2: 'Horizontal differencing' }, /* WhitePoint', Groups: { 2: 'Camera' } */ @@ -1225,6 +1230,10 @@ ExifImage.TAGS = { 7: 'Trilinear', 8: 'Color sequential linear', }, UserComment : function(data){ if(Buffer.isBuffer(data)) data = data.toString('utf8'); return ExifImage.prototype.tidyString(data); }, ColorSpace : { 1 : 'sRGB' }, -
redaktor revised this gist
May 6, 2014 . No changes.There are no files selected for viewing
-
redaktor revised this gist
May 6, 2014 . 1 changed file with 65 additions and 74 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -6,11 +6,11 @@ var fs = require('fs'), # may be 'unknown' (filled with spaces) according to the EXIF spec. # This allows conditional replacement with 'exiftool -TAG-= -TAG=VALUE'. # - also removed are any other trailing whitespace characters --- SubIFD --- ThumbnailOffset --- ISO 0x8827: { Name: 'ISO', @@ -22,6 +22,25 @@ var fs = require('fs'), }, and 0x8833 --- # handle maker notes as a conditional list 0x927c: \@Image::ExifTool::MakerNotes::Main, 0x9286: { Name: 'UserComment', # I have seen other applications write it incorrectly as 'string' or 'int8u' #ph Format: 'undef', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', }, 0xa432: { #24 Name: 'LensInfo', Notes: q{ 4 rational values giving focal and aperture ranges, called LensSpecification by the EXIF spec. }, # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4" PrintConv: \&Image::ExifTool::Exif::PrintLensInfo, }, */ /** @@ -948,13 +967,40 @@ ExifImage.TAGS = { } return [deg, m, s]; }, decToFrac: function(d){ if(typeof d == 'number'){ var df = 1, top = 1, bot = 1; var limit = 1e5; while (df != d && limit-- > 0) { if (df < d) { top += 1; } else { bot += 1; top = parseInt(d * bot, 10); } df = top / bot; } return {description:top.concat('/', bot, ' EV'), value:d}; } return d; }, versions : function(data){ var vStr = data.toString('utf8').trim().replace(/^0/, '').replace(/\0+$/, ''); var v = parseInt(vStr); return (typeof v === 'number' && v!=0) ? (v/100).toString() : vStr; }, expoTime : function(_t){ if (typeof _t === 'number' && _t < 0.25001 && _t > 0) { return '1/'.concat(Math.floor(0.5 + 1/_t)); } return (typeof _t === 'number') ? _t.toFixed(1).replace(/\.0$/, '') : _t.replace(/\.0$/, ''); }, aperture : function(data){ var v = Math.pow(2, (data / 2)); return (typeof v == 'number') ? {description:v, value:data} : data; }, /* helper end */ ExifVersion : function(data){ return ExifImage.TAGS.ref.versions(data); }, @@ -1145,66 +1191,16 @@ ExifImage.TAGS = { } return data }, ShutterSpeedValue : function(data){ return ExifImage.TAGS.ref.expoTime(data); }, ApertureValue : function(data){ return ExifImage.TAGS.ref.aperture(data); }, MaxApertureValue : function(data){ return ExifImage.TAGS.ref.aperture(data); }, ExposureCompensation : function(data){ return ExifImage.TAGS.ref.decToFrac(data); }, SubjectDistance : function(data){ return (data.match(/^(inf|undef)$/)) ? data : {description:data.toString().concat(' m'), value:data}; }, FocalLength : function(data){ return (typeof data !== 'number') ? data : {description:data.toString().concat(' mm'), value:data}; }, FocalPlaneResolutionUnit : { 1: 'None', 2: 'inches', @@ -1336,12 +1332,7 @@ ExifImage.TAGS = { 2 : 'Close view', 3 : 'Distant view' }, ExposureTime : function(data){ return ExifImage.TAGS.ref.expoTime(data); }, FileSource : function(data){ return (Buffer.isBuffer(data) && data.toJSON()[0]===3) ? { description:'Digital Still Camera', value:3 } : (parseInt(data)||data); }, -
redaktor revised this gist
May 6, 2014 . 1 changed file with 1487 additions and 590 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,28 @@ var fs = require('fs'), util = require('util'), BufferExtender = require('./Buffer'); /* TODO s # NOTE: trailing 'blanks' (spaces) are removed from all EXIF tags which # may be 'unknown' (filled with spaces) according to the EXIF spec. # This allows conditional replacement with 'exiftool -TAG-= -TAG=VALUE'. # - also removed are any other trailing whitespace characters SubIFD ThumbnailOffset ISO 0x8827: { Name: 'ISO', Notes: q{ called ISOSpeedRatings by EXIF 2.2, then PhotographicSensitivity by the EXIF 2.3 spec. }, PrintConv: '$val=~s/\s+/, /g; $val', }, and 0x8833 */ /** * Represents an image with Exif information. When instantiating it you have to @@ -95,7 +117,7 @@ ExifImage.prototype.processImage = function (data, callback) { while (offset < data.length) { if (data[offset++] != 0xFF) { callback(new Error('Invalid marker found at offset '+(--offset)+'. Expected 0xFF but found 0x'+data[offset].toString(16).toUpperCase()+'.')); return; } @@ -135,13 +157,13 @@ ExifImage.prototype.extractExifData = function (data, start, length) { } else if (data.getShort(tiffOffset) == 0x4D4D) { this.isBigEndian = true; } else { throw new Error('Invalid TIFF data! Expected 0x4949 or 0x4D4D at offset '+(tiffOffset)+' but found 0x'+data[tiffOffset].toString(16).toUpperCase()+data[tiffOffset + 1].toString(16).toUpperCase()+'.'); } // Valid TIFF headers always have 0x002A here if (data.getShort(tiffOffset + 2, this.isBigEndian) != 0x002A) { var expected = (this.isBigEndian) ? '0x002A' : '0x2A00'; throw new Error('Invalid TIFF data! Expected '+expected+' at offset '+(tiffOffset + 2)+' but found 0x'+data[tiffOffset + 2].toString(16).toUpperCase()+data[tiffOffset + 3].toString(16).toUpperCase()+'.'); } /********************************* IFD0 **********************************/ @@ -180,7 +202,7 @@ ExifImage.prototype.extractExifData = function (data, start, length) { // Look for a pointer to the Exif IFD in IFD0 and extract information from // it if available if (typeof this.exifData.image[ExifImage.TAGS.exif[0x8769]] != 'undefined') { ifdOffset = tiffOffset + this.exifData.image[ExifImage.TAGS.exif[0x8769]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); @@ -199,7 +221,7 @@ ExifImage.prototype.extractExifData = function (data, start, length) { // Look for a pointer to the GPS IFD in IFD0 and extract information from // it if available var gpsifdOffset = this.exifData.image[ExifImage.TAGS.exif[0x8825]]; if (typeof gpsifdOffset != 'undefined' && gpsifdOffset > 0) { ifdOffset = tiffOffset + this.exifData.image[ExifImage.TAGS.exif[0x8825]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); @@ -217,7 +239,7 @@ ExifImage.prototype.extractExifData = function (data, start, length) { // Look for a pointer to the interoperatbility IFD in the Exif IFD and // extract information from it if available if (typeof this.exifData.exif[ExifImage.TAGS.exif[0xA005]] != 'undefined') { ifdOffset = tiffOffset + this.exifData.exif[ExifImage.TAGS.exif[0xA005]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); @@ -240,28 +262,28 @@ ExifImage.prototype.extractExifData = function (data, start, length) { // check explicitly for the getString method in case somehow this isn't // a buffer. Found this in an image in the wild var makerNoteValue = this.exifData.exif[ExifImage.TAGS.exif[0x927C]]; if (typeof makerNoteValue != 'undefined') { if (typeof makerNoteValue.getString == 'undefined' && typeof makerNoteValue.length != 'undefined') { // assume we can convert to buffer (we can do arrays and strings) makerNoteValue = new Buffer(makerNoteValue); } if (typeof makerNoteValue.getString != 'undefined') { // Check the header to see what kind of Makernote we are dealing with if (makerNoteValue.getString(0, 7) === 'OLYMP\x00\x01' || makerNoteValue.getString(0, 7) === 'OLYMP\x00\x02') { this.extractMakernotes = require('./makernotes/olympus').extractMakernotes; } else if (makerNoteValue.getString(0, 7) === 'AGFA \x00\x01') { this.extractMakernotes = require('./makernotes/agfa').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === 'EPSON\x00\x01\x00') { this.extractMakernotes = require('./makernotes/epson').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === 'FUJIFILM') { this.extractMakernotes = require('./makernotes/fujifilm').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === 'SANYO') { this.extractMakernotes = require('./makernotes/sanyo').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === 'Nikon') { this.extractMakernotes = require('./makernotes/nikon').extractMakernotes; } else if (makerNoteValue.getString(0, 4) === '%\u0000\u0001\u0000') { this.extractMakernotes = require('./makernotes/canon').extractMakernotes; } else { // Makernotes are available but the format is not recognized so @@ -270,7 +292,7 @@ ExifImage.prototype.extractExifData = function (data, start, length) { this.exifData.makernote['error'] = makerNoteValue.getString(0, 5).concat('...: Unable to extract Makernote information as it is in an unsupported or unrecognized format.'); } if (typeof this.exifData.makernote['error'] == 'undefined') { this.exifData.makernote = this.extractMakernotes(data, self.makernoteOffset, tiffOffset); } } @@ -297,18 +319,18 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, } var tidyString = function(str) { if (typeof str === 'undefined') str = ''; str = str + ''; str = str.replace(/[^a-z0-9 \-\/\.\(\)\:\;\,\©\@\\]/gi, ''); str = str.replace(/^\s+|\s+$/g, ''); // trim if (str.toLowerCase() == 'undefined' || str.toLowerCase() == 'unknown') str = ''; return str.trim(); } entry.tagId = entry.tag.getShort(0, isBigEndian); // The tagId may correspond to more then one tagName so check which if (tags && tags[entry.tagId] && typeof tags[entry.tagId] == 'function') { if (!(entry.tagName = tags[entry.tagId](entry))) { return false; } @@ -337,7 +359,7 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, case 0x0002: // ascii strings, 1 byte per component entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; entry.value = data.getString(entry.valueOffset, entry.components); if (entry.value[entry.value.length - 1] === '\u0000') // Trim null terminated strings entry.value = tidyString(entry.value.substring(0, entry.value.length - 1)); break; @@ -394,7 +416,7 @@ ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, } // If this is the Makernote tag save its offset for later use if (entry.tagName === 'MakerNote') self.makernoteOffset = entry.valueOffset; // If the value array has only one element we don't need an array if (entry.value.length == 1) entry.value = entry.value[0]; @@ -432,435 +454,437 @@ ExifImage.TAGS = { // Exif tags exif : { 0x0001 : 'InteropIndex', 0x0002 : 'InteropVersion', 0x000B : 'ProcessingSoftware', 0x00FE : 'SubfileType', 0x00FF : 'OldSubfileType', 0x0100 : 'ImageWidth', 0x0101 : 'ImageHeight', 0x0102 : 'BitsPerSample', 0x0103 : 'Compression', 0x0106 : 'PhotometricInterpretation', 0x0107 : 'Thresholding', 0x0108 : 'CellWidth', 0x0109 : 'CellLength', 0x010A : 'FillOrder', 0x010D : 'DocumentName', 0x010E : 'ImageDescription', 0x010F : 'Make', 0x0110 : 'Model', 0x0111 : 'StripOffsets', 0x0112 : 'Orientation', 0x0115 : 'SamplesPerPixel', 0x0116 : 'RowsPerStrip', 0x0117 : 'StripByteCounts', 0x0118 : 'MinSampleValue', 0x0119 : 'MaxSampleValue', 0x011A : 'XResolution', 0x011B : 'YResolution', 0x011C : 'PlanarConfiguration', 0x011D : 'PageName', 0x011E : 'XPosition', 0x011F : 'YPosition', 0x0120 : 'FreeOffsets', 0x0121 : 'FreeByteCounts', 0x0122 : 'GrayResponseUnit', 0x0123 : 'GrayResponseCurve', 0x0124 : 'T4Options', 0x0125 : 'T6Options', 0x0128 : 'ResolutionUnit', 0x0129 : 'PageNumber', 0x012C : 'ColorResponseUnit', 0x012D : 'TransferFunction', 0x0131 : 'Software', 0x0132 : 'ModifyDate', 0x013B : 'Artist', 0x013C : 'HostComputer', 0x013D : 'Predictor', 0x013E : 'WhitePoint', 0x013F : 'PrimaryChromaticities', 0x0140 : 'ColorMap', 0x0141 : 'HalftoneHints', 0x0142 : 'TileWidth', 0x0143 : 'TileLength', 0x0144 : 'TileOffsets', 0x0145 : 'TileByteCounts', 0x0146 : 'BadFaxLines', 0x0147 : 'CleanFaxData', 0x0148 : 'ConsecutiveBadFaxLines', 0x014A : 'SubIFD', 0x014C : 'InkSet', 0x014D : 'InkNames', 0x014E : 'NumberofInks', 0x0150 : 'DotRange', 0x0151 : 'TargetPrinter', 0x0152 : 'ExtraSamples', 0x0153 : 'SampleFormat', 0x0154 : 'SMinSampleValue', 0x0155 : 'SMaxSampleValue', 0x0156 : 'TransferRange', 0x0157 : 'ClipPath', 0x0158 : 'XClipPathUnits', 0x0159 : 'YClipPathUnits', 0x015A : 'Indexed', 0x015B : 'JPEGTables', 0x015F : 'OPIProxy', 0x0190 : 'GlobalParametersIFD', 0x0191 : 'ProfileType', 0x0192 : 'FaxProfile', 0x0193 : 'CodingMethods', 0x0194 : 'VersionYear', 0x0195 : 'ModeNumber', 0x01B1 : 'Decode', 0x01B2 : 'DefaultImageColor', 0x01B3 : 'T82Options', 0x01B5 : 'JPEGTables', 0x0200 : 'JPEGProc', 0x0201 : 'ThumbnailOffset', 0x0202 : 'ThumbnailLength', 0x0203 : 'JPEGRestartInterval', 0x0205 : 'JPEGLosslessPredictors', 0x0206 : 'JPEGPointTransforms', 0x0207 : 'JPEGQTables', 0x0208 : 'JPEGDCTables', 0x0209 : 'JPEGACTables', 0x0211 : 'YCbCrCoefficients', 0x0212 : 'YCbCrSubSampling', 0x0213 : 'YCbCrPositioning', 0x0214 : 'ReferenceBlackWhite', 0x022F : 'StripRowCounts', 0x02BC : 'ApplicationNotes', 0x03E7 : 'USPTOMiscellaneous', 0x1000 : 'RelatedImageFileFormat', 0x1001 : 'RelatedImageWidth', 0x1002 : 'RelatedImageHeight', 0x4746 : 'Rating', 0x4747 : 'XP_DIP_XML', 0x4748 : 'StitchInfo', 0x4749 : 'RatingPercent', 0x800D : 'ImageID', 0x80A3 : 'WangTag1', 0x80A4 : 'WangAnnotation', 0x80A5 : 'WangTag3', 0x80A6 : 'WangTag4', 0x80E3 : 'Matteing', 0x80E4 : 'DataType', 0x80E5 : 'ImageDepth', 0x80E6 : 'TileDepth', 0x827D : 'Model2', 0x828D : 'CFARepeatPatternDim', 0x828E : 'CFAPattern2', 0x828F : 'BatteryLevel', 0x8290 : 'KodakIFD', 0x8298 : 'Copyright', 0x829A : 'ExposureTime', 0x829D : 'FNumber', 0x82A5 : 'MDFileTag', 0x82A6 : 'MDScalePixel', 0x82A7 : 'MDColorTable', 0x82A8 : 'MDLabName', 0x82A9 : 'MDSampleInfo', 0x82AA : 'MDPrepDate', 0x82AB : 'MDPrepTime', 0x82AC : 'MDFileUnits', 0x830E : 'PixelScale', 0x8335 : 'AdventScale', 0x8336 : 'AdventRevision', 0x835C : 'UIC1Tag', 0x835D : 'UIC2Tag', 0x835E : 'UIC3Tag', 0x835F : 'UIC4Tag', 0x83BB : 'IPTC-NAA', 0x847E : 'IntergraphPacketData', 0x847F : 'IntergraphFlagRegisters', 0x8480 : 'IntergraphMatrix', 0x8481 : 'INGRReserved', 0x8482 : 'ModelTiePoint', 0x84E0 : 'Site', 0x84E1 : 'ColorSequence', 0x84E2 : 'IT8Header', 0x84E3 : 'RasterPadding', 0x84E4 : 'BitsPerRunLength', 0x84E5 : 'BitsPerExtendedRunLength', 0x84E6 : 'ColorTable', 0x84E7 : 'ImageColorIndicator', 0x84E8 : 'BackgroundColorIndicator', 0x84E9 : 'ImageColorValue', 0x84EA : 'BackgroundColorValue', 0x84EB : 'PixelIntensityRange', 0x84EC : 'TransparencyIndicator', 0x84ED : 'ColorCharacterization', 0x84EE : 'HCUsage', 0x84EF : 'TrapIndicator', 0x84F0 : 'CMYKEquivalent', 0x8546 : 'SEMInfo', 0x8568 : 'AFCP_IPTC', 0x85B8 : 'PixelMagicJBIGOptions', 0x85D8 : 'ModelTransform', 0x8602 : 'WB_GRGBLevels', 0x8606 : 'LeafData', 0x8649 : 'PhotoshopSettings', 0x8769 : 'ExifOffset', 0x8773 : 'ICC_Profile', 0x877F : 'TIFF_FXExtensions', 0x8780 : 'MultiProfiles', 0x8781 : 'SharedData', 0x8782 : 'T88Options', 0x87AC : 'ImageLayer', 0x87AF : 'GeoTiffDirectory', 0x87B0 : 'GeoTiffDoubleParams', 0x87B1 : 'GeoTiffAsciiParams', 0x8822 : 'ExposureProgram', 0x8824 : 'SpectralSensitivity', 0x8825 : 'GPSInfo', 0x8827 : 'ISO', 0x8828 : 'Opto-ElectricConvFactor', 0x8829 : 'Interlace', 0x882A : 'TimeZoneOffset', 0x882B : 'SelfTimerMode', 0x8830 : 'SensitivityType', 0x8831 : 'StandardOutputSensitivity', 0x8832 : 'RecommendedExposureIndex', 0x8833 : 'ISOSpeed', 0x8834 : 'ISOSpeedLatitudeyyy', 0x8835 : 'ISOSpeedLatitudezzz', 0x885C : 'FaxRecvParams', 0x885D : 'FaxSubAddress', 0x885E : 'FaxRecvTime', 0x888A : 'LeafSubIFD', 0x9000 : 'ExifVersion', 0x9003 : 'DateTimeOriginal', 0x9004 : 'CreateDate', // TODO 0x9009 - undef[44] written by Google Plus uploader - PH 0x9009 : 'GooglePlus', 0x9101 : 'ComponentsConfiguration', 0x9102 : 'CompressedBitsPerPixel', 0x9201 : 'ShutterSpeedValue', 0x9202 : 'ApertureValue', 0x9203 : 'BrightnessValue', 0x9204 : 'ExposureCompensation', 0x9205 : 'MaxApertureValue', 0x9206 : 'SubjectDistance', 0x9207 : 'MeteringMode', 0x9208 : 'LightSource', 0x9209 : 'Flash', 0x920A : 'FocalLength', 0x920B : 'FlashEnergy', 0x920C : 'SpatialFrequencyResponse', 0x920D : 'Noise', 0x920E : 'FocalPlaneXResolution', 0x920F : 'FocalPlaneYResolution', 0x9210 : 'FocalPlaneResolutionUnit', 0x9211 : 'ImageNumber', 0x9212 : 'SecurityClassification', 0x9213 : 'ImageHistory', 0x9214 : 'SubjectArea', 0x9215 : 'ExposureIndex', 0x9216 : 'TIFF-EPStandardID', 0x9217 : 'SensingMethod', 0x923A : 'CIP3DataFile', 0x923B : 'CIP3Sheet', 0x923C : 'CIP3Side', 0x923F : 'StoNits', 0x927C : 'MakerNote', 0x9286 : 'UserComment', 0x9290 : 'SubSecTime', 0x9291 : 'SubSecTimeOriginal', 0x9292 : 'SubSecTimeDigitized', 0x932F : 'MSDocumentText', 0x9330 : 'MSPropertySetStorage', 0x9331 : 'MSDocumentTextPosition', 0x935C : 'ImageSourceData', 0x9C9B : 'XPTitle', 0x9C9C : 'XPComment', 0x9C9D : 'XPAuthor', 0x9C9E : 'XPKeywords', 0x9C9F : 'XPSubject', 0xA000 : 'FlashpixVersion', 0xA001 : 'ColorSpace', 0xA002 : 'ExifImageWidth', 0xA003 : 'ExifImageHeight', 0xA004 : 'RelatedSoundFile', 0xA005 : 'InteropOffset', 0xA20B : 'FlashEnergy', 0xA20C : 'SpatialFrequencyResponse', 0xA20D : 'Noise', 0xA20E : 'FocalPlaneXResolution', 0xA20F : 'FocalPlaneYResolution', 0xA210 : 'FocalPlaneResolutionUnit', 0xA211 : 'ImageNumber', 0xA212 : 'SecurityClassification', 0xA213 : 'ImageHistory', 0xA214 : 'SubjectLocation', 0xA215 : 'ExposureIndex', 0xA216 : 'TIFF-EPStandardID', 0xA217 : 'SensingMethod', 0xA300 : 'FileSource', 0xA301 : 'SceneType', 0xA302 : 'CFAPattern', 0xA401 : 'CustomRendered', 0xA402 : 'ExposureMode', 0xA403 : 'WhiteBalance', 0xA404 : 'DigitalZoomRatio', 0xA405 : 'FocalLengthIn35mmFilm', 0xA406 : 'SceneCaptureType', 0xA407 : 'GainControl', 0xA408 : 'Contrast', 0xA409 : 'Saturation', 0xA40A : 'Sharpness', 0xA40B : 'DeviceSettingDescription', 0xA40C : 'SubjectDistanceRange', 0xA420 : 'ImageUniqueID', 0xA430 : 'OwnerName', 0xA431 : 'SerialNumber', 0xA432 : 'LensInfo', 0xA433 : 'LensMake', 0xA434 : 'LensModel', 0xA435 : 'LensSerialNumber', 0xA480 : 'GDALMetadata', 0xA481 : 'GDALNoData', 0xA500 : 'Gamma', 0xAFC0 : 'ExpandSoftware', 0xAFC1 : 'ExpandLens', 0xAFC2 : 'ExpandFilm', 0xAFC3 : 'ExpandFilterLens', 0xAFC4 : 'ExpandScanner', 0xAFC5 : 'ExpandFlashLamp', 0xBC01 : 'PixelFormat', 0xBC02 : 'Transformation', 0xBC03 : 'Uncompressed', 0xBC04 : 'ImageType', 0xBC80 : 'ImageWidth', 0xBC81 : 'ImageHeight', 0xBC82 : 'WidthResolution', 0xBC83 : 'HeightResolution', 0xBCC0 : 'ImageOffset', 0xBCC1 : 'ImageByteCount', 0xBCC2 : 'AlphaOffset', 0xBCC3 : 'AlphaByteCount', 0xBCC4 : 'ImageDataDiscard', 0xBCC5 : 'AlphaDataDiscard', 0xC427 : 'OceScanjobDesc', 0xC428 : 'OceApplicationSelector', 0xC429 : 'OceIDNumber', 0xC42A : 'OceImageLogic', 0xC44F : 'Annotations', 0xC4A5 : 'PrintIM', 0xC580 : 'USPTOOriginalContentType', 0xC612 : 'DNGVersion', 0xC613 : 'DNGBackwardVersion', 0xC614 : 'UniqueCameraModel', 0xC615 : 'LocalizedCameraModel', 0xC616 : 'CFAPlaneColor', 0xC617 : 'CFALayout', 0xC618 : 'LinearizationTable', 0xC619 : 'BlackLevelRepeatDim', 0xC61A : 'BlackLevel', 0xC61B : 'BlackLevelDeltaH', 0xC61C : 'BlackLevelDeltaV', 0xC61D : 'WhiteLevel', 0xC61E : 'DefaultScale', 0xC61F : 'DefaultCropOrigin', 0xC620 : 'DefaultCropSize', 0xC621 : 'ColorMatrix1', 0xC622 : 'ColorMatrix2', 0xC623 : 'CameraCalibration1', 0xC624 : 'CameraCalibration2', 0xC625 : 'ReductionMatrix1', 0xC626 : 'ReductionMatrix2', 0xC627 : 'AnalogBalance', 0xC628 : 'AsShotNeutral', 0xC629 : 'AsShotWhiteXY', 0xC62A : 'BaselineExposure', 0xC62B : 'BaselineNoise', 0xC62C : 'BaselineSharpness', 0xC62D : 'BayerGreenSplit', 0xC62E : 'LinearResponseLimit', 0xC62F : 'CameraSerialNumber', 0xC630 : 'DNGLensInfo', 0xC631 : 'ChromaBlurRadius', 0xC632 : 'AntiAliasStrength', 0xC633 : 'ShadowScale', 0xC634 : 'DNGPrivateData', 0xC635 : 'MakerNoteSafety', 0xC640 : 'RawImageSegmentation', 0xC65A : 'CalibrationIlluminant1', 0xC65B : 'CalibrationIlluminant2', 0xC65C : 'BestQualityScale', 0xC65D : 'RawDataUniqueID', 0xC660 : 'AliasLayerMetadata', 0xC68B : 'OriginalRawFileName', 0xC68C : 'OriginalRawFileData', 0xC68D : 'ActiveArea', 0xC68E : 'MaskedAreas', 0xC68F : 'AsShotICCProfile', 0xC690 : 'AsShotPreProfileMatrix', 0xC691 : 'CurrentICCProfile', 0xC692 : 'CurrentPreProfileMatrix', 0xC6BF : 'ColorimetricReference', 0xC6D2 : 'PanasonicTitle', 0xC6D3 : 'PanasonicTitle2', 0xC6F3 : 'CameraCalibrationSig', 0xC6F4 : 'ProfileCalibrationSig', 0xC6F5 : 'ProfileIFD', 0xC6F6 : 'AsShotProfileName', 0xC6F7 : 'NoiseReductionApplied', 0xC6F8 : 'ProfileName', 0xC6F9 : 'ProfileHueSatMapDims', 0xC6FA : 'ProfileHueSatMapData1', 0xC6FB : 'ProfileHueSatMapData2', 0xC6FC : 'ProfileToneCurve', 0xC6FD : 'ProfileEmbedPolicy', 0xC6FE : 'ProfileCopyright', 0xC714 : 'ForwardMatrix1', 0xC715 : 'ForwardMatrix2', 0xC716 : 'PreviewApplicationName', 0xC717 : 'PreviewApplicationVersion', 0xC718 : 'PreviewSettingsName', 0xC719 : 'PreviewSettingsDigest', 0xC71A : 'PreviewColorSpace', 0xC71B : 'PreviewDateTime', 0xC71C : 'RawImageDigest', 0xC71D : 'OriginalRawFileDigest', 0xC71E : 'SubTileBlockSize', 0xC71F : 'RowInterleaveFactor', 0xC725 : 'ProfileLookTableDims', 0xC726 : 'ProfileLookTableData', 0xC740 : 'OpcodeList1', 0xC741 : 'OpcodeList2', 0xC74E : 'OpcodeList3', 0xC761 : 'NoiseProfile', 0xC763 : 'TimeCodes', 0xC764 : 'FrameRate', 0xC772 : 'TStop', 0xC789 : 'ReelName', 0xC791 : 'OriginalDefaultFinalSize', 0xC792 : 'OriginalBestQualitySize', 0xC793 : 'OriginalDefaultCropSize', 0xC7A1 : 'CameraLabel', 0xC7A3 : 'ProfileHueSatMapEncoding', 0xC7A4 : 'ProfileLookTableEncoding', 0xC7A5 : 'BaselineExposureOffset', 0xC7A6 : 'DefaultBlackRender', 0xC7A7 : 'NewRawImageDigest', 0xC7A8 : 'RawToPreviewGain', 0xC7B5 : 'DefaultUserCrop', 0xEA1C : 'Padding', 0xEA1D : 'OffsetSchema', 0xFDE8 : 'OwnerName', 0xFDE9 : 'SerialNumber', 0xFDEA : 'Lens', 0xFE00 : 'KDC_IFD', 0xFE4C : 'RawFile', 0xFE4D : 'Converter', 0xFE4E : 'WhiteBalance', 0xFE51 : 'Exposure', 0xFE52 : 'Shadows', 0xFE53 : 'Brightness', 0xFE54 : 'Contrast', 0xFE55 : 'Saturation', 0xFE56 : 'Sharpness', 0xFE57 : 'Smoothness', 0xFE58 : 'MoireFilter' }, @@ -915,140 +939,402 @@ ExifImage.TAGS = { } if(typeof deg !== 'number' || typeof m !== 'number' || typeof s !== 'number') return vArr; if (lRef === 'S' || lRef === 'N' || lRef === 'E' || lRef === 'W') { var lInt = (lRef == 'S' || lRef == 'W') ? -1 : 1; var v = (deg+(m/60)+(s/3600)) * lInt; return { description: deg.toString().concat('° ', m, '\' ', s.toFixed(4), '" ', lRef), value: (typeof v === 'number') ? v : [deg, m, s] }; } return [deg, m, s]; }, versions : function(data){ var v = data.toString('utf8').trim().replace(/\0+$/, ''); console.log( v ); data = (Buffer.isBuffer(data)) ? data.toJSON().join('.') : data.join('.'); return data.replace(/\0+$/, ''); }, /* helper end */ ExifVersion : function(data){ return ExifImage.TAGS.ref.versions(data); }, InteropVersion : function(data){ return ExifImage.TAGS.ref.versions(data); }, FlashpixVersion : function(data){ return ExifImage.TAGS.ref.versions(data); }, OldSubfileType : { 1: 'Full-resolution image', 2: 'Reduced-resolution image', 3: 'Single page of multi-page image' }, /* TODO : ExifImage.js has no file type while redaktor.meta.js has 'Compression' sub IdentifyRawFile($$){ my ($et, $comp) = @_; if ($$et{FILE_TYPE} eq 'TIFF' and not $$et{IdentifiedRawFile}) { if ($compression{$comp} and $compression{$comp} =~ /^\w+ ([A-Z]{3}) Compressed$/) { $et->OverrideFileType($$et{TIFF_TYPE} = $1); $$et{IdentifiedRawFile} = 1; } } } */ Thresholding : { 1: 'No dithering or halftoning', 2: 'Ordered dither or halftone', 3: 'Randomized dither' }, FillOrder : { 1: 'Normal', 2: 'Reversed' }, PlanarConfiguration : { 1: 'Chunky', 2: 'Planar' }, GrayResponseUnit : { 1: 0.1, 2: 0.001, 3: 0.0001, 4: 0.00001, 5: 0.000001 }, T4Options : { 0: '2-Dimensional encoding', 1: 'Uncompressed', 2: 'Fill bits added' }, T6Options : { 1: 'Uncompressed' }, ResolutionUnit : { 1: 'unknown', 2: 'inches', 3: 'cm' }, Predictor : { 1: 'None', 2: 'Horizontal differencing' }, /* TODO ? WhitePoint', Groups: { 2: 'Camera' } */ CleanFaxData : { 0: 'Clean', 1: 'Regenerated', 2: 'Unclean' }, ExtraSamples : { 0: 'Unspecified', 1: 'Associated Alpha', 2: 'Unassociated Alpha' }, Indexed : { 0: 'Not indexed', 1: 'Indexed' }, OPIProxy : { 0: 'Higher resolution image does not exist', 1: 'Higher resolution image exists' }, ProfileType: { 0: 'Unspecified', 1: 'Group 3 FAX' }, FaxProfile : { 0: 'Unknown', 1: 'Minimal B&W lossless, S', 2: 'Extended B&W lossless, F', 3: 'Lossless JBIG B&W, J', 4: 'Lossy color and grayscale, C', 5: 'Lossless color and grayscale, L', 6: 'Mixed raster content, M', 7: 'Profile T', 255: 'Multi Profiles' }, CodingMethods : { 0: 'Unspecified compression', 1: 'Modified Huffman', 2: 'Modified Read', 3: 'Modified MR', 4: 'JBIG', 5: 'Baseline JPEG', 6: 'JBIG color' }, JPEGProc : { 1: 'Baseline', 14: 'Lossless' }, Copyright : function(data){ data = data.replace(/ *\0/, String.fromCharCode(10)); data = data.replace(/ *\0[\s\S]*/, ''); return data.replace(/\n$/, ''); }, ModelTiePoint : { 2: 'Location' }, RasterPadding : { 0: 'Byte', 1: 'Word', 2: 'Long Word', 9: 'Sector', 10: 'Long Sector' }, ImageColorIndicator : { 0: 'Unspecified Image Color', 1: 'Specified Image Color' }, BackgroundColorIndicator : { 0: 'Unspecified Background Color', 1: 'Specified Background Color' }, HCUsage : { 0: 'CT', 1: 'Line Art', 2: 'Trap' }, TIFF_FXExtensions : { /* BITMASK */ 0: 'Resolution/Image Width', 1: 'N Layer Profile M', 2: 'Shared Data', 3: 'B&W JBIG2', 4: 'JBIG2 Profile M' }, MultiProfiles : { 0: 'Profile S', 1: 'Profile F', 2: 'Profile J', 3: 'Profile C', 4: 'Profile L', 5: 'Profile M', 6: 'Profile T', 7: 'Resolution/Image Width', 8: 'N Layer Profile M', 9: 'Shared Data', 10: 'JBIG2 Profile M' }, /* TODO 'GeoTiffDirectory', 'GeoTiffDoubleParams', RawConv: '$val . GetByteOrder()', # save byte order */ ExposureProgram : { 0: 'Not Defined', 1: 'Manual', 2: 'Program AE', 3: 'Aperture-priority AE', 4: 'Shutter speed priority AE', 5: 'Creative (Slow speed)', 6: 'Action (High speed)', 7: 'Portrait', 8: 'Landscape', 9: 'Bulb' }, SpectralSensitivity : { 2: 'Camera' }, SensitivityType : { 0: 'Unknown', 1: 'Standard Output Sensitivity', 2: 'Recommended Exposure Index', 3: 'ISO Speed', 4: 'Standard Output Sensitivity and Recommended Exposure Index', 5: 'Standard Output Sensitivity and ISO Speed', 6: 'Recommended Exposure Index and ISO Speed', 7: 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed', }, ComponentsConfiguration : function(data){ if (Buffer.isBuffer(data)) data = data.toJSON(); var c = ['', 'Y', 'Cb', 'Cr', 'R', 'G', 'B']; var cStr = ''; if(data instanceof Array){ if(data.join().trim() == '4,5,6,0') return {description:'RGB uncompressed', value:data}; if(data.join().trim() == '1,2,3,0') return {description:'Y, Cb, Cr', value:data}; data.forEach(function(index){ cStr.concat(c[index]); }); return {description:cStr, value:data}; } return data }, /* TODO 0x9201: { Name: 'ShutterSpeedValue', Notes: 'displayed in seconds, but stored as an APEX value', Format: 'rational64s', # Leica M8 patch (incorrectly written as rational64u) ValueConv: 'abs($val)<100 ? 2**(-$val) : 0', PrintConv: 'Image::ExifTool::Exif::PrintExposureTime($val)', }, 0x9202: { Name: 'ApertureValue', Notes: 'displayed as an F number, but stored as an APEX value', ValueConv: '2 ** ($val / 2)', PrintConv: 'sprintf("%.1f",$val)', }, # Wikipedia: BrightnessValue = Bv = Av + Tv - Sv # ExifTool: LightValue = LV = Av + Tv - Sv + 5 (5 is the Sv for ISO 100 in Exif usage) 0x9203: 'BrightnessValue', 0x9204: { Name: 'ExposureCompensation', Format: 'rational64s', # Leica M8 patch (incorrectly written as rational64u) Notes: 'called ExposureBiasValue by the EXIF spec.', PrintConv: 'Image::ExifTool::Exif::PrintFraction($val)', }, 0x9205: { Name: 'MaxApertureValue', Notes: 'displayed as an F number, but stored as an APEX value', Groups: { 2: 'Camera' }, ValueConv: '2 ** ($val / 2)', PrintConv: 'sprintf("%.1f",$val)', }, 0x9206: { Name: 'SubjectDistance', Groups: { 2: 'Camera' }, PrintConv: '$val =~ /^(inf|undef)$/ ? $val : "${val} m"', }, 0x920a: { Name: 'FocalLength', Groups: { 2: 'Camera' }, PrintConv: 'sprintf("%.1f mm",$val)', }, # handle maker notes as a conditional list 0x927c: \@Image::ExifTool::MakerNotes::Main, 0x9286: { Name: 'UserComment', # I have seen other applications write it incorrectly as 'string' or 'int8u' Format: 'undef', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', }, 0xa432: { #24 Name: 'LensInfo', Notes: q{ 4 rational values giving focal and aperture ranges, called LensSpecification by the EXIF spec. }, # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4" PrintConv: \&Image::ExifTool::Exif::PrintLensInfo, }, */ FocalPlaneResolutionUnit : { 1: 'None', 2: 'inches', 3: 'cm', 4: 'mm', 5: 'um', }, SecurityClassification : { T: 'Top Secret', S: 'Secret', C: 'Confidential', R: 'Restricted', U: 'Unclassified', }, SensingMethod : { 1: 'Monochrome area', 2: 'One-chip color area', 3: 'Two-chip color area', 4: 'Three-chip color area', 5: 'Color sequential area', 6: 'Monochrome linear', 7: 'Trilinear', 8: 'Color sequential linear', }, ColorSpace : { 1 : 'sRGB' }, MeteringMode : { 0 : 'Unknown', 1 : 'Average', 2 : 'CenterWeightedAverage', 3 : 'Spot', 4 : 'MultiSpot', 5 : 'Pattern', 6 : 'Partial', 255 : 'Other' }, LightSource : { 0 : 'Unknown', 1 : 'Daylight', 2 : 'Fluorescent', 3 : 'Tungsten (incandescent light)', 4 : 'Flash', 9 : 'Fine weather', 10 : 'Cloudy weather', 11 : 'Shade', 12 : 'Daylight fluorescent (D 5700 - 7100K)', 13 : 'Day white fluorescent (N 4600 - 5400K)', 14 : 'Cool white fluorescent (W 3900 - 4500K)', 15 : 'White fluorescent (WW 3200 - 3700K)', 17 : 'Standard light A', 18 : 'Standard light B', 19 : 'Standard light C', 20 : 'D55', 21 : 'D65', 22 : 'D75', 23 : 'D50', 24 : 'ISO studio tungsten', 255 : 'Other' }, Flash : { 0x0000 : 'No Flash', 0x0001 : 'Flash fired', 0x0005 : 'Strobe return light not detected', 0x0007 : 'Strobe return light detected', 0x0009 : 'Flash fired, compulsory flash mode', 0x000D : 'Flash fired, compulsory flash mode, return light not detected', 0x000F : 'Flash fired, compulsory flash mode, return light detected', 0x0010 : 'Flash did not fire, compulsory flash mode', 0x0018 : 'Flash did not fire, auto mode', 0x0019 : 'Flash fired, auto mode', 0x001D : 'Flash fired, auto mode, return light not detected', 0x001F : 'Flash fired, auto mode, return light detected', 0x0020 : 'No flash function', 0x0041 : 'Flash fired, red-eye reduction mode', 0x0045 : 'Flash fired, red-eye reduction mode, return light not detected', 0x0047 : 'Flash fired, red-eye reduction mode, return light detected', 0x0049 : 'Flash fired, compulsory flash mode, red-eye reduction mode', 0x004D : 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 0x004F : 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 0x0059 : 'Flash fired, auto mode, red-eye reduction mode', 0x005D : 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 0x005F : 'Flash fired, auto mode, return light detected, red-eye reduction mode' }, SensingMethod : { 1 : 'Not defined', 2 : 'One-chip color area sensor', 3 : 'Two-chip color area sensor', 4 : 'Three-chip color area sensor', 5 : 'Color sequential area sensor', 7 : 'Trilinear sensor', 8 : 'Color sequential linear sensor' }, SceneCaptureType : { 0 : 'Standard', 1 : 'Landscape', 2 : 'Portrait', 3 : 'Night scene' }, WhiteBalance : { 0 : 'Auto', 1 : 'Manual' }, GainControl : { 0 : 'None', 1 : 'Low gain up', 2 : 'High gain up', 3 : 'Low gain down', 4 : 'High gain down' }, Contrast : { 0 : 'Normal', 1 : 'Soft', 2 : 'Hard' }, Saturation : { 0 : 'Normal', 1 : 'Low saturation', 2 : 'High saturation' }, Sharpness : { 0 : 'Normal', 1 : 'Soft', 2 : 'Hard' }, SubjectDistanceRange : { 0 : 'Unknown', 1 : 'Macro', 2 : 'Close view', 3 : 'Distant view' }, ExposureTime : function(_t){ if (typeof _t === 'number' && _t < 0.25001 && _t > 0) { @@ -1057,11 +1343,97 @@ ExifImage.TAGS = { return (typeof _t === 'number') ? _t.toFixed(1).replace(/\.0$/, '') : _t.replace(/\.0$/, ''); }, FileSource : function(data){ return (Buffer.isBuffer(data) && data.toJSON()[0]===3) ? { description:'Digital Still Camera', value:3 } : (parseInt(data)||data); }, SceneType : function(data){ return (Buffer.isBuffer(data) && data.toJSON()[0]===1) ? { description:'Directly photographed', value:1 } : (parseInt(data)||data); }, PixelFormat : { 0x0d: '24-bit RGB', 0x0c: '24-bit BGR', 0x0e: '32-bit BGR', 0x15: '48-bit RGB', 0x12: '48-bit RGB Fixed Point', 0x3b: '48-bit RGB Half', 0x18: '96-bit RGB Fixed Point', 0x1b: '128-bit RGB Float', 0x0f: '32-bit BGRA', 0x16: '64-bit RGBA', 0x1d: '64-bit RGBA Fixed Point', 0x3a: '64-bit RGBA Half', 0x1e: '128-bit RGBA Fixed Point', 0x19: '128-bit RGBA Float', 0x10: '32-bit PBGRA', 0x17: '64-bit PRGBA', 0x1a: '128-bit PRGBA Float', 0x1c: '32-bit CMYK', 0x2c: '40-bit CMYK Alpha', 0x1f: '64-bit CMYK', 0x2d: '80-bit CMYK Alpha', 0x20: '24-bit 3 Channels', 0x21: '32-bit 4 Channels', 0x22: '40-bit 5 Channels', 0x23: '48-bit 6 Channels', 0x24: '56-bit 7 Channels', 0x25: '64-bit 8 Channels', 0x2e: '32-bit 3 Channels Alpha', 0x2f: '40-bit 4 Channels Alpha', 0x30: '48-bit 5 Channels Alpha', 0x31: '56-bit 6 Channels Alpha', 0x32: '64-bit 7 Channels Alpha', 0x33: '72-bit 8 Channels Alpha', 0x26: '48-bit 3 Channels', 0x27: '64-bit 4 Channels', 0x28: '80-bit 5 Channels', 0x29: '96-bit 6 Channels', 0x2a: '112-bit 7 Channels', 0x2b: '128-bit 8 Channels', 0x34: '64-bit 3 Channels Alpha', 0x35: '80-bit 4 Channels Alpha', 0x36: '96-bit 5 Channels Alpha', 0x37: '112-bit 6 Channels Alpha', 0x38: '128-bit 7 Channels Alpha', 0x39: '144-bit 8 Channels Alpha', 0x08: '8-bit Gray', 0x0b: '16-bit Gray', 0x13: '16-bit Gray Fixed Point', 0x3e: '16-bit Gray Half', 0x3f: '32-bit Gray Fixed Point', 0x11: '32-bit Gray Float', 0x05: 'Black & White', 0x09: '16-bit BGR555', 0x0a: '16-bit BGR565', 0x13: '32-bit BGR101010', 0x3d: '32-bit RGBE', }, Transformation : { 0: 'Horizontal (normal)', 1: 'Mirror vertical', 2: 'Mirror horizontal', 3: 'Rotate 180', 4: 'Rotate 90 CW', 5: 'Mirror horizontal and rotate 90 CW', 6: 'Mirror horizontal and rotate 270 CW', 7: 'Rotate 270 CW', }, Uncompressed : { 0: 'No', 1: 'Yes' }, ImageDataDiscard : { 0: 'Full Resolution', 1: 'Flexbits Discarded', 2: 'HighPass Frequency Data Discarded', 3: 'Highpass and LowPass Frequency Data Discarded', }, AlphaDataDiscard : { 0: 'Full Resolution', 1: 'Flexbits Discarded', 2: 'HighPass Frequency Data Discarded', 3: 'Highpass and LowPass Frequency Data Discarded', }, USPTOOriginalContentType : { 0: 'Text or Drawing', 1: 'Grayscale', 2: 'Color', }, CFAPattern : function(data){ /* The value consists of: - Two short, being the grid width and height of the repeated pattern. @@ -1088,11 +1460,408 @@ ExifImage.TAGS = { console.log( rtn ); return (rtn.indexOf('0')==-1) ? rtn.slice(0,-1) : arr; }, CustomRendered : { 0 : 'Normal', 1 : 'Custom', 4 : 'Apple iPhone5c horizontal orientation', 6 : 'Apple iPhone5c panorama' }, ExposureMode : { 0: 'Auto', 1: 'Manual', 2: 'Auto bracket', 3: 'Samsung EX/NX specific' }, /* TODO DNG 0xc612: { Name: 'DNGVersion', Notes: 'tags 0xc612-0xc7b5 are used in DNG images unless otherwise noted', DataMember: 'DNGVersion', RawConv: '$$self{DNGVersion} = $val', PrintConv: '$val =~ tr/ /./; $val', }, 0xc613: { Name: 'DNGBackwardVersion', PrintConv: '$val =~ tr/ /./; $val', }, 0xc614: 'UniqueCameraModel', 0xc615: { Name: 'LocalizedCameraModel', Format: 'string', PrintConv: '$self->Printable($val, 0)', }, 0xc616: { Name: 'CFAPlaneColor', PrintConv: q{ my @cols = qw(Red Green Blue Cyan Magenta Yellow White); my @vals = map { $cols[$_] || "Unknown($_)" } split(' ', $val); return join(',', @vals); }, }, 0xc617: { Name: 'CFALayout : {1: 'Rectangular', 2: 'Even columns offset down 1/2 row', 3: 'Even columns offset up 1/2 row', 4: 'Even rows offset right 1/2 column', 5: 'Even rows offset left 1/2 column', # the following are new for DNG 1.3: 6: 'Even rows offset up by 1/2 row, even columns offset left by 1/2 column', 7: 'Even rows offset up by 1/2 row, even columns offset right by 1/2 column', 8: 'Even rows offset down by 1/2 row, even columns offset left by 1/2 column', 9: 'Even rows offset down by 1/2 row, even columns offset right by 1/2 column', }, }, 0xc634: [ { Condition: '$$self{TIFF_TYPE} =~ /^(ARW|SR2)$/', Name: 'SR2Private', Groups: { 1: 'SR2' }, Flags: 'SubIFD', Format: 'int32u', # some utilites have problems unless this is int8u format: # - Adobe Camera Raw 5.3 gives an error # - Apple Preview 10.5.8 gets the wrong white balance FixFormat: 'int8u', # (stupid Sony) SubDirectory: { DirName: 'SR2Private', TagTable: 'Image::ExifTool::Sony::SR2Private', Start: '$val', }, }, { Condition: '$$valPt =~ /^Adobe\0/', Name: 'DNGAdobeData', Flags: [ 'Binary', 'Protected' ], Writable: 'undef', # (writable directory!) (to make it possible to delete this mess) WriteGroup: 'IFD0', NestedHtmlDump: 1, SubDirectory: { TagTable: 'Image::ExifTool::DNG::AdobeData' }, Format: 'undef', # written incorrectly as int8u (change to undef for speed) }, { # Pentax/Samsung models that write AOC maker notes in JPG images: # K-5,K-7,K-m,K-x,K-r,K10D,K20D,K100D,K110D,K200D,K2000,GX10,GX20 # (Note: the following expression also appears in WriteExif.pl) Condition: q{ $$valPt =~ /^(PENTAX |SAMSUNG)\0/ and $$self{Model} =~ /\b(K(-[57mrx]|(10|20|100|110|200)D|2000)|GX(10|20))\b/ }, Name: 'MakerNotePentax', MakerNotes: 1, # (causes "MakerNotes header" to be identified in HtmlDump output) Binary: 1, # Note: Don't make this block-writable for a few reasons: # 1) It would be dangerous (possibly confusing Pentax software) # 2) It is a different format from the JPEG version of MakerNotePentax # 3) It is converted to JPEG format by RebuildMakerNotes() when copying SubDirectory: { TagTable: 'Image::ExifTool::Pentax::Main', Start: '$valuePtr + 10', Base: '$start - 10', ByteOrder: 'Unknown', # easier to do this than read byteorder word }, Format: 'undef', # written incorrectly as int8u (change to undef for speed) }, { # must duplicate the above tag with a different name for more recent # Pentax models which use the "PENTAX" instead of the "AOC" maker notes # in JPG images (needed when copying maker notes from DNG to JPG) Condition: '$$valPt =~ /^(PENTAX |SAMSUNG)\0/', Name: 'MakerNotePentax5', MakerNotes: 1, Binary: 1, SubDirectory: { TagTable: 'Image::ExifTool::Pentax::Main', Start: '$valuePtr + 10', Base: '$start - 10', ByteOrder: 'Unknown', }, Format: 'undef', }, { Name: 'DNGPrivateData', Flags: [ 'Binary', 'Protected' ], Format: 'undef', Writable: 'undef', WriteGroup: 'IFD0', }, ], 0xc635: { Name: 'MakerNoteSafety : {0: 'Unsafe', 1: 'Safe', }, }, 0xc640: { #15 Name: 'RawImageSegmentation', # (int16u[3], not writable) Notes: q{ used in segmented Canon CR2 images. 3 numbers: 1. Number of segments minus one; 2. Pixel width of segments except last; 3. Pixel width of last segment }, }, 0xc65a: { Name: 'CalibrationIlluminant1', SeparateTable: 'LightSource', PrintConv: \%lightSource, }, 0xc65b: { Name: 'CalibrationIlluminant2', SeparateTable: 'LightSource', PrintConv: \%lightSource, }, 0xc65c: 'BestQualityScale', 0xc65d: { Name: 'RawDataUniqueID', Format: 'undef', ValueConv: 'uc(unpack("H*",$val))', }, 0xc660: { #3 Name: 'AliasLayerMetadata', Notes: 'used by Alias Sketchbook Pro', }, 0xc68b: { Name: 'OriginalRawFileName', Format: 'string', # sometimes written as int8u }, 0xc68c: { Name: 'OriginalRawFileData', # (writable directory!) Writable: 'undef', # must be defined here so tag will be extracted if specified WriteGroup: 'IFD0', Flags: [ 'Binary', 'Protected' ], SubDirectory: { TagTable: 'Image::ExifTool::DNG::OriginalRaw', }, }, 0xc68d: 'ActiveArea', 0xc68e: 'MaskedAreas', 0xc68f: { Name: 'AsShotICCProfile', Binary: 1, Writable: 'undef', # must be defined here so tag will be extracted if specified SubDirectory: { DirName: 'AsShotICCProfile', TagTable: 'Image::ExifTool::ICC_Profile::Main', }, }, 0xc690: 'AsShotPreProfileMatrix', 0xc691: { Name: 'CurrentICCProfile', Binary: 1, Writable: 'undef', # must be defined here so tag will be extracted if specified SubDirectory: { DirName: 'CurrentICCProfile', TagTable: 'Image::ExifTool::ICC_Profile::Main', }, }, 0xc692: 'CurrentPreProfileMatrix', 0xc6bf: 'ColorimetricReference', 0xc6d2: { #JD (Panasonic DMC-TZ5) # this text is UTF-8 encoded (hooray!) - PH (TZ5) Name: 'PanasonicTitle', Format: 'string', # written incorrectly as 'undef' Notes: 'proprietary Panasonic tag used for baby/pet name, etc', # panasonic always records this tag (64 zero bytes), # so ignore it unless it contains valid information RawConv: 'length($val) ? $val : undef', ValueConv: '$self->Decode($val, "UTF8")', }, 0xc6d3: { #PH (Panasonic DMC-FS7) Name: 'PanasonicTitle2', Format: 'string', # written incorrectly as 'undef' Notes: 'proprietary Panasonic tag used for baby/pet name with age', # panasonic always records this tag (128 zero bytes), # so ignore it unless it contains valid information RawConv: 'length($val) ? $val : undef', ValueConv: '$self->Decode($val, "UTF8")', }, 0xc6f3: 'CameraCalibrationSig', 0xc6f4: 'ProfileCalibrationSig', 0xc6f5: { Name: 'ProfileIFD', # (ExtraCameraProfiles) Groups: { 1: 'ProfileIFD' }, Flags: 'SubIFD', SubDirectory: { ProcessProc: \&ProcessTiffIFD, WriteProc: \&ProcessTiffIFD, DirName: 'ProfileIFD', Start: '$val', Base: '$start', # offsets relative to start of TIFF-like header MaxSubdirs: 10, Magic: 0x4352, # magic number for TIFF-like header }, }, 0xc6f6: 'AsShotProfileName', 0xc6f7: 'NoiseReductionApplied', 0xc6f8: 'ProfileName', 0xc6f9: 'ProfileHueSatMapDims', 0xc6fa: { Name: 'ProfileHueSatMapData1', %longBin }, 0xc6fb: { Name: 'ProfileHueSatMapData2', %longBin }, 0xc6fc: { Name: 'ProfileToneCurve', Binary: 1, }, 0xc6fd: { Name: 'ProfileEmbedPolicy : {0: 'Allow Copying', 1: 'Embed if Used', 2: 'Never Embed', 3: 'No Restrictions', }, }, 0xc6fe: 'ProfileCopyright', 0xc714: 'ForwardMatrix1', 0xc715: 'ForwardMatrix2', 0xc716: 'PreviewApplicationName', 0xc717: 'PreviewApplicationVersion', 0xc718: 'PreviewSettingsName', 0xc719: { Name: 'PreviewSettingsDigest', Format: 'undef', ValueConv: 'unpack("H*", $val)', }, 0xc71a: 'PreviewColorSpace', 0xc71b: { Name: 'PreviewDateTime', Groups: { 2: 'Time' }, ValueConv: q{ require Image::ExifTool::XMP; return Image::ExifTool::XMP::ConvertXMPDate($val); }, }, 0xc71c: { Name: 'RawImageDigest', Format: 'undef', ValueConv: 'unpack("H*", $val)', }, 0xc71d: { Name: 'OriginalRawFileDigest', Format: 'undef', ValueConv: 'unpack("H*", $val)', }, 0xc71e: 'SubTileBlockSize', 0xc71f: 'RowInterleaveFactor', 0xc725: 'ProfileLookTableDims', 0xc726: { Name: 'ProfileLookTableData', Binary: 1, }, 0xc740: { # DNG 1.3 Name: 'OpcodeList1', Binary: 1, # opcodes: # 1: 'WarpRectilinear', # 2: 'WarpFisheye', # 3: 'FixVignetteRadial', # 4: 'FixBadPixelsConstant', # 5: 'FixBadPixelsList', # 6: 'TrimBounds', # 7: 'MapTable', # 8: 'MapPolynomial', # 9: 'GainMap', # 10: 'DeltaPerRow', # 11: 'DeltaPerColumn', # 12: 'ScalePerRow', # 13: 'ScalePerColumn', }, 0xc741: { # DNG 1.3 Name: 'OpcodeList2', Binary: 1, }, 0xc74e: { # DNG 1.3 Name: 'OpcodeList3', Binary: 1, }, 0xc761: 'NoiseProfile', # DNG 1.3 0xc763: { #28 Name: 'TimeCodes', ValueConv: q{ my @a = split ' ', $val; my @v; push @v, join('.', map { sprintf('%.2x',$_) } splice(@a,0,8)) while @a >= 8; join ' ', @v; }, # Note: Currently ignore the flags: # byte 0 0x80 - color frame # byte 0 0x40 - drop frame # byte 1 0x80 - field phase PrintConv: q{ my @a = map hex, split /[. ]+/, $val; my @v; while (@a >= 8) { my $str = sprintf("%.2x:%.2x:%.2x.%.2x", $a[3]&0x3f, $a[2]&0x7f, $a[1]&0x7f, $a[0]&0x3f); if ($a[3] & 0x80) { # date+timezone exist if BGF2 is set my $tz = $a[7] & 0x3f; my $bz = sprintf('%.2x', $tz); $bz = 100 if $bz =~ /[a-f]/i; # not BCD if ($bz < 26) { $tz = ($bz < 13 ? 0 : 26) - $bz; } elsif ($bz == 32) { $tz = 12.75; } elsif ($bz >= 28 and $bz <= 31) { $tz = 0; # UTC } elsif ($bz < 100) { undef $tz; # undefined or user-defined } elsif ($tz < 0x20) { $tz = (($tz < 0x10 ? 10 : 20) - $tz) - 0.5; } else { $tz = (($tz < 0x30 ? 53 : 63) - $tz) + 0.5; } if ($a[7] & 0x80) { # MJD format (/w UTC time) my ($h,$m,$s,$f) = split /[:.]/, $str; my $jday = sprintf('%x%.2x%.2x', reverse @a[4..6]); $str = ConvertUnixTime(($jday - 40587) * 24 * 3600 + ((($h+$tz) * 60) + $m) * 60 + $s) . ".$f"; $str =~ s/^(\d+):(\d+):(\d+) /$1-$2-${3}T/; } else { # YYMMDD (Note: CinemaDNG 1.1 example seems wrong) my $yr = sprintf('%.2x',$a[6]) + 1900; $yr += 100 if $yr < 1970; $str = sprintf('%d-%.2x-%.2xT%s',$yr,$a[5],$a[4],$str); } $str .= TimeZoneString($tz*60) if defined $tz; } push @v, $str; splice @a, 0, 8; } join ' ', @v; }, }, 0xc764: { #28 Name: 'FrameRate', PrintConv: 'int($val * 1000 + 0.5) / 1000', }, 0xc772: { #28 Name: 'TStop', PrintConv: 'join("-", map { sprintf("%.2f",$_) } split " ", $val)', }, 0xc789: 'ReelName', #28 0xc791: 'OriginalDefaultFinalSize', # DNG 1.4 0xc792: { # DNG 1.4 Name: 'OriginalBestQualitySize', Notes: 'called OriginalBestQualityFinalSize by the DNG spec', }, 0xc793: 'OriginalDefaultCropSize', # DNG 1.4 0xc7a1: 'CameraLabel', #28 0xc7a3: { # DNG 1.4 Name: 'ProfileHueSatMapEncoding : {0: 'Linear', 1: 'sRGB', }, }, 0xc7a4: { # DNG 1.4 Name: 'ProfileLookTableEncoding : {0: 'Linear', 1: 'sRGB', }, }, 0xc7a5: 'BaselineExposureOffset', # DNG 1.4 0xc7a6: { # DNG 1.4 Name: 'DefaultBlackRender : {0: 'Auto', 1: 'None', }, }, 0xc7a7: { # DNG 1.4 Name: 'NewRawImageDigest', Format: 'undef', ValueConv: 'unpack("H*", $val)', }, */ GPSVersionID: function(data){ @@ -1144,23 +1913,34 @@ ExifImage.TAGS = { GPSDestDistanceRef: { K: 'Kilometers', M: 'Miles', N: 'Nautical Miles' }, GPSDifferential: { 0: 'No Correction', 1: 'Differential Corrected' } /* TODO - following might be important for WRITING... sub ConvertExifText($$;$) 0x2bc: { Name: 'ApplicationNotes', # (writable directory!) Writable: 'int8u', Format: 'undef', Flags: [ 'Binary', 'Protected' ], # this could be an XMP block SubDirectory: { DirName: 'XMP', TagTable: 'Image::ExifTool::XMP::Main', }, }, 0x001b: { Name: 'GPSProcessingMethod', Writable: 'undef', Notes: 'values of 'GPS', 'CELLID', 'WLAN' or 'MANUAL' by the EXIF spec.', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', RawConvInv: 'Image::ExifTool::Exif::EncodeExifText($self,$val)', }, 0x001c: { Name: 'GPSAreaInformation', Writable: 'undef', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', RawConvInv: 'Image::ExifTool::Exif::EncodeExifText($self,$val)', }, GPSTimeStamp: { Notes => q{ when writing, date is stripped off if present, and time is adjusted to UTC @@ -1188,25 +1968,142 @@ ExifImage.TAGS = { while ($a[-2] < 0) { $a[-2] += 60; --$a[-3] } $a[-3] = ($a[-3] + 24) % 24; } return '$a[-3]:$a[-2]:$a[-1]'; } }, 0x001d: { Name: 'GPSDateStamp', Groups: { 2: 'Time' }, Writable: 'string', Format: 'undef', # (Casio EX-H20G uses '\0' instead of ':' as a separator) Count: 11, Shift: 'Time', Notes: q{ when writing, time is stripped off if present, after adjusting date/time to UTC if time includes a timezone. Format is YYYY:mm:dd }, ValueConv: 'Image::ExifTool::Exif::ExifDate($val)', ValueConvInv: '$val', # pull date out of any format date/time string # (and adjust to UTC if this is a full date/time/timezone value) PrintConvInv: q{ my $secs; if ($val =~ /[-+]/ and ($secs = Image::ExifTool::GetUnixTime($val, 1))) { $val = Image::ExifTool::ConvertUnixTime($secs); } return $val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/ ? '$1:$2:$3' : undef; }, } 0x111: [ { Condition: q[ $$self{TIFF_TYPE} eq 'MRW' and $$self{DIR_NAME} eq 'IFD0' and $$self{Model} =~ /^DiMAGE A200/ ], Name: 'StripOffsets', IsOffset: 1, OffsetPair: 0x117, # point to associated byte counts # A200 stores this information in the wrong byte order!! ValueConv: '$val=join(' ',unpack('N*',pack('V*',split(' ',$val))));\$val', ByteOrder: 'LittleEndian', }, { Condition: q[ ($$self{TIFF_TYPE} ne 'CR2' or $$self{DIR_NAME} ne 'IFD0') and ($$self{TIFF_TYPE} ne 'DNG' or $$self{DIR_NAME} !~ /^SubIFD[12]$/) ], Name: 'StripOffsets', IsOffset: 1, OffsetPair: 0x117, # point to associated byte counts ValueConv: 'length($val) > 32 ? \$val : $val', }, { Condition: '$$self{DIR_NAME} eq 'IFD0'', Name: 'PreviewImageStart', IsOffset: 1, OffsetPair: 0x117, Notes: q{ PreviewImageStart in IFD0 of CR2 images and SubIFD1 of DNG images, and JpgFromRawStart in SubIFD2 of DNG images }, DataTag: 'PreviewImage', Writable: 'int32u', WriteGroup: 'IFD0', WriteCondition: '$$self{TIFF_TYPE} eq 'CR2'', Protected: 2, }, { Condition: '$$self{DIR_NAME} eq 'SubIFD1'', Name: 'PreviewImageStart', IsOffset: 1, OffsetPair: 0x117, DataTag: 'PreviewImage', Writable: 'int32u', WriteGroup: 'SubIFD1', WriteCondition: '$$self{TIFF_TYPE} eq 'DNG'', Protected: 2, }, { Name: 'JpgFromRawStart', IsOffset: 1, OffsetPair: 0x117, DataTag: 'JpgFromRaw', Writable: 'int32u', WriteGroup: 'SubIFD2', WriteCondition: '$$self{TIFF_TYPE} eq 'DNG'', Protected: 2, }, ], 0x117: [ { Condition: q[ $$self{TIFF_TYPE} eq 'MRW' and $$self{DIR_NAME} eq 'IFD0' and $$self{Model} =~ /^DiMAGE A200/ ], Name: 'StripByteCounts', OffsetPair: 0x111, # point to associated offset # A200 stores this information in the wrong byte order!! ValueConv: '$val=join(' ',unpack('N*',pack('V*',split(' ',$val))));\$val', ByteOrder: 'LittleEndian', }, { Condition: q[ ($$self{TIFF_TYPE} ne 'CR2' or $$self{DIR_NAME} ne 'IFD0') and ($$self{TIFF_TYPE} ne 'DNG' or $$self{DIR_NAME} !~ /^SubIFD[12]$/) ], Name: 'StripByteCounts', OffsetPair: 0x111, # point to associated offset ValueConv: 'length($val) > 32 ? \$val : $val', }, { Condition: '$$self{DIR_NAME} eq 'IFD0'', Name: 'PreviewImageLength', OffsetPair: 0x111, Notes: q{ PreviewImageLength in IFD0 of CR2 images and SubIFD1 of DNG images, and JpgFromRawLength in SubIFD2 of DNG images }, DataTag: 'PreviewImage', Writable: 'int32u', WriteGroup: 'IFD0', WriteCondition: '$$self{TIFF_TYPE} eq 'CR2'', Protected: 2, }, { Condition: '$$self{DIR_NAME} eq 'SubIFD1'', Name: 'PreviewImageLength', OffsetPair: 0x111, DataTag: 'PreviewImage', Writable: 'int32u', WriteGroup: 'SubIFD1', WriteCondition: '$$self{TIFF_TYPE} eq 'DNG'', Protected: 2, }, { Name: 'JpgFromRawLength', OffsetPair: 0x111, DataTag: 'JpgFromRaw', Writable: 'int32u', WriteGroup: 'SubIFD2', WriteCondition: '$$self{TIFF_TYPE} eq 'DNG'', Protected: 2, }, ], */ } -
redaktor renamed this gist
May 6, 2014 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
redaktor created this gist
May 6, 2014 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,1214 @@ var fs = require('fs'), util = require('util'), BufferExtender = require('./Buffer'); /** * Represents an image with Exif information. When instantiating it you have to * provide an image and a callback function which is called once all metadata * is extracted from the image. * * Available options are: * - image The image to get Exif data from can be either a filesystem path or * a Buffer. * - exif_buffer An exif_buffer to directly parse. * * @param options Configuration options as described above * @param callback Function to call when data is extracted or an error occured * @return Nothing of importance, calls the specified callback function instead */ function ExifImage (options, callback) { var self = this; if (!options) var options = {}; this.image; this.imageType; this.isBigEndian; this.makernoteOffset; this.exifData = { image : {}, // Information about the main image thumbnail : {}, // Information about the thumbnail exif : {}, // Exif information gps : {}, // GPS information interoperability: {}, // Exif Interoperability information makernote : {} // Makernote information }; if (!options.image && !options.exif_buffer) { throw new Error('You have to provide an image or exif_buffer, it is pretty hard to extract Exif data from nothing...'); } else if (typeof callback !== 'function') { throw new Error('You have to provide a callback function.'); } else { if (options.image) { this.loadImage(options.image, function (error, image) { if (error) callback(error); else callback(false, image); }); } else { process.nextTick(function(){ self.extractExifData(options.exif_buffer, 0, options.exif_buffer.length); callback(null, self.exifData); }); } } } module.exports = ExifImage; ExifImage.prototype.loadImage = function (image, callback) { var self = this; if (image.constructor.name === 'Buffer') { this.processImage(image, callback); } else if (image.constructor.name === 'String') { fs.readFile(image, function (error, data) { if (error) callback(new Error('Encountered the following error while trying to read given image: '+error)); else self.processImage(data, callback); }); } else { callback(new Error('Given image is neither a buffer nor a file, please provide one of these.')); } }; ExifImage.prototype.processImage = function (data, callback) { var self = this; var offset = 0; if (data[offset++] == 0xFF && data[offset++] == 0xD8) { self.imageType = 'JPEG'; } else { callback(new Error('The given image is not a JPEG and thus unsupported right now.')); return; } try { while (offset < data.length) { if (data[offset++] != 0xFF) { callback(new Error('Invalid marker found at offset '+(--offset)+'. Expected 0xFF but found 0x'+data[offset].toString(16).toUpperCase()+".")); return; } if (data[offset++] == 0xE1) { var exifData = self.extractExifData(data, offset + 2, data.getShort(offset, true) - 2); callback(false, exifData); return; } else { offset += data.getShort(offset, true); } } } catch (error) { callback(error); } callback(new Error('No Exif segment found in the given image.')); }; ExifImage.prototype.extractExifData = function (data, start, length) { var self = this; var tiffOffset = start + 6; var ifdOffset, numberOfEntries; // Exif data always starts with Exif\0\0 if (data.toString('utf8', start, tiffOffset) != 'Exif\0\0') { throw new Error('The Exif data ist not valid.'); } // After the Exif start we either have 0x4949 if the following data is // stored in big endian or 0x4D4D if it is stored in little endian if (data.getShort(tiffOffset) == 0x4949) { this.isBigEndian = false; } else if (data.getShort(tiffOffset) == 0x4D4D) { this.isBigEndian = true; } else { throw new Error('Invalid TIFF data! Expected 0x4949 or 0x4D4D at offset '+(tiffOffset)+' but found 0x'+data[tiffOffset].toString(16).toUpperCase()+data[tiffOffset + 1].toString(16).toUpperCase()+"."); } // Valid TIFF headers always have 0x002A here if (data.getShort(tiffOffset + 2, this.isBigEndian) != 0x002A) { var expected = (this.isBigEndian) ? '0x002A' : '0x2A00'; throw new Error('Invalid TIFF data! Expected '+expected+' at offset '+(tiffOffset + 2)+' but found 0x'+data[tiffOffset + 2].toString(16).toUpperCase()+data[tiffOffset + 3].toString(16).toUpperCase()+"."); } /********************************* IFD0 **********************************/ // Offset to IFD0 which is always followed by two bytes with the amount of // entries in this IFD ifdOffset = tiffOffset + data.getLong(tiffOffset + 4, this.isBigEndian); numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); // Each IFD entry consists of 12 bytes which we loop through and extract // the data from for (var i = 0; i < numberOfEntries; i++) { //console.log( data ); var exifEntry = self.extractExifEntry(data, (ifdOffset + 2 + (i * 12)), tiffOffset, this.isBigEndian, ExifImage.TAGS.exif); if (exifEntry && exifEntry.tagName !== null) this.exifData.image[exifEntry.tagName] = exifEntry.value; } /********************************* IFD1 **********************************/ // Check if there is an offset for IFD1. If so it is always followed by two // bytes with the amount of entries in this IFD, if not there is no IFD1 var nextIfdOffset = data.getLong(ifdOffset + 2 + (numberOfEntries * 12), this.isBigEndian) if (nextIfdOffset != 0x00000000) { ifdOffset = tiffOffset + nextIfdOffset; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); // Each IFD entry consists of 12 bytes which we loop through and extract // the data from for (var i = 0; i < numberOfEntries; i++) { var exifEntry = self.extractExifEntry(data, (ifdOffset + 2 + (i * 12)), tiffOffset, this.isBigEndian, ExifImage.TAGS.exif); if (exifEntry && exifEntry.tagName !== null) this.exifData.thumbnail[exifEntry.tagName] = exifEntry.value; } } /******************************* EXIF IFD ********************************/ // Look for a pointer to the Exif IFD in IFD0 and extract information from // it if available if (typeof this.exifData.image[ExifImage.TAGS.exif[0x8769]] != "undefined") { ifdOffset = tiffOffset + this.exifData.image[ExifImage.TAGS.exif[0x8769]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); // Each IFD entry consists of 12 bytes which we loop through and extract // the data from for (var i = 0; i < numberOfEntries; i++) { var exifEntry = self.extractExifEntry(data, (ifdOffset + 2 + (i * 12)), tiffOffset, this.isBigEndian, ExifImage.TAGS.exif); if (exifEntry && exifEntry.tagName !== null) this.exifData.exif[exifEntry.tagName] = exifEntry.value; } } /******************************** GPS IFD ********************************/ // Look for a pointer to the GPS IFD in IFD0 and extract information from // it if available var gpsifdOffset = this.exifData.image[ExifImage.TAGS.exif[0x8825]]; if (typeof gpsifdOffset != "undefined" && gpsifdOffset > 0) { ifdOffset = tiffOffset + this.exifData.image[ExifImage.TAGS.exif[0x8825]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); // Each IFD entry consists of 12 bytes which we loop through and extract // the data from for (var i = 0; i < numberOfEntries; i++) { var exifEntry = self.extractExifEntry(data, (ifdOffset + 2 + (i * 12)), tiffOffset, this.isBigEndian, ExifImage.TAGS.gps); if (exifEntry && exifEntry.tagName !== null) this.exifData.gps[exifEntry.tagName] = exifEntry.value; } } /************************* Interoperability IFD **************************/ // Look for a pointer to the interoperatbility IFD in the Exif IFD and // extract information from it if available if (typeof this.exifData.exif[ExifImage.TAGS.exif[0xA005]] != "undefined") { ifdOffset = tiffOffset + this.exifData.exif[ExifImage.TAGS.exif[0xA005]]; numberOfEntries = data.getShort(ifdOffset, this.isBigEndian); // Each IFD entry consists of 12 bytes which we loop through and extract // the data from for (var i = 0; i < numberOfEntries; i++) { var exifEntry = self.extractExifEntry(data, (ifdOffset + 2 + (i * 12)), tiffOffset, this.isBigEndian, ExifImage.TAGS.exif); if (exifEntry && exifEntry.tagName !== null) this.exifData.interoperability[exifEntry.tagName] = exifEntry.value; } } /***************************** Makernote IFD *****************************/ // Look for Makernote data in the Exif IFD, check which type of proprietary // Makernotes the image contains, load the respective functionality and // start the extraction // check explicitly for the getString method in case somehow this isn't // a buffer. Found this in an image in the wild var makerNoteValue = this.exifData.exif[ExifImage.TAGS.exif[0x927C]]; if (typeof makerNoteValue != "undefined") { if (typeof makerNoteValue.getString == "undefined" && typeof makerNoteValue.length != "undefined") { // assume we can convert to buffer (we can do arrays and strings) makerNoteValue = new Buffer(makerNoteValue); } if (typeof makerNoteValue.getString != "undefined") { // Check the header to see what kind of Makernote we are dealing with if (makerNoteValue.getString(0, 7) === "OLYMP\x00\x01" || makerNoteValue.getString(0, 7) === "OLYMP\x00\x02") { this.extractMakernotes = require('./makernotes/olympus').extractMakernotes; } else if (makerNoteValue.getString(0, 7) === "AGFA \x00\x01") { this.extractMakernotes = require('./makernotes/agfa').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === "EPSON\x00\x01\x00") { this.extractMakernotes = require('./makernotes/epson').extractMakernotes; } else if (makerNoteValue.getString(0, 8) === "FUJIFILM") { this.extractMakernotes = require('./makernotes/fujifilm').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === "SANYO") { this.extractMakernotes = require('./makernotes/sanyo').extractMakernotes; } else if (makerNoteValue.getString(0, 5) === "Nikon") { this.extractMakernotes = require('./makernotes/nikon').extractMakernotes; } else if (makerNoteValue.getString(0, 4) === "%\u0000\u0001\u0000") { this.extractMakernotes = require('./makernotes/canon').extractMakernotes; } else { // Makernotes are available but the format is not recognized so // an error message is pushed instead, this ain't the best // solution but should do for now this.exifData.makernote['error'] = makerNoteValue.getString(0, 5).concat('...: Unable to extract Makernote information as it is in an unsupported or unrecognized format.'); } if (typeof this.exifData.makernote['error'] == "undefined") { this.exifData.makernote = this.extractMakernotes(data, self.makernoteOffset, tiffOffset); } } } return this.exifData; }; ExifImage.prototype.extractExifEntry = function (data, entryOffset, tiffOffset, isBigEndian, tags) { var self = this; var tagName; var entry = { tag : data.slice(entryOffset, entryOffset + 2), tagId : null, tagName : null, format : data.getShort(entryOffset + 2, isBigEndian), components : data.getLong(entryOffset + 4, isBigEndian), valueOffset: null, value : [] } var tidyString = function(str) { if (typeof str === "undefined") str = ""; str = str + ""; str = str.replace(/[^a-z0-9 \-\/\.\(\)\:\;\,\©\@\\]/gi, ''); str = str.replace(/^\s+|\s+$/g, ''); // trim if (str.toLowerCase() == "undefined" || str.toLowerCase() == "unknown") str = ""; return str.trim(); } entry.tagId = entry.tag.getShort(0, isBigEndian); // The tagId may correspond to more then one tagName so check which if (tags && tags[entry.tagId] && typeof tags[entry.tagId] == "function") { if (!(entry.tagName = tags[entry.tagId](entry))) { return false; } // The tagId corresponds to exactly one tagName } else if (tags && tags[entry.tagId]) { entry.tagName = tags[entry.tagId]; // The tagId is not recognized } else { return false; } if (entry.components > data.length) { entry.components = 0; return entry; } switch (entry.format) { case 0x0001: // unsigned byte, 1 byte per component entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getByte(entry.valueOffset + i)); break; case 0x0002: // ascii strings, 1 byte per component entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; entry.value = data.getString(entry.valueOffset, entry.components); if (entry.value[entry.value.length - 1] === "\u0000") // Trim null terminated strings entry.value = tidyString(entry.value.substring(0, entry.value.length - 1)); break; case 0x0003: // unsigned short, 2 byte per component entry.valueOffset = (entry.components <= 2) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getShort(entry.valueOffset + i * 2, isBigEndian)); break; case 0x0004: // unsigned long, 4 byte per component entry.valueOffset = (entry.components == 1) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getLong(entry.valueOffset + i * 4, isBigEndian)); break; case 0x0005: // unsigned rational, 8 byte per component (4 byte numerator and 4 byte denominator) entry.valueOffset = data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getLong(entry.valueOffset + i * 8, isBigEndian) / data.getLong(entry.valueOffset + i * 8 + 4, isBigEndian)); break; case 0x0006: // signed byte, 1 byte per component entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getSignedByte(entry.valueOffset + i)); break; case 0x0007: // undefined, 1 byte per component entry.valueOffset = (entry.components <= 4) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; entry.value.push(data.slice(entry.valueOffset, entry.valueOffset + entry.components)); break; case 0x0008: // signed short, 2 byte per component entry.valueOffset = (entry.components <= 2) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getSignedShort(entry.valueOffset + i * 2, isBigEndian)); break; case 0x0009: // signed long, 4 byte per component entry.valueOffset = (entry.components == 1) ? entryOffset + 8 : data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getSignedLong(entry.valueOffset + i * 4, isBigEndian)); break; case 0x000A: // signed rational, 8 byte per component (4 byte numerator and 4 byte denominator) entry.valueOffset = data.getLong(entryOffset + 8, isBigEndian) + tiffOffset; for (var i = 0; i < entry.components; i++) entry.value.push(data.getSignedLong(entry.valueOffset + i * 8, isBigEndian) / data.getSignedLong(entry.valueOffset + i * 8 + 4, isBigEndian)); break; default: return false; } // If this is the Makernote tag save its offset for later use if (entry.tagName === "MakerNote") self.makernoteOffset = entry.valueOffset; // If the value array has only one element we don't need an array if (entry.value.length == 1) entry.value = entry.value[0]; // Is there a string match - todo lang.exists if (entry.tagName in ExifImage.TAGS.ref){ var ref = ExifImage.TAGS.ref[entry.tagName]; if (typeof ref === 'function'){ /* composite values */ var pair = null; switch(entry.tagName){ case 'GPSLatitude': pair = self.exifData.gps.GPSLatitudeRef.value; break; case 'GPSLongitude': pair = self.exifData.gps.GPSLongitudeRef.value; break; } entry.value = (pair) ? ref(entry.value, pair) : ref(entry.value); } else if (entry.value in ref){ entry.value = { description: ref[entry.value], value:entry.value }; } ref = null; } return entry; }; /** * Comprehensive list of TIFF and Exif tags found on * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html */ ExifImage.TAGS = { // Exif tags exif : { 0x0001 : "InteropIndex", 0x0002 : "InteropVersion", 0x000B : "ProcessingSoftware", 0x00FE : "SubfileType", 0x00FF : "OldSubfileType", 0x0100 : "ImageWidth", 0x0101 : "ImageHeight", 0x0102 : "BitsPerSample", 0x0103 : "Compression", 0x0106 : "PhotometricInterpretation", 0x0107 : "Thresholding", 0x0108 : "CellWidth", 0x0109 : "CellLength", 0x010A : "FillOrder", 0x010D : "DocumentName", 0x010E : "ImageDescription", 0x010F : "Make", 0x0110 : "Model", 0x0111 : "StripOffsets", 0x0112 : "Orientation", 0x0115 : "SamplesPerPixel", 0x0116 : "RowsPerStrip", 0x0117 : "StripByteCounts", 0x0118 : "MinSampleValue", 0x0119 : "MaxSampleValue", 0x011A : "XResolution", 0x011B : "YResolution", 0x011C : "PlanarConfiguration", 0x011D : "PageName", 0x011E : "XPosition", 0x011F : "YPosition", 0x0120 : "FreeOffsets", 0x0121 : "FreeByteCounts", 0x0122 : "GrayResponseUnit", 0x0123 : "GrayResponseCurve", 0x0124 : "T4Options", 0x0125 : "T6Options", 0x0128 : "ResolutionUnit", 0x0129 : "PageNumber", 0x012C : "ColorResponseUnit", 0x012D : "TransferFunction", 0x0131 : "Software", 0x0132 : "ModifyDate", 0x013B : "Artist", 0x013C : "HostComputer", 0x013D : "Predictor", 0x013E : "WhitePoint", 0x013F : "PrimaryChromaticities", 0x0140 : "ColorMap", 0x0141 : "HalftoneHints", 0x0142 : "TileWidth", 0x0143 : "TileLength", 0x0144 : "TileOffsets", 0x0145 : "TileByteCounts", 0x0146 : "BadFaxLines", 0x0147 : "CleanFaxData", 0x0148 : "ConsecutiveBadFaxLines", 0x014A : "SubIFD", 0x014C : "InkSet", 0x014D : "InkNames", 0x014E : "NumberofInks", 0x0150 : "DotRange", 0x0151 : "TargetPrinter", 0x0152 : "ExtraSamples", 0x0153 : "SampleFormat", 0x0154 : "SMinSampleValue", 0x0155 : "SMaxSampleValue", 0x0156 : "TransferRange", 0x0157 : "ClipPath", 0x0158 : "XClipPathUnits", 0x0159 : "YClipPathUnits", 0x015A : "Indexed", 0x015B : "JPEGTables", 0x015F : "OPIProxy", 0x0190 : "GlobalParametersIFD", 0x0191 : "ProfileType", 0x0192 : "FaxProfile", 0x0193 : "CodingMethods", 0x0194 : "VersionYear", 0x0195 : "ModeNumber", 0x01B1 : "Decode", 0x01B2 : "DefaultImageColor", 0x01B3 : "T82Options", 0x01B5 : "JPEGTables", 0x0200 : "JPEGProc", 0x0201 : "ThumbnailOffset", 0x0202 : "ThumbnailLength", 0x0203 : "JPEGRestartInterval", 0x0205 : "JPEGLosslessPredictors", 0x0206 : "JPEGPointTransforms", 0x0207 : "JPEGQTables", 0x0208 : "JPEGDCTables", 0x0209 : "JPEGACTables", 0x0211 : "YCbCrCoefficients", 0x0212 : "YCbCrSubSampling", 0x0213 : "YCbCrPositioning", 0x0214 : "ReferenceBlackWhite", 0x022F : "StripRowCounts", 0x02BC : "ApplicationNotes", 0x03E7 : "USPTOMiscellaneous", 0x1000 : "RelatedImageFileFormat", 0x1001 : "RelatedImageWidth", 0x1002 : "RelatedImageHeight", 0x4746 : "Rating", 0x4747 : "XP_DIP_XML", 0x4748 : "StitchInfo", 0x4749 : "RatingPercent", 0x800D : "ImageID", 0x80A3 : "WangTag1", 0x80A4 : "WangAnnotation", 0x80A5 : "WangTag3", 0x80A6 : "WangTag4", 0x80E3 : "Matteing", 0x80E4 : "DataType", 0x80E5 : "ImageDepth", 0x80E6 : "TileDepth", 0x827D : "Model2", 0x828D : "CFARepeatPatternDim", 0x828E : "CFAPattern2", 0x828F : "BatteryLevel", 0x8290 : "KodakIFD", 0x8298 : "Copyright", 0x829A : "ExposureTime", 0x829D : "FNumber", 0x82A5 : "MDFileTag", 0x82A6 : "MDScalePixel", 0x82A7 : "MDColorTable", 0x82A8 : "MDLabName", 0x82A9 : "MDSampleInfo", 0x82AA : "MDPrepDate", 0x82AB : "MDPrepTime", 0x82AC : "MDFileUnits", 0x830E : "PixelScale", 0x8335 : "AdventScale", 0x8336 : "AdventRevision", 0x835C : "UIC1Tag", 0x835D : "UIC2Tag", 0x835E : "UIC3Tag", 0x835F : "UIC4Tag", 0x83BB : "IPTC-NAA", 0x847E : "IntergraphPacketData", 0x847F : "IntergraphFlagRegisters", 0x8480 : "IntergraphMatrix", 0x8481 : "INGRReserved", 0x8482 : "ModelTiePoint", 0x84E0 : "Site", 0x84E1 : "ColorSequence", 0x84E2 : "IT8Header", 0x84E3 : "RasterPadding", 0x84E4 : "BitsPerRunLength", 0x84E5 : "BitsPerExtendedRunLength", 0x84E6 : "ColorTable", 0x84E7 : "ImageColorIndicator", 0x84E8 : "BackgroundColorIndicator", 0x84E9 : "ImageColorValue", 0x84EA : "BackgroundColorValue", 0x84EB : "PixelIntensityRange", 0x84EC : "TransparencyIndicator", 0x84ED : "ColorCharacterization", 0x84EE : "HCUsage", 0x84EF : "TrapIndicator", 0x84F0 : "CMYKEquivalent", 0x8546 : "SEMInfo", 0x8568 : "AFCP_IPTC", 0x85B8 : "PixelMagicJBIGOptions", 0x85D8 : "ModelTransform", 0x8602 : "WB_GRGBLevels", 0x8606 : "LeafData", 0x8649 : "PhotoshopSettings", 0x8769 : "ExifOffset", 0x8773 : "ICC_Profile", 0x877F : "TIFF_FXExtensions", 0x8780 : "MultiProfiles", 0x8781 : "SharedData", 0x8782 : "T88Options", 0x87AC : "ImageLayer", 0x87AF : "GeoTiffDirectory", 0x87B0 : "GeoTiffDoubleParams", 0x87B1 : "GeoTiffAsciiParams", 0x8822 : "ExposureProgram", 0x8824 : "SpectralSensitivity", 0x8825 : "GPSInfo", 0x8827 : "ISO", 0x8828 : "Opto-ElectricConvFactor", 0x8829 : "Interlace", 0x882A : "TimeZoneOffset", 0x882B : "SelfTimerMode", 0x8830 : "SensitivityType", 0x8831 : "StandardOutputSensitivity", 0x8832 : "RecommendedExposureIndex", 0x8833 : "ISOSpeed", 0x8834 : "ISOSpeedLatitudeyyy", 0x8835 : "ISOSpeedLatitudezzz", 0x885C : "FaxRecvParams", 0x885D : "FaxSubAddress", 0x885E : "FaxRecvTime", 0x888A : "LeafSubIFD", 0x9000 : "ExifVersion", 0x9003 : "DateTimeOriginal", 0x9004 : "CreateDate", 0x9101 : "ComponentsConfiguration", 0x9102 : "CompressedBitsPerPixel", 0x9201 : "ShutterSpeedValue", 0x9202 : "ApertureValue", 0x9203 : "BrightnessValue", 0x9204 : "ExposureCompensation", 0x9205 : "MaxApertureValue", 0x9206 : "SubjectDistance", 0x9207 : "MeteringMode", 0x9208 : "LightSource", 0x9209 : "Flash", 0x920A : "FocalLength", 0x920B : "FlashEnergy", 0x920C : "SpatialFrequencyResponse", 0x920D : "Noise", 0x920E : "FocalPlaneXResolution", 0x920F : "FocalPlaneYResolution", 0x9210 : "FocalPlaneResolutionUnit", 0x9211 : "ImageNumber", 0x9212 : "SecurityClassification", 0x9213 : "ImageHistory", 0x9214 : "SubjectArea", 0x9215 : "ExposureIndex", 0x9216 : "TIFF-EPStandardID", 0x9217 : "SensingMethod", 0x923A : "CIP3DataFile", 0x923B : "CIP3Sheet", 0x923C : "CIP3Side", 0x923F : "StoNits", 0x927C : "MakerNote", 0x9286 : "UserComment", 0x9290 : "SubSecTime", 0x9291 : "SubSecTimeOriginal", 0x9292 : "SubSecTimeDigitized", 0x932F : "MSDocumentText", 0x9330 : "MSPropertySetStorage", 0x9331 : "MSDocumentTextPosition", 0x935C : "ImageSourceData", 0x9C9B : "XPTitle", 0x9C9C : "XPComment", 0x9C9D : "XPAuthor", 0x9C9E : "XPKeywords", 0x9C9F : "XPSubject", 0xA000 : "FlashpixVersion", 0xA001 : "ColorSpace", 0xA002 : "ExifImageWidth", 0xA003 : "ExifImageHeight", 0xA004 : "RelatedSoundFile", 0xA005 : "InteropOffset", 0xA20B : "FlashEnergy", 0xA20C : "SpatialFrequencyResponse", 0xA20D : "Noise", 0xA20E : "FocalPlaneXResolution", 0xA20F : "FocalPlaneYResolution", 0xA210 : "FocalPlaneResolutionUnit", 0xA211 : "ImageNumber", 0xA212 : "SecurityClassification", 0xA213 : "ImageHistory", 0xA214 : "SubjectLocation", 0xA215 : "ExposureIndex", 0xA216 : "TIFF-EPStandardID", 0xA217 : "SensingMethod", 0xA300 : "FileSource", 0xA301 : "SceneType", 0xA302 : "CFAPattern", 0xA401 : "CustomRendered", 0xA402 : "ExposureMode", 0xA403 : "WhiteBalance", 0xA404 : "DigitalZoomRatio", 0xA405 : "FocalLengthIn35mmFilm", 0xA406 : "SceneCaptureType", 0xA407 : "GainControl", 0xA408 : "Contrast", 0xA409 : "Saturation", 0xA40A : "Sharpness", 0xA40B : "DeviceSettingDescription", 0xA40C : "SubjectDistanceRange", 0xA420 : "ImageUniqueID", 0xA430 : "OwnerName", 0xA431 : "SerialNumber", 0xA432 : "LensInfo", 0xA433 : "LensMake", 0xA434 : "LensModel", 0xA435 : "LensSerialNumber", 0xA480 : "GDALMetadata", 0xA481 : "GDALNoData", 0xA500 : "Gamma", 0xAFC0 : "ExpandSoftware", 0xAFC1 : "ExpandLens", 0xAFC2 : "ExpandFilm", 0xAFC3 : "ExpandFilterLens", 0xAFC4 : "ExpandScanner", 0xAFC5 : "ExpandFlashLamp", 0xBC01 : "PixelFormat", 0xBC02 : "Transformation", 0xBC03 : "Uncompressed", 0xBC04 : "ImageType", 0xBC80 : "ImageWidth", 0xBC81 : "ImageHeight", 0xBC82 : "WidthResolution", 0xBC83 : "HeightResolution", 0xBCC0 : "ImageOffset", 0xBCC1 : "ImageByteCount", 0xBCC2 : "AlphaOffset", 0xBCC3 : "AlphaByteCount", 0xBCC4 : "ImageDataDiscard", 0xBCC5 : "AlphaDataDiscard", 0xC427 : "OceScanjobDesc", 0xC428 : "OceApplicationSelector", 0xC429 : "OceIDNumber", 0xC42A : "OceImageLogic", 0xC44F : "Annotations", 0xC4A5 : "PrintIM", 0xC580 : "USPTOOriginalContentType", 0xC612 : "DNGVersion", 0xC613 : "DNGBackwardVersion", 0xC614 : "UniqueCameraModel", 0xC615 : "LocalizedCameraModel", 0xC616 : "CFAPlaneColor", 0xC617 : "CFALayout", 0xC618 : "LinearizationTable", 0xC619 : "BlackLevelRepeatDim", 0xC61A : "BlackLevel", 0xC61B : "BlackLevelDeltaH", 0xC61C : "BlackLevelDeltaV", 0xC61D : "WhiteLevel", 0xC61E : "DefaultScale", 0xC61F : "DefaultCropOrigin", 0xC620 : "DefaultCropSize", 0xC621 : "ColorMatrix1", 0xC622 : "ColorMatrix2", 0xC623 : "CameraCalibration1", 0xC624 : "CameraCalibration2", 0xC625 : "ReductionMatrix1", 0xC626 : "ReductionMatrix2", 0xC627 : "AnalogBalance", 0xC628 : "AsShotNeutral", 0xC629 : "AsShotWhiteXY", 0xC62A : "BaselineExposure", 0xC62B : "BaselineNoise", 0xC62C : "BaselineSharpness", 0xC62D : "BayerGreenSplit", 0xC62E : "LinearResponseLimit", 0xC62F : "CameraSerialNumber", 0xC630 : "DNGLensInfo", 0xC631 : "ChromaBlurRadius", 0xC632 : "AntiAliasStrength", 0xC633 : "ShadowScale", 0xC634 : "DNGPrivateData", 0xC635 : "MakerNoteSafety", 0xC640 : "RawImageSegmentation", 0xC65A : "CalibrationIlluminant1", 0xC65B : "CalibrationIlluminant2", 0xC65C : "BestQualityScale", 0xC65D : "RawDataUniqueID", 0xC660 : "AliasLayerMetadata", 0xC68B : "OriginalRawFileName", 0xC68C : "OriginalRawFileData", 0xC68D : "ActiveArea", 0xC68E : "MaskedAreas", 0xC68F : "AsShotICCProfile", 0xC690 : "AsShotPreProfileMatrix", 0xC691 : "CurrentICCProfile", 0xC692 : "CurrentPreProfileMatrix", 0xC6BF : "ColorimetricReference", 0xC6D2 : "PanasonicTitle", 0xC6D3 : "PanasonicTitle2", 0xC6F3 : "CameraCalibrationSig", 0xC6F4 : "ProfileCalibrationSig", 0xC6F5 : "ProfileIFD", 0xC6F6 : "AsShotProfileName", 0xC6F7 : "NoiseReductionApplied", 0xC6F8 : "ProfileName", 0xC6F9 : "ProfileHueSatMapDims", 0xC6FA : "ProfileHueSatMapData1", 0xC6FB : "ProfileHueSatMapData2", 0xC6FC : "ProfileToneCurve", 0xC6FD : "ProfileEmbedPolicy", 0xC6FE : "ProfileCopyright", 0xC714 : "ForwardMatrix1", 0xC715 : "ForwardMatrix2", 0xC716 : "PreviewApplicationName", 0xC717 : "PreviewApplicationVersion", 0xC718 : "PreviewSettingsName", 0xC719 : "PreviewSettingsDigest", 0xC71A : "PreviewColorSpace", 0xC71B : "PreviewDateTime", 0xC71C : "RawImageDigest", 0xC71D : "OriginalRawFileDigest", 0xC71E : "SubTileBlockSize", 0xC71F : "RowInterleaveFactor", 0xC725 : "ProfileLookTableDims", 0xC726 : "ProfileLookTableData", 0xC740 : "OpcodeList1", 0xC741 : "OpcodeList2", 0xC74E : "OpcodeList3", 0xC761 : "NoiseProfile", 0xC763 : "TimeCodes", 0xC764 : "FrameRate", 0xC772 : "TStop", 0xC789 : "ReelName", 0xC791 : "OriginalDefaultFinalSize", 0xC792 : "OriginalBestQualitySize", 0xC793 : "OriginalDefaultCropSize", 0xC7A1 : "CameraLabel", 0xC7A3 : "ProfileHueSatMapEncoding", 0xC7A4 : "ProfileLookTableEncoding", 0xC7A5 : "BaselineExposureOffset", 0xC7A6 : "DefaultBlackRender", 0xC7A7 : "NewRawImageDigest", 0xC7A8 : "RawToPreviewGain", 0xC7B5 : "DefaultUserCrop", 0xEA1C : "Padding", 0xEA1D : "OffsetSchema", 0xFDE8 : "OwnerName", 0xFDE9 : "SerialNumber", 0xFDEA : "Lens", 0xFE00 : "KDC_IFD", 0xFE4C : "RawFile", 0xFE4D : "Converter", 0xFE4E : "WhiteBalance", 0xFE51 : "Exposure", 0xFE52 : "Shadows", 0xFE53 : "Brightness", 0xFE54 : "Contrast", 0xFE55 : "Saturation", 0xFE56 : "Sharpness", 0xFE57 : "Smoothness", 0xFE58 : "MoireFilter" }, // GPS Tags gps : { 0x0000 : 'GPSVersionID', 0x0001 : 'GPSLatitudeRef', 0x0002 : 'GPSLatitude', 0x0003 : 'GPSLongitudeRef', 0x0004 : 'GPSLongitude', 0x0005 : 'GPSAltitudeRef', 0x0006 : 'GPSAltitude', 0x0007 : 'GPSTimeStamp', 0x0008 : 'GPSSatellites', 0x0009 : 'GPSStatus', 0x000A : 'GPSMeasureMode', 0x000B : 'GPSDOP', 0x000C : 'GPSSpeedRef', 0x000D : 'GPSSpeed', 0x000E : 'GPSTrackRef', 0x000F : 'GPSTrack', 0x0010 : 'GPSImgDirectionRef', 0x0011 : 'GPSImgDirection', 0x0012 : 'GPSMapDatum', 0x0013 : 'GPSDestLatitudeRef', 0x0014 : 'GPSDestLatitude', 0x0015 : 'GPSDestLongitudeRef', 0x0016 : 'GPSDestLongitude', 0x0017 : 'GPSDestBearingRef', 0x0018 : 'GPSDestBearing', 0x0019 : 'GPSDestDistanceRef', 0x001A : 'GPSDestDistance', 0x001B : 'GPSProcessingMethod', 0x001C : 'GPSAreaInformation', 0x001D : 'GPSDateStamp', 0x001E : 'GPSDifferential', 0x001F : 'GPSHPositioningError' }, ref : { /* helper functions - TODO might go in helper module */ arrToDeg: function(vArr, lRef){ var deg = parseFloat(vArr[0]), m = parseFloat(vArr[1]), s = parseFloat(vArr[2]); if(s==0 && m>0){ var _m = Math.floor(m); s = (m-_m)*60; m = _m; _m = null; } if(typeof deg !== 'number' || typeof m !== 'number' || typeof s !== 'number') return vArr; if (lRef === 'S' || lRef === 'N' || lRef === 'E' || lRef === 'W') { var lInt = (lRef == "S" || lRef == "W") ? -1 : 1; var v = (deg+(m/60)+(s/3600)) * lInt; return { description: deg.toString().concat(" deg ", m, "' ", s.toFixed(4), "'' ", lRef), value: (typeof v === 'number') ? v : [deg, m, s] }; } return [deg, m, s]; }, /* helper end */ ColorSpace : { 1 : "sRGB" }, ExposureProgram : { 0 : "Not defined", 1 : "Manual", 2 : "Normal program", 3 : "Aperture priority", 4 : "Shutter priority", 5 : "Creative program", 6 : "Action program", 7 : "Portrait mode", 8 : "Landscape mode" }, MeteringMode : { 0 : "Unknown", 1 : "Average", 2 : "CenterWeightedAverage", 3 : "Spot", 4 : "MultiSpot", 5 : "Pattern", 6 : "Partial", 255 : "Other" }, LightSource : { 0 : "Unknown", 1 : "Daylight", 2 : "Fluorescent", 3 : "Tungsten (incandescent light)", 4 : "Flash", 9 : "Fine weather", 10 : "Cloudy weather", 11 : "Shade", 12 : "Daylight fluorescent (D 5700 - 7100K)", 13 : "Day white fluorescent (N 4600 - 5400K)", 14 : "Cool white fluorescent (W 3900 - 4500K)", 15 : "White fluorescent (WW 3200 - 3700K)", 17 : "Standard light A", 18 : "Standard light B", 19 : "Standard light C", 20 : "D55", 21 : "D65", 22 : "D75", 23 : "D50", 24 : "ISO studio tungsten", 255 : "Other" }, Flash : { 0x0000 : "No Flash", 0x0001 : "Flash fired", 0x0005 : "Strobe return light not detected", 0x0007 : "Strobe return light detected", 0x0009 : "Flash fired, compulsory flash mode", 0x000D : "Flash fired, compulsory flash mode, return light not detected", 0x000F : "Flash fired, compulsory flash mode, return light detected", 0x0010 : "Flash did not fire, compulsory flash mode", 0x0018 : "Flash did not fire, auto mode", 0x0019 : "Flash fired, auto mode", 0x001D : "Flash fired, auto mode, return light not detected", 0x001F : "Flash fired, auto mode, return light detected", 0x0020 : "No flash function", 0x0041 : "Flash fired, red-eye reduction mode", 0x0045 : "Flash fired, red-eye reduction mode, return light not detected", 0x0047 : "Flash fired, red-eye reduction mode, return light detected", 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode", 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 0x0059 : "Flash fired, auto mode, red-eye reduction mode", 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode", 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode" }, SensingMethod : { 1 : "Not defined", 2 : "One-chip color area sensor", 3 : "Two-chip color area sensor", 4 : "Three-chip color area sensor", 5 : "Color sequential area sensor", 7 : "Trilinear sensor", 8 : "Color sequential linear sensor" }, SceneCaptureType : { 0 : "Standard", 1 : "Landscape", 2 : "Portrait", 3 : "Night scene" }, CustomRendered : { 0 : "Normal", 1 : "Custom" }, WhiteBalance : { 0 : "Auto", 1 : "Manual" }, GainControl : { 0 : "None", 1 : "Low gain up", 2 : "High gain up", 3 : "Low gain down", 4 : "High gain down" }, Contrast : { 0 : "Normal", 1 : "Soft", 2 : "Hard" }, Saturation : { 0 : "Normal", 1 : "Low saturation", 2 : "High saturation" }, Sharpness : { 0 : "Normal", 1 : "Soft", 2 : "Hard" }, SubjectDistanceRange : { 0 : "Unknown", 1 : "Macro", 2 : "Close view", 3 : "Distant view" }, ExposureTime : function(_t){ if (typeof _t === 'number' && _t < 0.25001 && _t > 0) { return '1/'.concat(Math.floor(0.5 + 1/_t)); } return (typeof _t === 'number') ? _t.toFixed(1).replace(/\.0$/, '') : _t.replace(/\.0$/, ''); }, FileSource : function(data){ return (Buffer.isBuffer(data) && data.toJSON()[0]===3) ? { description:"Digital Still Camera", value:3 } : (parseInt(data)||data); }, SceneType : function(data){ return (Buffer.isBuffer(data) && data.toJSON()[0]===1) ? { description:"Directly photographed", value:1 } : (parseInt(data)||data); }, CFAPattern : function(data){ /* The value consists of: - Two short, being the grid width and height of the repeated pattern. - Next, for every pixel in that pattern, an identification code. */ var arr = data.toJSON(); if ( (arr.reduce(function(a, b){return a + b;})) > 36 ) return '<truncated data>'; if ( arr.length < 2 ) return '<zero pattern size>'; var w = arr[1]; var h = arr[3]; if ( (4 + w * h) !== arr.length ) return '<truncated data>'; var cfaColor = ['Red', 'Green', 'Blue', 'Cyan', 'Magenta', 'Yellow', 'White']; var rtn = '['; var r = 0; arr.forEach(function(index,i){ if(i>3){ r++; var color = (index < cfaColor.length) ? cfaColor[index] : 0; rtn = rtn.concat( color ); color = null; if(r==2){ rtn = rtn.concat(']['); r = 0; } else { rtn = rtn.concat(','); } } }); console.log( rtn ); return (rtn.indexOf('0')==-1) ? rtn.slice(0,-1) : arr; }, ComponentsConfiguration : function(data){ var c = ["-", "Y", "Cb", "Cr", "R", "G", "B"]; /* TODO */ return data.toJSON(); }, GPSVersionID: function(data){ return (Buffer.isBuffer(data)) ? data.toJSON().join('.') : data.join('.'); }, GPSLatitudeRef: function(data){ var known = {N: {description:'North', value:data}, S: {description:'South', value:data}}; if(typeof data === 'string' && data in known){ return known[data]; } else if ( typeof parseInt(data) === 'number' ){ return (parseInt(data)<0) ? known.S : known.N; } return (Buffer.isBuffer(data)) ? data.toJSON() : data; }, GPSLatitude: function(data, lRef){ var values = (Buffer.isBuffer(data)) ? data.toJSON() : data; if( values instanceof Array && values.length == 3 ) return ExifImage.TAGS.ref.arrToDeg(values, lRef); return values; }, GPSLongitudeRef: function(data){ var known = {E: {description:'East', value:data}, W: {description:'West', value:data}}; if(typeof data === 'string' && data in known){ return known[data]; } else if ( typeof parseInt(data) === 'number' ){ return (parseInt(data)<0) ? known.W : known.E; } return (Buffer.isBuffer(data)) ? data.toJSON() : data; }, GPSLongitude: function(data, lRef){ var values = (Buffer.isBuffer(data)) ? data.toJSON() : data; if( values instanceof Array && values.length == 3 ) return ExifImage.TAGS.ref.arrToDeg(values, lRef); return values; }, GPSAltitudeRef: { 0: 'Above Sea Level', 1: 'Below Sea Level' }, GPSAltitude: function(data){ var v = (typeof data === 'string') ? parseInt(data.replace(/\s*m$/, '')) : data; if(typeof v === 'number') return {description: v.toString().trim().concat(' m'), value: data}; return data; }, GPSTimeStamp: function(data){ return (Buffer.isBuffer(data)) ? data.toJSON().join(':') : data.join(':'); }, GPSMeasureMode: { 2: '2-Dimensional Measurement', 3: '3-Dimensional Measurement' }, GPSStatus: { A: 'Measurement Active', V: 'Measurement Void' }, GPSSpeedRef: { K: 'km/h', M: 'mph', N: 'knots' }, GPSTrackRef: { M: 'Magnetic North', T: 'True North' }, GPSImgDirectionRef: { M: 'Magnetic North', T: 'True North' }, GPSDestLatitudeRef: { N: 'North', S: 'South' }, GPSDestLongitudeRef: { E: 'East', W: 'West' }, GPSDestBearingRef: { M: 'Magnetic North', T: 'True North' }, GPSDestDistanceRef: { K: 'Kilometers', M: 'Miles', N: 'Nautical Miles' }, GPSDifferential: { 0: 'No Correction', 1: 'Differential Corrected' } /* TODO - might be important for writing... sub ConvertExifText($$;$) 0x001b: { Name: 'GPSProcessingMethod', Writable: 'undef', Notes: 'values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', RawConvInv: 'Image::ExifTool::Exif::EncodeExifText($self,$val)', }, 0x001c: { Name: 'GPSAreaInformation', Writable: 'undef', RawConv: 'Image::ExifTool::Exif::ConvertExifText($self,$val,1)', RawConvInv: 'Image::ExifTool::Exif::EncodeExifText($self,$val)', }, GPSTimeStamp: { Notes => q{ when writing, date is stripped off if present, and time is adjusted to UTC if it includes a timezone }, ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)', ValueConvInv => '$val=~tr/:/ /;$val', # pull time out of any format date/time string # (converting to UTC if a timezone is given) PrintConvInv => sub { my $v = shift; my @tz; if ($v =~ s/([-+])(.*)//s) { # remove timezone my $s = $1 eq '-' ? 1 : -1; # opposite sign to convert back to UTC my $t = $2; @tz = ($s*$1, $s*$2) if $t =~ /^(\d{2}):?(\d{2})\s*$/; } my @a = ($v =~ /((?=\d|\.\d)\d*(?:\.\d*)?)/g); push @a, '00' while @a < 3; if (@tz) { # adjust to UTC $a[-2] += $tz[1]; $a[-3] += $tz[0]; while ($a[-2] >= 60) { $a[-2] -= 60; ++$a[-3] } while ($a[-2] < 0) { $a[-2] += 60; --$a[-3] } $a[-3] = ($a[-3] + 24) % 24; } return "$a[-3]:$a[-2]:$a[-1]"; } }, 0x001d: { Name: 'GPSDateStamp', Groups: { 2: 'Time' }, Writable: 'string', Format: 'undef', # (Casio EX-H20G uses "\0" instead of ":" as a separator) Count: 11, Shift: 'Time', Notes: q{ when writing, time is stripped off if present, after adjusting date/time to UTC if time includes a timezone. Format is YYYY:mm:dd }, ValueConv: 'Image::ExifTool::Exif::ExifDate($val)', ValueConvInv: '$val', # pull date out of any format date/time string # (and adjust to UTC if this is a full date/time/timezone value) PrintConvInv: q{ my $secs; if ($val =~ /[-+]/ and ($secs = Image::ExifTool::GetUnixTime($val, 1))) { $val = Image::ExifTool::ConvertUnixTime($secs); } return $val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/ ? "$1:$2:$3" : undef; }, } */ } };