Skip to content

Instantly share code, notes, and snippets.

@redaktor
Last active August 29, 2015 14:01
Show Gist options
  • Save redaktor/c3683f49cab545e91729 to your computer and use it in GitHub Desktop.
Save redaktor/c3683f49cab545e91729 to your computer and use it in GitHub Desktop.
WIP (exiftool like)
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 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',
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,
},
*/
/**
* 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]];
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.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) {
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 : []
}
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 = this.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
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 '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){
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',
// 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'
},
// 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(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;
return {
description: deg.toString().concat('° ', m, '\' ', s.toFixed(4), '" ', lRef),
value: (typeof v === 'number') ? v : [deg, m, s]
};
}
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;
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;
},
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(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); },
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;
}
}
}
*/
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',
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'
},
/*
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
},
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',
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',
},
UserComment : function(data){
if(Buffer.isBuffer(data)) data = data.toString('utf8');
return ExifImage.prototype.tidyString(data);
},
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(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);
},
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.
- 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;
},
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 - own namespace ? - needs fix - DNG reader for headers
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){
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 - 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
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;
},
}
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,
},
],
*/
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment