Coder Social home page Coder Social logo

Parsing makerNote tag about exifr HOT 5 CLOSED

jdcoldsmith avatar jdcoldsmith commented on June 15, 2024 5
Parsing makerNote tag

from exifr.

Comments (5)

jdcoldsmith avatar jdcoldsmith commented on June 15, 2024

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.

rcauquil avatar rcauquil commented on June 15, 2024

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.

jdcoldsmith avatar jdcoldsmith commented on June 15, 2024

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.

rcauquil avatar rcauquil commented on June 15, 2024

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.

jdcoldsmith avatar jdcoldsmith commented on June 15, 2024

Feel free to shoot me any questions you have and I'll do my best to answer them!

from exifr.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.