Last active
August 29, 2015 14:01
-
-
Save redaktor/c3683f49cab545e91729 to your computer and use it in GitHub Desktop.
WIP (exiftool like)
This file contains 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 characters
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