Comments (5)
For others looking at this, I realized that parsing the makerNote is a very difficult and camera specific task. I ultimately wrote my own code to parse Canon MakerNotes using an early build of this repo https://github.com/exif-js/exif-js
from exifr.
Hi @jdcoldsmith
I'm looking to do the same but with fujifilm, got a bunch of information on exiv2 pages but I'm struggling to get the data in the right format. Would be awesome to get your insights
from exifr.
Hey @rcauquil! I can't say that I have worked with fujifilm exif tags, but I can give you the javascript file I created to parse a jpeg's MakerNote data. This file is derived from https://github.com/exif-js/exif-js. Hopefully this will work in your case, let me know if you need any help using the file!
/**
* receives a file read as a binary string and will return the requested MakerNote tags
*
* @param {String} fileString - must be a file read as a binary string using FileReader.readAsBinaryString()
* @param {Object} makerNoteTags - an object containing the requested MakerNote tags with the hex tag as the property
* and the name of the tag as the value (e.g. { 0x4015: 'VignettingCorr' })
* @returns {Object} an object containing the names and retrieved values of the MakerNote tags (e.g. { VignettingCorr: "13900" })
*/
export function ParseMakerNote(fileString, makerNoteTags) {
return findMakerNotesinJPEG(fileString, makerNoteTags);
}
function getStringAt(data, iOffset, iLength) {
let aStr = [];
for (let i = iOffset, j = 0; i < iOffset + iLength; i++, j++) {
aStr[j] = String.fromCharCode(getByteAt(data, i));
}
return aStr.join("");
}
function getLongAt(data, iOffset, bBigEndian) {
let iByte1 = getByteAt(data, iOffset);
let iByte2 = getByteAt(data, iOffset + 1);
let iByte3 = getByteAt(data, iOffset + 2);
let iByte4 = getByteAt(data, iOffset + 3);
let iLong = bBigEndian ? (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
: (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
if (iLong < 0) {
iLong += 4294967296;
}
return iLong;
}
function getSLongAt(data, iOffset, bBigEndian) {
let iULong = getLongAt(data, iOffset, bBigEndian);
if (iULong > 2147483647)
return iULong - 4294967296;
else
return iULong;
}
function getShortAt(data, iOffset, bBigEndian) {
let iShort = bBigEndian ? (getByteAt(data, iOffset) << 8) + getByteAt(data, iOffset + 1)
: (getByteAt(data, iOffset + 1) << 8) + getByteAt(data, iOffset);
if (iShort < 0) {
iShort += 65536;
}
return iShort;
}
function getByteAt(data, iOffset) {
return data.charCodeAt(iOffset) & 0xFF;
}
function readTagValue(data, iEntryOffset, iTIFFStart, iDirStart, bBigEnd, iOffsetBase) {
let iType = getShortAt(data, iEntryOffset + 2, bBigEnd);
let iNumValues = getLongAt(data, iEntryOffset + 4, bBigEnd);
let iValueOffset = getLongAt(data, iEntryOffset + 8, bBigEnd) + iTIFFStart + iOffsetBase;
switch (iType) {
case 1: // byte, 8-bit unsigned int
if (iNumValues == 1) {
return getByteAt(data, iEntryOffset + 8, bBigEnd);
} else {
let iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getByteAt(data, iValOffset + n);
}
return aVals;
}
case 2: // ascii, 8-bit byte
let iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
let ascii = getStringAt(data, iStringOffset, iNumValues);
// from perl libimage-exiftool Exif.pm
// "truncate at null terminator (shouldn't have a null based on
// the EXIF spec, but it seems that few people actually read
// the spec)
// So read the entire string length and trim off the NULL.
// trim trailing spaces must be a reference to
// "Note: allow spaces instead of nulls in the ID codes because
// it is fairly common for camera manufacturers to get this
// wrong"
return ascii.replace(/\0.*/, "").replace(/ +$/, "")
case 3: // short, 16 bit int
if (iNumValues == 1) {
return getShortAt(data, iEntryOffset + 8, bBigEnd);
} else {
let iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8);
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getShortAt(data, iValOffset + 2 * n, bBigEnd);
}
return aVals;
}
case 4: // long, 32 bit int
if (iNumValues == 1) {
return getLongAt(data, iEntryOffset + 8, bBigEnd);
} else {
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getLongAt(data, iValueOffset + 4 * n, bBigEnd);
}
return aVals;
}
case 5: // rational = two long values, first is numerator, second is
// denominator
if (iNumValues == 1) {
return getLongAt(data, iValueOffset, bBigEnd) / getLongAt(data, iValueOffset + 4, bBigEnd);
} else {
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getLongAt(data, iValueOffset + 8 * n, bBigEnd) / getLongAt(data, iValueOffset + 4 + 8 * n, bBigEnd);
}
return aVals;
}
// case 7: // undefined, 8-bit byte, value depending on field
case 7: // IFDPointer
if (iNumValues == 1) {
return getByteAt(data, iEntryOffset + 8, bBigEnd);
} else if (iNumValues > 20) { // lets assume it's a IFD
// pointer?
return getLongAt(data, iEntryOffset + 8, bBigEnd);
} else {
let iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8);
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getByteAt(data, iValOffset + n);
}
return aVals;
}
case 9: // slong, 32 bit signed int
if (iNumValues == 1) {
return getSLongAt(data, iEntryOffset + 8, bBigEnd);
} else {
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getSLongAt(data, iValueOffset + 4 * n, bBigEnd);
}
return aVals;
}
case 10: // signed rational, two slongs, first is numerator,
// second is denominator
if (iNumValues == 1) {
return getSLongAt(data, iValueOffset, bBigEnd) / getSLongAt(data, iValueOffset + 4, bBigEnd);
} else {
let aVals = [];
for (let n = 0; n < iNumValues; n++) {
aVals[n] = getSLongAt(data, iValueOffset + 8 * n, bBigEnd) / getSLongAt(data, iValueOffset + 4 + 8 * n, bBigEnd);
}
return aVals;
}
case 13: // IFDPointer
return getLongAt(data, iEntryOffset + 8, bBigEnd) + iDirStart;
}
}
function readTags(data, iTIFFStart, iDirStart, oStrings, bBigEnd, iOffsetBase = 0) {
let iEntries = getShortAt(data, iTIFFStart + iDirStart, bBigEnd);
let oTags = {};
for (let i = 0; i < iEntries; i++) {
let iEntryOffset = iTIFFStart + iDirStart + i * 12 + 2;
let localAddress = getShortAt(data, iEntryOffset, bBigEnd);
let strTag = oStrings[localAddress];
if (!strTag) {
continue;
}
oTags[strTag] = readTagValue(data, iEntryOffset, iTIFFStart, iDirStart, bBigEnd, iOffsetBase);
if (iEntries > 1000) {
return oTags;
}
}
return oTags;
}
function readEXIFData(fileString, iStart, makerNoteTags) {
if (getStringAt(fileString, iStart, 4) != "Exif") {
return false;
}
let iTIFFOffset = iStart + 6;
// test for TIFF validity and endianness
let bBigEnd;
if (getShortAt(fileString, iTIFFOffset) == 0x4949) {
bBigEnd = false;
} else if (getShortAt(fileString, iTIFFOffset) == 0x4D4D) {
bBigEnd = true;
} else {
return false;
}
if (getShortAt(fileString, iTIFFOffset + 2, bBigEnd) != 0x002A) {
return false;
}
if (getLongAt(fileString, iTIFFOffset + 4, bBigEnd) != 0x00000008) {
return false;
}
let oMakerNoteTags = {};
let oTags = readTags(fileString, iTIFFOffset, 8, { 0x8769: "ExifIFDPointer", 0x010F: "Make" }, bBigEnd);
// if we have exif and if the camera make is Canon
if (oTags.ExifIFDPointer && oTags.Make && oTags.Make.trim() == 'Canon') {
// get a pointer to the makerNote data
let oExifTags = readTags(fileString, iTIFFOffset, oTags.ExifIFDPointer, { 0x927C: "MakerNoteIFDPointer", 0xEA1D : "OffsetSchema" }, bBigEnd);
// if we found the MakerNote tag
if (oExifTags.MakerNoteIFDPointer) {
// check to see if there is an offset schema
let iOffsetBase = 0;
if (oExifTags.OffsetSchema) {
iOffsetBase = calculateOffsetBase(fileString, iTIFFOffset, oExifTags.MakerNoteIFDPointer, bBigEnd);
}
// get the MakerNote tags
oMakerNoteTags = readTags(fileString, iTIFFOffset, oExifTags.MakerNoteIFDPointer, makerNoteTags, bBigEnd, iOffsetBase);
// convert the tag values to strings
for (let strTag in oMakerNoteTags) {
let tagValue = oMakerNoteTags[strTag];
oMakerNoteTags[strTag] = tagValue.toString();
}
}
}
return oMakerNoteTags;
}
function calculateOffsetBase(fileString, iTIFFStart, iDirStart, bBigEnd) {
if (typeof iDirStart === 'undefined') {
return {};
}
let iEntries = getShortAt(fileString, iTIFFStart + iDirStart, bBigEnd);
let expectedFirstIFDValueLocation = iDirStart + 2 + (iEntries * 12);
let nextIFDvalue = getLongAt(fileString, expectedFirstIFDValueLocation + iTIFFStart, bBigEnd);
if (nextIFDvalue == 0x00000000) { // if next IFD pointer is blank,
// jump over it
expectedFirstIFDValueLocation += 4;
}
for (let i = 0; i < iEntries; i++) {
let iEntryOffset = iTIFFStart + iDirStart + i * 12 + 2;
let type = getShortAt(fileString, iEntryOffset + 2, bBigEnd);
let dataSize = getLongAt(fileString, iEntryOffset + 4, bBigEnd);
if ((type == 1 && dataSize > 4) || (type == 2 && dataSize > 4)
|| (type == 3 && dataSize > 2)
|| (type == 4 && dataSize > 1)
|| (type == 5 && dataSize > 1)
|| (type == 7 && dataSize > 4)
|| (type == 9 && dataSize > 1)
|| (type == 10 && dataSize > 1)
|| (type == 13 && dataSize > 1)) { // i.e., if more
// than 8 bytes,
// this must be a
// pointer
let ifdPointer = getLongAt(fileString, iEntryOffset + 8, bBigEnd);
let differenceBetweenExpectedAndActual = expectedFirstIFDValueLocation - ifdPointer;
return differenceBetweenExpectedAndActual;
}
}
return 0; // default to no offset
}
function findMakerNotesinJPEG(fileString, makerNoteTags) {
if (getByteAt(fileString, 0) != 0xFF || getByteAt(fileString, 1) != 0xD8) {
return false; // not a valid jpeg
}
let iOffset = 2;
let iLength = fileString.length;
let oExifData = {};
while (iOffset < iLength) {
if (getByteAt(fileString, iOffset) != 0xFF) {
return oExifData;
}
let iMarker = getByteAt(fileString, iOffset + 1);
// look for the EXIF data tag
if (iMarker == 225 || iMarker == 22400) {
return readEXIFData(fileString, iOffset + 4, makerNoteTags);
} else {
iOffset += 2 + getShortAt(fileString, iOffset + 2, true);
}
}
return oExifData;
}
from exifr.
hey thanks @jdcoldsmith !
sorry for the delayed reply I had a tough week...
I'll give it a try, I'm pretty new to exif, ifd etc... and it does not make sense to me yet.
I found all the tags etc... from exiv2 docs:
https://exiv2.org/tags-fujifilm.html
https://exiv2.org/makernote.html
I'm trying to understand how it's shaped and how I can extract the tag:value
I've seen your code and the link, but I'm strugling to wrap my head around
If you have time I'd like to discuss it
Have a great evening
from exifr.
Feel free to shoot me any questions you have and I'll do my best to answer them!
from exifr.
Related Issues (20)
- Missing/wrong Rating meta? HOT 2
- Using exifr.parse on a buffer that contains just exif data HOT 1
- Missing some tag data HOT 1
- Can't load fs and zlib HOT 1
- Unable to get lens model number for Nikon camera, I'm sure it's stored in MakerNote
- How to store all tags in the specified image? HOT 2
- Why doesn't appear on jpg pages?
- exifr leaks File Descriptors on error
- Exifr fails to read PNG image parameters if the field is 1000+ characters HOT 1
- exifr.gps -> NaN on ios 15 - desktop and android 13 are fine
- Error: ICC header is too short on AVIF image HOT 1
- JPG ifd1 ThumbnailOffset was lost 12 byte
- Automate transcoding of userComment string value
- Support for recent accessibility IPTC fields HOT 4
- Null value for GPS data , while uploading image from android(v11 + ) mobile HOT 1
- Compatibility with Vite
- Support for react native typescript
- Expose TIFF header start and position of Makernote tag HOT 3
- Parsing IPTC from TIFF throws error
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from exifr.