[
  {
    "path": ".gitignore",
    "content": "node_modules\ndist"
  },
  {
    "path": ".npmignore",
    "content": "test\nMakefile"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 0.1.11 - July 9th 2017\n\n - On nodejs, decode strings using UTF-8 instead of ASCII. Ideally we'd do this in the browser (UTF-16 currently) as well but that is a bit more work. \n\n## 0.1.10 - July 9th 2017\n\n- Ignore unknown tag formats instead of failing. There seem to be images with format 0 that otherwise contain valid data.\n- Fix bug in date parsing to not rely on the current date. This broke when the current date was the 31st of the month.\n- In case a tag appears more than once, take the first, not the last. For example some pictures taken on an iPhone suffer from duplicate Orientation tags, where the first one is the wrong one.\n- treat ModifyDate tag as a date value\n\n## 0.1.9 - April 9th 2015\n\n- parse ISO 8601 dates with timezone offset\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License\n===============\n\nCopyright (c) 2010 Bruno Windels <bruno.windels@gmail.com>, Daniel Leinich <leinich@gmx.net>.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "Makefile",
    "content": "VERSION=$(shell node --eval \"console.log(require('./package.json').version)\")\nBUNDLE=dist/exif-parser-$(VERSION).js\nMIN_BUNDLE=dist/exif-parser-$(VERSION)-min.js\nBROWSERIFY=node_modules/.bin/browserify\nUGLIFY=node_modules/.bin/uglifyjs\n\nbuild-browser-bundle: setup\n\t@echo \"building $(BUNDLE) ...\"\n\t@mkdir -p dist/\n\t@$(BROWSERIFY) --bare browser-global.js -o $(BUNDLE)\n\t@echo \"building $(MIN_BUNDLE) ...\"\n\t@$(UGLIFY) $(BUNDLE) -o $(MIN_BUNDLE) --compress\nsetup:\n\t@npm install --no-optional --loglevel error --development\nclean:\n\t@rm -rf dist/"
  },
  {
    "path": "README.md",
    "content": "exif-parser\n========\nexif-parser is a parser for image metadata in the exif format, the most popular metadata format for jpeg and tiff images. It is written in pure javascript and has no external dependencies. It can also get the size of jpeg images and the size of the jpeg thumbnail embedded in the exif data. It can also extract the embedded thumbnail image.\n\n### Installing\n\n    npm install exif-parser\n\nYou can also build a browser bundle to include it with a `<script>` tag in a HTML document, like this:\n\n\tgit clone git@github.com:bwindels/exif-parser.git\n\tcd exif-parser/\n\tmake build-browser-bundle\n\nBuilt versions of the bundles are also available in the [exif-parser-browser-bundles repo](https://github.com/bwindels/exif-parser-browser-bundles).\n\nThis will generate a `dist/exif-parser-(version).js` and `dist/exif-parser-(version)-min.js` file. These bundles expose the parser on the `ExifParser` global variable, which you would use like this:\n\n\tvar parser = window.ExifParser.create(arrayBuffer);\n\n### Creating a parser\nTo start parsing exif data, create a new parser like below. Note that the buffer you pass does not have to be the buffer for the full jpeg file. The exif section of a jpeg file has a maximum size of 65535 bytes and the section seems to always occur within the first 100 bytes of the file. So it is safe to only fetch the first 65635 bytes of a jpeg file and pass those to the parser.\n\nThe buffer you pass to create can be a node buffer or a DOM ArrayBuffer.\nNote that the `parse` method throws an Error when the data passed in is not valid JPEG data.\n\n```\nvar parser = require('exif-parser').create(buffer);\ntry {\n\tvar result = parser.parse();\n} catch(err) {\n\t// got invalid data, handle error\n}\n```\n\n### Setting the flags\n\nBefore calling parse, you can set a number of flags on the parser, telling it how to behave while parsing.\n\nAdd fields in the binary format to result. Since these fields are mostly used for internal fields like Padding, you generally are not interested in these. If enabled, values for these fields will be a Buffer object in node or an ArrayBuffer in DOM environments (browsers).\n\n    parser.enableBinaryFields([boolean]), default false;\n\nEXIF tags are organized into different sections, and to tell you the offset to other sections, EXIF uses certain tags. These tags don't tell you anything about the image, but are more for parsers to find out about all tags. Hence, these \"pointer\" fields are not included in the result tags field by default. Change this flag to include them nonetheless.\n\n    parser.enablePointers([boolean]), default false;\n\nResolve tags to their textual name, making result.tags a dictonary object instead of an array with the tag objects with no textual tag name.\n\n    parser.enableTagNames([boolean]), default true;\n\nRead the image size while parsing.\n\n    parser.enableImageSize([boolean]), default true;\n\nRead the EXIF tags. Could be useful to disable if you only want to read the image size.\n\n    parser.enableReturnTags([boolean]), default true;\n\nEXIF values can be represented in a number of formats (fractions, degrees, arrays, ...) with different precision.\nEnabling this tries to cast values as much as possible to the appropriate javascript types like number, Date.\n\n    parser.enableSimpleValues([boolean]), default true;\n\n### working with the result\n\n#### Getting the tags\nthe tags that were found while parsing are stored in ```result.tags``` unless you set ```parser.enableReturnTags(false)```. If ```parser.enableTagNames``` is set to true, ```result.tags``` will be an object with the key being the tag name and the value being the tag value. If ```parser.enableTagNames``` is set to false, ```result.tags``` will be an array of objects containing section, type and value properties.\n\n#### Getting the image size\nIf ```parser.enableImageSize``` is set to true, ```result.getImageSize()``` will give you the image size as an object with width and height properties.\n\n#### Getting the thumbnail\n\nYou can check if there is a thumbnail present in the exif data with ```result.hasThumbnail()```. Exif supports thumbnails is jpeg and tiff format, though most are in jpeg format. You can check if there is a thumbnail present in a give format by passing the mime type: ```result.hasThumbnail(\"image/jpeg\")```.\n\nYou can also get the image size of the thumbnail as an object with width and height properties: ```result.getThumbnailSize()```.\n\nTo get the node buffer or arraybuffer containing just the thumbnail, call ```result.getThumbnailBuffer()```\n\n# Running the unit tests\n\nInstall `nodeunit` globally from npm if you haven't done so already.\nYou can run the tests with `nodeunit test/test-*.js`.\n\n# Contributions\n\nI welcome external contributions through pull requests. If you do so, please don't use regular expressions. I don't like them, and don't want to maintain a project where they are used. Also, when fixing a bug please provide a regression unit test if it makes sense.\n"
  },
  {
    "path": "browser-global.js",
    "content": "var api = require('./index');\nvar global = (1,eval)('this');\nglobal.ExifParser = api;\n"
  },
  {
    "path": "cmd/extract-thumbnail.js",
    "content": "var exifParser = require('../index');\n\nvar buf = require('fs').readFileSync(process.argv[2]);\nvar parser = exifParser.create(buf).enableReturnTags(false);\nvar result = parser.parse();\n\nif(!result.hasThumbnail('image/jpeg')) {\n\tconsole.log('no jpeg thumbnail in this image');\n\tprocess.exit(1);\n} else {\n\tvar size = result.getThumbnailSize();\n\tvar buffer = result.getThumbnailBuffer();\n\trequire('fs').writeFileSync(process.argv[3], buffer);\n\tconsole.log('wrote thumbnail of size ' + size.width + 'x' + size.height + ' to ' + process.argv[3] + ' (' + buffer.length + ' bytes)');\n\tprocess.exit(0);\n}"
  },
  {
    "path": "cmd/list.js",
    "content": "var exifParser = require('../index');\n\nvar buf = require('fs').readFileSync(process.argv[2]);\nvar parser = exifParser.create(buf);\nvar result = parser.parse();\n\nObject.keys(result.tags).forEach(function(name) {\n\tconsole.log(name+': ' + result.tags[name]);\n});"
  },
  {
    "path": "index.js",
    "content": "var Parser = require('./lib/parser');\n\nfunction getGlobal() {\n\treturn (1,eval)('this');\n}\n\nmodule.exports = {\n\tcreate: function(buffer, global) {\n\t\tglobal = global || getGlobal();\n\t\tif(buffer instanceof global.ArrayBuffer) {\n\t\t\tvar DOMBufferStream = require('./lib/dom-bufferstream');\n\t\t\treturn new Parser(new DOMBufferStream(buffer, 0, buffer.byteLength, true, global));\n\t\t} else {\n\t\t\tvar NodeBufferStream = require('./lib/bufferstream');\n\t\t\treturn new Parser(new NodeBufferStream(buffer, 0, buffer.length, true));\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "lib/bufferstream.js",
    "content": "function BufferStream(buffer, offset, length, bigEndian) {\n\tthis.buffer = buffer;\n\tthis.offset = offset || 0;\n\tlength = typeof length === 'number' ? length : buffer.length;\n\tthis.endPosition = this.offset + length;\n\tthis.setBigEndian(bigEndian);\n}\n\nBufferStream.prototype = {\n\tsetBigEndian: function(bigEndian) {\n\t\tthis.bigEndian = !!bigEndian;\n\t},\n\tnextUInt8: function() {\n\t\tvar value = this.buffer.readUInt8(this.offset);\n\t\tthis.offset += 1;\n\t\treturn value;\n\t},\n\tnextInt8: function() {\n\t\tvar value = this.buffer.readInt8(this.offset);\n\t\tthis.offset += 1;\n\t\treturn value;\n\t},\n\tnextUInt16: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readUInt16BE(this.offset) : this.buffer.readUInt16LE(this.offset);\n\t\tthis.offset += 2;\n\t\treturn value;\n\t},\n\tnextUInt32: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readUInt32BE(this.offset) : this.buffer.readUInt32LE(this.offset);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextInt16: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readInt16BE(this.offset) : this.buffer.readInt16LE(this.offset);\n\t\tthis.offset += 2;\n\t\treturn value;\n\t},\n\tnextInt32: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readInt32BE(this.offset) : this.buffer.readInt32LE(this.offset);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextFloat: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readFloatBE(this.offset) : this.buffer.readFloatLE(this.offset);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextDouble: function() {\n\t\tvar value = this.bigEndian ? this.buffer.readDoubleBE(this.offset) : this.buffer.readDoubleLE(this.offset);\n\t\tthis.offset += 8;\n\t\treturn value;\n\t},\n\tnextBuffer: function(length) {\n\t\tvar value = this.buffer.slice(this.offset, this.offset + length);\n\t\tthis.offset += length;\n\t\treturn value;\n\t},\n\tremainingLength: function() {\n\t\treturn this.endPosition - this.offset;\n\t},\n\tnextString: function(length) {\n\t\tvar value = this.buffer.toString('utf8', this.offset, this.offset + length);\n\t\tthis.offset += length;\n\t\treturn value;\n\t},\n\tmark: function() {\n\t\tvar self = this;\n\t\treturn {\n\t\t\topenWithOffset: function(offset) {\n\t\t\t\toffset = (offset || 0) + this.offset;\n\t\t\t\treturn new BufferStream(self.buffer, offset, self.endPosition - offset, self.bigEndian);\n\t\t\t},\n\t\t\toffset: this.offset\n\t\t};\n\t},\n\toffsetFrom: function(marker) {\n\t\treturn this.offset - marker.offset;\n\t},\n\tskip: function(amount) {\n\t\tthis.offset += amount;\n\t},\n\tbranch: function(offset, length) {\n\t\tlength = typeof length === 'number' ? length : this.endPosition - (this.offset + offset);\n\t\treturn new BufferStream(this.buffer, this.offset + offset, length, this.bigEndian);\n\t}\n};\n\nmodule.exports = BufferStream;\n"
  },
  {
    "path": "lib/date.js",
    "content": "function parseNumber(s) {\n\treturn parseInt(s, 10);\n}\n\n//in seconds\nvar hours = 3600;\nvar minutes = 60;\n\n//take date (year, month, day) and time (hour, minutes, seconds) digits in UTC\n//and return a timestamp in seconds\nfunction parseDateTimeParts(dateParts, timeParts) {\n\tdateParts = dateParts.map(parseNumber);\n\ttimeParts = timeParts.map(parseNumber);\n\tvar year = dateParts[0];\n\tvar month = dateParts[1] - 1;\n\tvar day = dateParts[2];\n\tvar hours = timeParts[0];\n\tvar minutes = timeParts[1];\n\tvar seconds = timeParts[2];\n\tvar date = Date.UTC(year, month, day, hours, minutes, seconds, 0);\n\tvar timestamp = date / 1000;\n\treturn timestamp;\n}\n\n//parse date with \"2004-09-04T23:39:06-08:00\" format,\n//one of the formats supported by ISO 8601, and\n//convert to utc timestamp in seconds\nfunction parseDateWithTimezoneFormat(dateTimeStr) {\n\n\tvar dateParts = dateTimeStr.substr(0, 10).split('-');\n\tvar timeParts = dateTimeStr.substr(11, 8).split(':');\n\tvar timezoneStr = dateTimeStr.substr(19, 6);\n\tvar timezoneParts = timezoneStr.split(':').map(parseNumber);\n\tvar timezoneOffset = (timezoneParts[0] * hours) +\n\t\t(timezoneParts[1] * minutes);\n\n\tvar timestamp = parseDateTimeParts(dateParts, timeParts);\n\t//minus because the timezoneOffset describes\n\t//how much the described time is ahead of UTC\n\ttimestamp -= timezoneOffset;\n\n\tif(typeof timestamp === 'number' && !isNaN(timestamp)) {\n\t\treturn timestamp;\n\t}\n}\n\n//parse date with \"YYYY:MM:DD hh:mm:ss\" format, convert to utc timestamp in seconds\nfunction parseDateWithSpecFormat(dateTimeStr) {\n\tvar parts = dateTimeStr.split(' '),\n\t\tdateParts = parts[0].split(':'),\n\t\ttimeParts = parts[1].split(':');\n\n\tvar timestamp = parseDateTimeParts(dateParts, timeParts);\n\n\tif(typeof timestamp === 'number' && !isNaN(timestamp)) {\n\t\treturn timestamp;\n\t}\n}\n\nfunction parseExifDate(dateTimeStr) {\n\t//some easy checks to determine two common date formats\n\n\t//is the date in the standard \"YYYY:MM:DD hh:mm:ss\" format?\n\tvar isSpecFormat = dateTimeStr.length === 19 &&\n\t\tdateTimeStr.charAt(4) === ':';\n\t//is the date in the non-standard format,\n\t//\"2004-09-04T23:39:06-08:00\" to include a timezone?\n\tvar isTimezoneFormat = dateTimeStr.length === 25 &&\n\t\tdateTimeStr.charAt(10) === 'T';\n\tvar timestamp;\n\n\tif(isTimezoneFormat) {\n\t\treturn parseDateWithTimezoneFormat(dateTimeStr);\n\t}\n\telse if(isSpecFormat) {\n\t\treturn parseDateWithSpecFormat(dateTimeStr);\n\t}\n}\n\nmodule.exports = {\n\tparseDateWithSpecFormat: parseDateWithSpecFormat,\n\tparseDateWithTimezoneFormat: parseDateWithTimezoneFormat,\n\tparseExifDate: parseExifDate\n};\n"
  },
  {
    "path": "lib/dom-bufferstream.js",
    "content": "/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */\n\nfunction DOMBufferStream(arrayBuffer, offset, length, bigEndian, global, parentOffset) {\n\tthis.global = global;\n\toffset = offset || 0;\n\tlength = length || (arrayBuffer.byteLength - offset);\n\tthis.arrayBuffer = arrayBuffer.slice(offset, offset + length);\n\tthis.view = new global.DataView(this.arrayBuffer, 0, this.arrayBuffer.byteLength);\n\tthis.setBigEndian(bigEndian);\n\tthis.offset = 0;\n\tthis.parentOffset = (parentOffset || 0) + offset;\n}\n\nDOMBufferStream.prototype = {\n\tsetBigEndian: function(bigEndian) {\n\t\tthis.littleEndian = !bigEndian;\n\t},\n\tnextUInt8: function() {\n\t\tvar value = this.view.getUint8(this.offset);\n\t\tthis.offset += 1;\n\t\treturn value;\n\t},\n\tnextInt8: function() {\n\t\tvar value = this.view.getInt8(this.offset);\n\t\tthis.offset += 1;\n\t\treturn value;\n\t},\n\tnextUInt16: function() {\n\t\tvar value = this.view.getUint16(this.offset, this.littleEndian);\n\t\tthis.offset += 2;\n\t\treturn value;\n\t},\n\tnextUInt32: function() {\n\t\tvar value = this.view.getUint32(this.offset, this.littleEndian);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextInt16: function() {\n\t\tvar value = this.view.getInt16(this.offset, this.littleEndian);\n\t\tthis.offset += 2;\n\t\treturn value;\n\t},\n\tnextInt32: function() {\n\t\tvar value = this.view.getInt32(this.offset, this.littleEndian);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextFloat: function() {\n\t\tvar value = this.view.getFloat32(this.offset, this.littleEndian);\n\t\tthis.offset += 4;\n\t\treturn value;\n\t},\n\tnextDouble: function() {\n\t\tvar value = this.view.getFloat64(this.offset, this.littleEndian);\n\t\tthis.offset += 8;\n\t\treturn value;\n\t},\n\tnextBuffer: function(length) {\n\t\t//this won't work in IE10\n\t\tvar value = this.arrayBuffer.slice(this.offset, this.offset + length);\n\t\tthis.offset += length;\n\t\treturn value;\n\t},\n\tremainingLength: function() {\n\t\treturn this.arrayBuffer.byteLength - this.offset;\n\t},\n\tnextString: function(length) {\n\t\tvar value = this.arrayBuffer.slice(this.offset, this.offset + length);\n\t\tvalue = String.fromCharCode.apply(null, new this.global.Uint8Array(value));\n\t\tthis.offset += length;\n\t\treturn value;\n\t},\n\tmark: function() {\n\t\tvar self = this;\n\t\treturn {\n\t\t\topenWithOffset: function(offset) {\n\t\t\t\toffset = (offset || 0) + this.offset;\n\t\t\t\treturn new DOMBufferStream(self.arrayBuffer, offset, self.arrayBuffer.byteLength - offset, !self.littleEndian, self.global, self.parentOffset);\n\t\t\t},\n\t\t\toffset: this.offset,\n\t\t\tgetParentOffset: function() {\n\t\t\t\treturn self.parentOffset;\n\t\t\t}\n\t\t};\n\t},\n\toffsetFrom: function(marker) {\n\t\treturn this.parentOffset + this.offset - (marker.offset + marker.getParentOffset());\n\t},\n\tskip: function(amount) {\n\t\tthis.offset += amount;\n\t},\n\tbranch: function(offset, length) {\n\t\tlength = typeof length === 'number' ? length : this.arrayBuffer.byteLength - (this.offset + offset);\n\t\treturn new DOMBufferStream(this.arrayBuffer, this.offset + offset, length, !this.littleEndian, this.global, this.parentOffset);\n\t}\n};\n\nmodule.exports = DOMBufferStream;\n"
  },
  {
    "path": "lib/exif-tags.js",
    "content": "module.exports = {\n\texif : {\n\t\t0x0001 : \"InteropIndex\",\n\t\t0x0002 : \"InteropVersion\",\n\t\t0x000B : \"ProcessingSoftware\",\n\t\t0x00FE : \"SubfileType\",\n\t\t0x00FF : \"OldSubfileType\",\n\t\t0x0100 : \"ImageWidth\",\n\t\t0x0101 : \"ImageHeight\",\n\t\t0x0102 : \"BitsPerSample\",\n\t\t0x0103 : \"Compression\",\n\t\t0x0106 : \"PhotometricInterpretation\",\n\t\t0x0107 : \"Thresholding\",\n\t\t0x0108 : \"CellWidth\",\n\t\t0x0109 : \"CellLength\",\n\t\t0x010A : \"FillOrder\",\n\t\t0x010D : \"DocumentName\",\n\t\t0x010E : \"ImageDescription\",\n\t\t0x010F : \"Make\",\n\t\t0x0110 : \"Model\",\n\t\t0x0111 : \"StripOffsets\",\n\t\t0x0112 : \"Orientation\",\n\t\t0x0115 : \"SamplesPerPixel\",\n\t\t0x0116 : \"RowsPerStrip\",\n\t\t0x0117 : \"StripByteCounts\",\n\t\t0x0118 : \"MinSampleValue\",\n\t\t0x0119 : \"MaxSampleValue\",\n\t\t0x011A : \"XResolution\",\n\t\t0x011B : \"YResolution\",\n\t\t0x011C : \"PlanarConfiguration\",\n\t\t0x011D : \"PageName\",\n\t\t0x011E : \"XPosition\",\n\t\t0x011F : \"YPosition\",\n\t\t0x0120 : \"FreeOffsets\",\n\t\t0x0121 : \"FreeByteCounts\",\n\t\t0x0122 : \"GrayResponseUnit\",\n\t\t0x0123 : \"GrayResponseCurve\",\n\t\t0x0124 : \"T4Options\",\n\t\t0x0125 : \"T6Options\",\n\t\t0x0128 : \"ResolutionUnit\",\n\t\t0x0129 : \"PageNumber\",\n\t\t0x012C : \"ColorResponseUnit\",\n\t\t0x012D : \"TransferFunction\",\n\t\t0x0131 : \"Software\",\n\t\t0x0132 : \"ModifyDate\",\n\t\t0x013B : \"Artist\",\n\t\t0x013C : \"HostComputer\",\n\t\t0x013D : \"Predictor\",\n\t\t0x013E : \"WhitePoint\",\n\t\t0x013F : \"PrimaryChromaticities\",\n\t\t0x0140 : \"ColorMap\",\n\t\t0x0141 : \"HalftoneHints\",\n\t\t0x0142 : \"TileWidth\",\n\t\t0x0143 : \"TileLength\",\n\t\t0x0144 : \"TileOffsets\",\n\t\t0x0145 : \"TileByteCounts\",\n\t\t0x0146 : \"BadFaxLines\",\n\t\t0x0147 : \"CleanFaxData\",\n\t\t0x0148 : \"ConsecutiveBadFaxLines\",\n\t\t0x014A : \"SubIFD\",\n\t\t0x014C : \"InkSet\",\n\t\t0x014D : \"InkNames\",\n\t\t0x014E : \"NumberofInks\",\n\t\t0x0150 : \"DotRange\",\n\t\t0x0151 : \"TargetPrinter\",\n\t\t0x0152 : \"ExtraSamples\",\n\t\t0x0153 : \"SampleFormat\",\n\t\t0x0154 : \"SMinSampleValue\",\n\t\t0x0155 : \"SMaxSampleValue\",\n\t\t0x0156 : \"TransferRange\",\n\t\t0x0157 : \"ClipPath\",\n\t\t0x0158 : \"XClipPathUnits\",\n\t\t0x0159 : \"YClipPathUnits\",\n\t\t0x015A : \"Indexed\",\n\t\t0x015B : \"JPEGTables\",\n\t\t0x015F : \"OPIProxy\",\n\t\t0x0190 : \"GlobalParametersIFD\",\n\t\t0x0191 : \"ProfileType\",\n\t\t0x0192 : \"FaxProfile\",\n\t\t0x0193 : \"CodingMethods\",\n\t\t0x0194 : \"VersionYear\",\n\t\t0x0195 : \"ModeNumber\",\n\t\t0x01B1 : \"Decode\",\n\t\t0x01B2 : \"DefaultImageColor\",\n\t\t0x01B3 : \"T82Options\",\n\t\t0x01B5 : \"JPEGTables\",\n\t\t0x0200 : \"JPEGProc\",\n\t\t0x0201 : \"ThumbnailOffset\",\n\t\t0x0202 : \"ThumbnailLength\",\n\t\t0x0203 : \"JPEGRestartInterval\",\n\t\t0x0205 : \"JPEGLosslessPredictors\",\n\t\t0x0206 : \"JPEGPointTransforms\",\n\t\t0x0207 : \"JPEGQTables\",\n\t\t0x0208 : \"JPEGDCTables\",\n\t\t0x0209 : \"JPEGACTables\",\n\t\t0x0211 : \"YCbCrCoefficients\",\n\t\t0x0212 : \"YCbCrSubSampling\",\n\t\t0x0213 : \"YCbCrPositioning\",\n\t\t0x0214 : \"ReferenceBlackWhite\",\n\t\t0x022F : \"StripRowCounts\",\n\t\t0x02BC : \"ApplicationNotes\",\n\t\t0x03E7 : \"USPTOMiscellaneous\",\n\t\t0x1000 : \"RelatedImageFileFormat\",\n\t\t0x1001 : \"RelatedImageWidth\",\n\t\t0x1002 : \"RelatedImageHeight\",\n\t\t0x4746 : \"Rating\",\n\t\t0x4747 : \"XP_DIP_XML\",\n\t\t0x4748 : \"StitchInfo\",\n\t\t0x4749 : \"RatingPercent\",\n\t\t0x800D : \"ImageID\",\n\t\t0x80A3 : \"WangTag1\",\n\t\t0x80A4 : \"WangAnnotation\",\n\t\t0x80A5 : \"WangTag3\",\n\t\t0x80A6 : \"WangTag4\",\n\t\t0x80E3 : \"Matteing\",\n\t\t0x80E4 : \"DataType\",\n\t\t0x80E5 : \"ImageDepth\",\n\t\t0x80E6 : \"TileDepth\",\n\t\t0x827D : \"Model2\",\n\t\t0x828D : \"CFARepeatPatternDim\",\n\t\t0x828E : \"CFAPattern2\",\n\t\t0x828F : \"BatteryLevel\",\n\t\t0x8290 : \"KodakIFD\",\n\t\t0x8298 : \"Copyright\",\n\t\t0x829A : \"ExposureTime\",\n\t\t0x829D : \"FNumber\",\n\t\t0x82A5 : \"MDFileTag\",\n\t\t0x82A6 : \"MDScalePixel\",\n\t\t0x82A7 : \"MDColorTable\",\n\t\t0x82A8 : \"MDLabName\",\n\t\t0x82A9 : \"MDSampleInfo\",\n\t\t0x82AA : \"MDPrepDate\",\n\t\t0x82AB : \"MDPrepTime\",\n\t\t0x82AC : \"MDFileUnits\",\n\t\t0x830E : \"PixelScale\",\n\t\t0x8335 : \"AdventScale\",\n\t\t0x8336 : \"AdventRevision\",\n\t\t0x835C : \"UIC1Tag\",\n\t\t0x835D : \"UIC2Tag\",\n\t\t0x835E : \"UIC3Tag\",\n\t\t0x835F : \"UIC4Tag\",\n\t\t0x83BB : \"IPTC-NAA\",\n\t\t0x847E : \"IntergraphPacketData\",\n\t\t0x847F : \"IntergraphFlagRegisters\",\n\t\t0x8480 : \"IntergraphMatrix\",\n\t\t0x8481 : \"INGRReserved\",\n\t\t0x8482 : \"ModelTiePoint\",\n\t\t0x84E0 : \"Site\",\n\t\t0x84E1 : \"ColorSequence\",\n\t\t0x84E2 : \"IT8Header\",\n\t\t0x84E3 : \"RasterPadding\",\n\t\t0x84E4 : \"BitsPerRunLength\",\n\t\t0x84E5 : \"BitsPerExtendedRunLength\",\n\t\t0x84E6 : \"ColorTable\",\n\t\t0x84E7 : \"ImageColorIndicator\",\n\t\t0x84E8 : \"BackgroundColorIndicator\",\n\t\t0x84E9 : \"ImageColorValue\",\n\t\t0x84EA : \"BackgroundColorValue\",\n\t\t0x84EB : \"PixelIntensityRange\",\n\t\t0x84EC : \"TransparencyIndicator\",\n\t\t0x84ED : \"ColorCharacterization\",\n\t\t0x84EE : \"HCUsage\",\n\t\t0x84EF : \"TrapIndicator\",\n\t\t0x84F0 : \"CMYKEquivalent\",\n\t\t0x8546 : \"SEMInfo\",\n\t\t0x8568 : \"AFCP_IPTC\",\n\t\t0x85B8 : \"PixelMagicJBIGOptions\",\n\t\t0x85D8 : \"ModelTransform\",\n\t\t0x8602 : \"WB_GRGBLevels\",\n\t\t0x8606 : \"LeafData\",\n\t\t0x8649 : \"PhotoshopSettings\",\n\t\t0x8769 : \"ExifOffset\",\n\t\t0x8773 : \"ICC_Profile\",\n\t\t0x877F : \"TIFF_FXExtensions\",\n\t\t0x8780 : \"MultiProfiles\",\n\t\t0x8781 : \"SharedData\",\n\t\t0x8782 : \"T88Options\",\n\t\t0x87AC : \"ImageLayer\",\n\t\t0x87AF : \"GeoTiffDirectory\",\n\t\t0x87B0 : \"GeoTiffDoubleParams\",\n\t\t0x87B1 : \"GeoTiffAsciiParams\",\n\t\t0x8822 : \"ExposureProgram\",\n\t\t0x8824 : \"SpectralSensitivity\",\n\t\t0x8825 : \"GPSInfo\",\n\t\t0x8827 : \"ISO\",\n\t\t0x8828 : \"Opto-ElectricConvFactor\",\n\t\t0x8829 : \"Interlace\",\n\t\t0x882A : \"TimeZoneOffset\",\n\t\t0x882B : \"SelfTimerMode\",\n\t\t0x8830 : \"SensitivityType\",\n\t\t0x8831 : \"StandardOutputSensitivity\",\n\t\t0x8832 : \"RecommendedExposureIndex\",\n\t\t0x8833 : \"ISOSpeed\",\n\t\t0x8834 : \"ISOSpeedLatitudeyyy\",\n\t\t0x8835 : \"ISOSpeedLatitudezzz\",\n\t\t0x885C : \"FaxRecvParams\",\n\t\t0x885D : \"FaxSubAddress\",\n\t\t0x885E : \"FaxRecvTime\",\n\t\t0x888A : \"LeafSubIFD\",\n\t\t0x9000 : \"ExifVersion\",\n\t\t0x9003 : \"DateTimeOriginal\",\n\t\t0x9004 : \"CreateDate\",\n\t\t0x9101 : \"ComponentsConfiguration\",\n\t\t0x9102 : \"CompressedBitsPerPixel\",\n\t\t0x9201 : \"ShutterSpeedValue\",\n\t\t0x9202 : \"ApertureValue\",\n\t\t0x9203 : \"BrightnessValue\",\n\t\t0x9204 : \"ExposureCompensation\",\n\t\t0x9205 : \"MaxApertureValue\",\n\t\t0x9206 : \"SubjectDistance\",\n\t\t0x9207 : \"MeteringMode\",\n\t\t0x9208 : \"LightSource\",\n\t\t0x9209 : \"Flash\",\n\t\t0x920A : \"FocalLength\",\n\t\t0x920B : \"FlashEnergy\",\n\t\t0x920C : \"SpatialFrequencyResponse\",\n\t\t0x920D : \"Noise\",\n\t\t0x920E : \"FocalPlaneXResolution\",\n\t\t0x920F : \"FocalPlaneYResolution\",\n\t\t0x9210 : \"FocalPlaneResolutionUnit\",\n\t\t0x9211 : \"ImageNumber\",\n\t\t0x9212 : \"SecurityClassification\",\n\t\t0x9213 : \"ImageHistory\",\n\t\t0x9214 : \"SubjectArea\",\n\t\t0x9215 : \"ExposureIndex\",\n\t\t0x9216 : \"TIFF-EPStandardID\",\n\t\t0x9217 : \"SensingMethod\",\n\t\t0x923A : \"CIP3DataFile\",\n\t\t0x923B : \"CIP3Sheet\",\n\t\t0x923C : \"CIP3Side\",\n\t\t0x923F : \"StoNits\",\n\t\t0x927C : \"MakerNote\",\n\t\t0x9286 : \"UserComment\",\n\t\t0x9290 : \"SubSecTime\",\n\t\t0x9291 : \"SubSecTimeOriginal\",\n\t\t0x9292 : \"SubSecTimeDigitized\",\n\t\t0x932F : \"MSDocumentText\",\n\t\t0x9330 : \"MSPropertySetStorage\",\n\t\t0x9331 : \"MSDocumentTextPosition\",\n\t\t0x935C : \"ImageSourceData\",\n\t\t0x9C9B : \"XPTitle\",\n\t\t0x9C9C : \"XPComment\",\n\t\t0x9C9D : \"XPAuthor\",\n\t\t0x9C9E : \"XPKeywords\",\n\t\t0x9C9F : \"XPSubject\",\n\t\t0xA000 : \"FlashpixVersion\",\n\t\t0xA001 : \"ColorSpace\",\n\t\t0xA002 : \"ExifImageWidth\",\n\t\t0xA003 : \"ExifImageHeight\",\n\t\t0xA004 : \"RelatedSoundFile\",\n\t\t0xA005 : \"InteropOffset\",\n\t\t0xA20B : \"FlashEnergy\",\n\t\t0xA20C : \"SpatialFrequencyResponse\",\n\t\t0xA20D : \"Noise\",\n\t\t0xA20E : \"FocalPlaneXResolution\",\n\t\t0xA20F : \"FocalPlaneYResolution\",\n\t\t0xA210 : \"FocalPlaneResolutionUnit\",\n\t\t0xA211 : \"ImageNumber\",\n\t\t0xA212 : \"SecurityClassification\",\n\t\t0xA213 : \"ImageHistory\",\n\t\t0xA214 : \"SubjectLocation\",\n\t\t0xA215 : \"ExposureIndex\",\n\t\t0xA216 : \"TIFF-EPStandardID\",\n\t\t0xA217 : \"SensingMethod\",\n\t\t0xA300 : \"FileSource\",\n\t\t0xA301 : \"SceneType\",\n\t\t0xA302 : \"CFAPattern\",\n\t\t0xA401 : \"CustomRendered\",\n\t\t0xA402 : \"ExposureMode\",\n\t\t0xA403 : \"WhiteBalance\",\n\t\t0xA404 : \"DigitalZoomRatio\",\n\t\t0xA405 : \"FocalLengthIn35mmFormat\",\n\t\t0xA406 : \"SceneCaptureType\",\n\t\t0xA407 : \"GainControl\",\n\t\t0xA408 : \"Contrast\",\n\t\t0xA409 : \"Saturation\",\n\t\t0xA40A : \"Sharpness\",\n\t\t0xA40B : \"DeviceSettingDescription\",\n\t\t0xA40C : \"SubjectDistanceRange\",\n\t\t0xA420 : \"ImageUniqueID\",\n\t\t0xA430 : \"OwnerName\",\n\t\t0xA431 : \"SerialNumber\",\n\t\t0xA432 : \"LensInfo\",\n\t\t0xA433 : \"LensMake\",\n\t\t0xA434 : \"LensModel\",\n\t\t0xA435 : \"LensSerialNumber\",\n\t\t0xA480 : \"GDALMetadata\",\n\t\t0xA481 : \"GDALNoData\",\n\t\t0xA500 : \"Gamma\",\n\t\t0xAFC0 : \"ExpandSoftware\",\n\t\t0xAFC1 : \"ExpandLens\",\n\t\t0xAFC2 : \"ExpandFilm\",\n\t\t0xAFC3 : \"ExpandFilterLens\",\n\t\t0xAFC4 : \"ExpandScanner\",\n\t\t0xAFC5 : \"ExpandFlashLamp\",\n\t\t0xBC01 : \"PixelFormat\",\n\t\t0xBC02 : \"Transformation\",\n\t\t0xBC03 : \"Uncompressed\",\n\t\t0xBC04 : \"ImageType\",\n\t\t0xBC80 : \"ImageWidth\",\n\t\t0xBC81 : \"ImageHeight\",\n\t\t0xBC82 : \"WidthResolution\",\n\t\t0xBC83 : \"HeightResolution\",\n\t\t0xBCC0 : \"ImageOffset\",\n\t\t0xBCC1 : \"ImageByteCount\",\n\t\t0xBCC2 : \"AlphaOffset\",\n\t\t0xBCC3 : \"AlphaByteCount\",\n\t\t0xBCC4 : \"ImageDataDiscard\",\n\t\t0xBCC5 : \"AlphaDataDiscard\",\n\t\t0xC427 : \"OceScanjobDesc\",\n\t\t0xC428 : \"OceApplicationSelector\",\n\t\t0xC429 : \"OceIDNumber\",\n\t\t0xC42A : \"OceImageLogic\",\n\t\t0xC44F : \"Annotations\",\n\t\t0xC4A5 : \"PrintIM\",\n\t\t0xC580 : \"USPTOOriginalContentType\",\n\t\t0xC612 : \"DNGVersion\",\n\t\t0xC613 : \"DNGBackwardVersion\",\n\t\t0xC614 : \"UniqueCameraModel\",\n\t\t0xC615 : \"LocalizedCameraModel\",\n\t\t0xC616 : \"CFAPlaneColor\",\n\t\t0xC617 : \"CFALayout\",\n\t\t0xC618 : \"LinearizationTable\",\n\t\t0xC619 : \"BlackLevelRepeatDim\",\n\t\t0xC61A : \"BlackLevel\",\n\t\t0xC61B : \"BlackLevelDeltaH\",\n\t\t0xC61C : \"BlackLevelDeltaV\",\n\t\t0xC61D : \"WhiteLevel\",\n\t\t0xC61E : \"DefaultScale\",\n\t\t0xC61F : \"DefaultCropOrigin\",\n\t\t0xC620 : \"DefaultCropSize\",\n\t\t0xC621 : \"ColorMatrix1\",\n\t\t0xC622 : \"ColorMatrix2\",\n\t\t0xC623 : \"CameraCalibration1\",\n\t\t0xC624 : \"CameraCalibration2\",\n\t\t0xC625 : \"ReductionMatrix1\",\n\t\t0xC626 : \"ReductionMatrix2\",\n\t\t0xC627 : \"AnalogBalance\",\n\t\t0xC628 : \"AsShotNeutral\",\n\t\t0xC629 : \"AsShotWhiteXY\",\n\t\t0xC62A : \"BaselineExposure\",\n\t\t0xC62B : \"BaselineNoise\",\n\t\t0xC62C : \"BaselineSharpness\",\n\t\t0xC62D : \"BayerGreenSplit\",\n\t\t0xC62E : \"LinearResponseLimit\",\n\t\t0xC62F : \"CameraSerialNumber\",\n\t\t0xC630 : \"DNGLensInfo\",\n\t\t0xC631 : \"ChromaBlurRadius\",\n\t\t0xC632 : \"AntiAliasStrength\",\n\t\t0xC633 : \"ShadowScale\",\n\t\t0xC634 : \"DNGPrivateData\",\n\t\t0xC635 : \"MakerNoteSafety\",\n\t\t0xC640 : \"RawImageSegmentation\",\n\t\t0xC65A : \"CalibrationIlluminant1\",\n\t\t0xC65B : \"CalibrationIlluminant2\",\n\t\t0xC65C : \"BestQualityScale\",\n\t\t0xC65D : \"RawDataUniqueID\",\n\t\t0xC660 : \"AliasLayerMetadata\",\n\t\t0xC68B : \"OriginalRawFileName\",\n\t\t0xC68C : \"OriginalRawFileData\",\n\t\t0xC68D : \"ActiveArea\",\n\t\t0xC68E : \"MaskedAreas\",\n\t\t0xC68F : \"AsShotICCProfile\",\n\t\t0xC690 : \"AsShotPreProfileMatrix\",\n\t\t0xC691 : \"CurrentICCProfile\",\n\t\t0xC692 : \"CurrentPreProfileMatrix\",\n\t\t0xC6BF : \"ColorimetricReference\",\n\t\t0xC6D2 : \"PanasonicTitle\",\n\t\t0xC6D3 : \"PanasonicTitle2\",\n\t\t0xC6F3 : \"CameraCalibrationSig\",\n\t\t0xC6F4 : \"ProfileCalibrationSig\",\n\t\t0xC6F5 : \"ProfileIFD\",\n\t\t0xC6F6 : \"AsShotProfileName\",\n\t\t0xC6F7 : \"NoiseReductionApplied\",\n\t\t0xC6F8 : \"ProfileName\",\n\t\t0xC6F9 : \"ProfileHueSatMapDims\",\n\t\t0xC6FA : \"ProfileHueSatMapData1\",\n\t\t0xC6FB : \"ProfileHueSatMapData2\",\n\t\t0xC6FC : \"ProfileToneCurve\",\n\t\t0xC6FD : \"ProfileEmbedPolicy\",\n\t\t0xC6FE : \"ProfileCopyright\",\n\t\t0xC714 : \"ForwardMatrix1\",\n\t\t0xC715 : \"ForwardMatrix2\",\n\t\t0xC716 : \"PreviewApplicationName\",\n\t\t0xC717 : \"PreviewApplicationVersion\",\n\t\t0xC718 : \"PreviewSettingsName\",\n\t\t0xC719 : \"PreviewSettingsDigest\",\n\t\t0xC71A : \"PreviewColorSpace\",\n\t\t0xC71B : \"PreviewDateTime\",\n\t\t0xC71C : \"RawImageDigest\",\n\t\t0xC71D : \"OriginalRawFileDigest\",\n\t\t0xC71E : \"SubTileBlockSize\",\n\t\t0xC71F : \"RowInterleaveFactor\",\n\t\t0xC725 : \"ProfileLookTableDims\",\n\t\t0xC726 : \"ProfileLookTableData\",\n\t\t0xC740 : \"OpcodeList1\",\n\t\t0xC741 : \"OpcodeList2\",\n\t\t0xC74E : \"OpcodeList3\",\n\t\t0xC761 : \"NoiseProfile\",\n\t\t0xC763 : \"TimeCodes\",\n\t\t0xC764 : \"FrameRate\",\n\t\t0xC772 : \"TStop\",\n\t\t0xC789 : \"ReelName\",\n\t\t0xC791 : \"OriginalDefaultFinalSize\",\n\t\t0xC792 : \"OriginalBestQualitySize\",\n\t\t0xC793 : \"OriginalDefaultCropSize\",\n\t\t0xC7A1 : \"CameraLabel\",\n\t\t0xC7A3 : \"ProfileHueSatMapEncoding\",\n\t\t0xC7A4 : \"ProfileLookTableEncoding\",\n\t\t0xC7A5 : \"BaselineExposureOffset\",\n\t\t0xC7A6 : \"DefaultBlackRender\",\n\t\t0xC7A7 : \"NewRawImageDigest\",\n\t\t0xC7A8 : \"RawToPreviewGain\",\n\t\t0xC7B5 : \"DefaultUserCrop\",\n\t\t0xEA1C : \"Padding\",\n\t\t0xEA1D : \"OffsetSchema\",\n\t\t0xFDE8 : \"OwnerName\",\n\t\t0xFDE9 : \"SerialNumber\",\n\t\t0xFDEA : \"Lens\",\n\t\t0xFE00 : \"KDC_IFD\",\n\t\t0xFE4C : \"RawFile\",\n\t\t0xFE4D : \"Converter\",\n\t\t0xFE4E : \"WhiteBalance\",\n\t\t0xFE51 : \"Exposure\",\n\t\t0xFE52 : \"Shadows\",\n\t\t0xFE53 : \"Brightness\",\n\t\t0xFE54 : \"Contrast\",\n\t\t0xFE55 : \"Saturation\",\n\t\t0xFE56 : \"Sharpness\",\n\t\t0xFE57 : \"Smoothness\",\n\t\t0xFE58 : \"MoireFilter\"\n\t\t\n\t},\n\tgps : {\t\n\t\t0x0000 : 'GPSVersionID',\n\t\t0x0001 : 'GPSLatitudeRef',\n\t\t0x0002 : 'GPSLatitude',\n\t\t0x0003 : 'GPSLongitudeRef',\n\t\t0x0004 : 'GPSLongitude',\n\t\t0x0005 : 'GPSAltitudeRef',\n\t\t0x0006 : 'GPSAltitude',\n\t\t0x0007 : 'GPSTimeStamp',\n\t\t0x0008 : 'GPSSatellites',\n\t\t0x0009 : 'GPSStatus',\n\t\t0x000A : 'GPSMeasureMode',\n\t\t0x000B : 'GPSDOP',\n\t\t0x000C : 'GPSSpeedRef',\n\t\t0x000D : 'GPSSpeed',\n\t\t0x000E : 'GPSTrackRef',\n\t\t0x000F : 'GPSTrack',\n\t\t0x0010 : 'GPSImgDirectionRef',\n\t\t0x0011 : 'GPSImgDirection',\n\t\t0x0012 : 'GPSMapDatum',\n\t\t0x0013 : 'GPSDestLatitudeRef',\n\t\t0x0014 : 'GPSDestLatitude',\n\t\t0x0015 : 'GPSDestLongitudeRef',\n\t\t0x0016 : 'GPSDestLongitude',\n\t\t0x0017 : 'GPSDestBearingRef',\n\t\t0x0018 : 'GPSDestBearing',\n\t\t0x0019 : 'GPSDestDistanceRef',\n\t\t0x001A : 'GPSDestDistance',\n\t\t0x001B : 'GPSProcessingMethod',\n\t\t0x001C : 'GPSAreaInformation',\n\t\t0x001D : 'GPSDateStamp',\n\t\t0x001E : 'GPSDifferential',\n\t\t0x001F : 'GPSHPositioningError'\n\t}\n};"
  },
  {
    "path": "lib/exif.js",
    "content": "/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */\n\nfunction readExifValue(format, stream) {\n\tswitch(format) {\n\t\tcase 1: return stream.nextUInt8();\n\t\tcase 3: return stream.nextUInt16();\n\t\tcase 4: return stream.nextUInt32();\n\t\tcase 5: return [stream.nextUInt32(), stream.nextUInt32()];\n\t\tcase 6: return stream.nextInt8();\n\t\tcase 8: return stream.nextUInt16();\n\t\tcase 9: return stream.nextUInt32();\n\t\tcase 10: return [stream.nextInt32(), stream.nextInt32()];\n\t\tcase 11: return stream.nextFloat();\n\t\tcase 12: return stream.nextDouble();\n\t\tdefault: throw new Error('Invalid format while decoding: ' + format);\n\t}\n}\n\nfunction getBytesPerComponent(format) {\n\tswitch(format) {\n\t\tcase 1:\n\t\tcase 2:\n\t\tcase 6:\n\t\tcase 7:\n\t\t\treturn 1;\n\t\tcase 3:\n\t\tcase 8:\n\t\t\treturn 2;\n\t\tcase 4:\n\t\tcase 9:\n\t\tcase 11:\n\t\t\treturn 4;\n\t\tcase 5:\n\t\tcase 10:\n\t\tcase 12:\n\t\t\treturn 8;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nfunction readExifTag(tiffMarker, stream) {\n\tvar tagType = stream.nextUInt16(),\n\t\tformat = stream.nextUInt16(),\n\t\tbytesPerComponent = getBytesPerComponent(format),\n\t\tcomponents = stream.nextUInt32(),\n\t\tvalueBytes = bytesPerComponent * components,\n\t\tvalues,\n\t\tvalue,\n\t\tc;\n\n\t/* if the value is bigger then 4 bytes, the value is in the data section of the IFD\n\tand the value present in the tag is the offset starting from the tiff header. So we replace the stream\n\twith a stream that is located at the given offset in the data section. s*/\n\tif(valueBytes > 4) {\n\t\tstream = tiffMarker.openWithOffset(stream.nextUInt32());\n\t}\n\t//we don't want to read strings as arrays\n\tif(format === 2) {\n\t\tvalues = stream.nextString(components);\n\t\t//cut off \\0 characters\n\t\tvar lastNull = values.indexOf('\\0');\n\t\tif(lastNull !== -1) {\n\t\t\tvalues = values.substr(0, lastNull);\n\t\t}\n\t}\n\telse if(format === 7) {\n\t\tvalues = stream.nextBuffer(components);\n\t}\n\telse if(format !== 0) {\n\t\tvalues = [];\n\t\tfor(c = 0; c < components; ++c) {\n\t\t\tvalues.push(readExifValue(format, stream));\n\t\t}\n\t}\n\t//since our stream is a stateful object, we need to skip remaining bytes\n\t//so our offset stays correct\n\tif(valueBytes < 4) {\n\t\tstream.skip(4 - valueBytes);\n\t}\n\n\treturn [tagType, values, format];\n}\n\nfunction readIFDSection(tiffMarker, stream, iterator) {\n\t// make sure we can read nextUint16 byte\n\tif (stream.remainingLength() < 2){\n\t\treturn;\n\t}\n\tvar numberOfEntries = stream.nextUInt16(), tag, i;\n\tfor(i = 0; i < numberOfEntries; ++i) {\n\t\ttag = readExifTag(tiffMarker, stream);\n\t\titerator(tag[0], tag[1], tag[2]);\n\t}\n}\n\nfunction readHeader(stream) {\n\tvar exifHeader = stream.nextString(6);\n\tif(exifHeader !== 'Exif\\0\\0') {\n\t\tthrow new Error('Invalid EXIF header');\n\t}\n\n\tvar tiffMarker = stream.mark();\n\tvar tiffHeader = stream.nextUInt16();\n\tif(tiffHeader === 0x4949) {\n\t\tstream.setBigEndian(false);\n\t} else if(tiffHeader === 0x4D4D) {\n\t\tstream.setBigEndian(true);\n\t} else {\n\t\tthrow new Error('Invalid TIFF header');\n\t}\n\tif(stream.nextUInt16() !== 0x002A) {\n\t\tthrow new Error('Invalid TIFF data');\n\t}\n\treturn tiffMarker;\n}\n\nmodule.exports = {\n\tIFD0: 1,\n\tIFD1: 2,\n\tGPSIFD: 3,\n\tSubIFD: 4,\n\tInteropIFD: 5,\n\tparseTags: function(stream, iterator) {\n\t\tvar tiffMarker;\n\t\ttry {\n\t\t\ttiffMarker = readHeader(stream);\n\t\t} catch(e) {\n\t\t\treturn false;\t//ignore APP1 sections with invalid headers\n\t\t}\n\t\tvar subIfdOffset, gpsOffset, interopOffset;\n\t\tvar ifd0Stream = tiffMarker.openWithOffset(stream.nextUInt32()),\n\t\t\tIFD0 = this.IFD0;\n\t\treadIFDSection(tiffMarker, ifd0Stream, function(tagType, value, format) {\n\t\t\tswitch(tagType) {\n\t\t\t\tcase 0x8825: gpsOffset = value[0]; break;\n\t\t\t\tcase 0x8769: subIfdOffset = value[0]; break;\n\t\t\t\tdefault: iterator(IFD0, tagType, value, format); break;\n\t\t\t}\n\t\t});\n\t\tvar ifd1Offset = ifd0Stream.nextUInt32();\n\t\tif(ifd1Offset !== 0) {\n\t\t\tvar ifd1Stream = tiffMarker.openWithOffset(ifd1Offset);\n\t\t\treadIFDSection(tiffMarker, ifd1Stream, iterator.bind(null, this.IFD1));\n\t\t}\n\n\t\tif(gpsOffset) {\n\t\t\tvar gpsStream = tiffMarker.openWithOffset(gpsOffset);\n\t\t\treadIFDSection(tiffMarker, gpsStream, iterator.bind(null, this.GPSIFD));\n\t\t}\n\n\t\tif(subIfdOffset) {\n\t\t\tvar subIfdStream = tiffMarker.openWithOffset(subIfdOffset), InteropIFD = this.InteropIFD;\n\t\t\treadIFDSection(tiffMarker, subIfdStream, function(tagType, value, format) {\n\t\t\t\tif(tagType === 0xA005) {\n\t\t\t\t\tinteropOffset = value[0];\n\t\t\t\t} else {\n\t\t\t\t\titerator(InteropIFD, tagType, value, format);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif(interopOffset) {\n\t\t\tvar interopStream = tiffMarker.openWithOffset(interopOffset);\n\t\t\treadIFDSection(tiffMarker, interopStream, iterator.bind(null, this.InteropIFD));\n\t\t}\n\t\treturn true;\n\t}\n};\n"
  },
  {
    "path": "lib/jpeg.js",
    "content": "/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */\n\nmodule.exports = {\n\tparseSections: function(stream, iterator) {\n\t\tvar len, markerType;\n\t\tstream.setBigEndian(true);\n\t\t//stop reading the stream at the SOS (Start of Stream) marker,\n\t\t//because its length is not stored in the header so we can't\n\t\t//know where to jump to. The only marker after that is just EOI (End Of Image) anyway\n\t\twhile(stream.remainingLength() > 0 && markerType !== 0xDA) {\n\t\t\tif(stream.nextUInt8() !== 0xFF) {\n\t\t\t\tthrow new Error('Invalid JPEG section offset');\n\t\t\t}\n\t\t\tmarkerType = stream.nextUInt8();\n\t\t\t//don't read size from markers that have no datas\n\t\t\tif((markerType >= 0xD0 && markerType <= 0xD9) || markerType === 0xDA) {\n\t\t\t\tlen = 0;\n\t\t\t} else {\n\t\t\t\tlen = stream.nextUInt16() - 2;\n\t\t\t}\n\t\t\titerator(markerType, stream.branch(0, len));\n\t\t\tstream.skip(len);\n\t\t}\n\t},\n\t//stream should be located after SOF section size and in big endian mode, like passed to parseSections iterator\n\tgetSizeFromSOFSection: function(stream) {\n\t\tstream.skip(1);\n\t\treturn {\n\t\t\theight: stream.nextUInt16(),\n\t\t\twidth: stream.nextUInt16()\n\t\t};\n\t},\n\tgetSectionName: function(markerType) {\n\t\tvar name, index;\n\t\tswitch(markerType) {\n\t\t\tcase 0xD8: name = 'SOI'; break;\n\t\t\tcase 0xC4: name = 'DHT'; break;\n\t\t\tcase 0xDB: name = 'DQT'; break;\n\t\t\tcase 0xDD: name = 'DRI'; break;\n\t\t\tcase 0xDA: name = 'SOS'; break;\n\t\t\tcase 0xFE: name = 'COM'; break;\n\t\t\tcase 0xD9: name = 'EOI'; break;\n\t\t\tdefault:\n\t\t\t\tif(markerType >= 0xE0 && markerType <= 0xEF) {\n\t\t\t\t\tname = 'APP';\n\t\t\t\t\tindex = markerType - 0xE0;\n\t\t\t\t}\n\t\t\t\telse if(markerType >= 0xC0 && markerType <= 0xCF && markerType !== 0xC4 && markerType !== 0xC8 && markerType !== 0xCC) {\n\t\t\t\t\tname = 'SOF';\n\t\t\t\t\tindex = markerType - 0xC0;\n\t\t\t\t}\n\t\t\t\telse if(markerType >= 0xD0 && markerType <= 0xD7) {\n\t\t\t\t\tname = 'RST';\n\t\t\t\t\tindex = markerType - 0xD0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t\tvar nameStruct = {\n\t\t\tname: name\n\t\t};\n\t\tif(typeof index === 'number') {\n\t\t\tnameStruct.index = index;\n\t\t}\n\t\treturn nameStruct;\n\t}\n};"
  },
  {
    "path": "lib/parser.js",
    "content": "/*jslint browser: true, devel: true, bitwise: false, debug: true, eqeq: false, es5: true, evil: false, forin: false, newcap: false, nomen: true, plusplus: true, regexp: false, unparam: false, sloppy: true, stupid: false, sub: false, todo: true, vars: true, white: true */\n\nvar jpeg = require('./jpeg'),\n\texif = require('./exif'),\n\tsimplify = require('./simplify');\n\nfunction ExifResult(startMarker, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset) {\n\tthis.startMarker = startMarker;\n\tthis.tags = tags;\n\tthis.imageSize = imageSize;\n\tthis.thumbnailOffset = thumbnailOffset;\n\tthis.thumbnailLength = thumbnailLength;\n\tthis.thumbnailType = thumbnailType;\n\tthis.app1Offset = app1Offset;\n}\n\nExifResult.prototype = {\n\thasThumbnail: function(mime) {\n\t\tif(!this.thumbnailOffset || !this.thumbnailLength) {\n\t\t\treturn false;\n\t\t}\n\t\tif(typeof mime !== 'string') {\n\t\t\treturn true;\n\t\t}\n\t\tif(mime.toLowerCase().trim() === 'image/jpeg') {\n\t\t\treturn this.thumbnailType === 6;\n\t\t}\n\t\tif(mime.toLowerCase().trim() === 'image/tiff') {\n\t\t\treturn this.thumbnailType === 1;\n\t\t}\n\t\treturn false;\n\t},\n\tgetThumbnailOffset: function() {\n\t\treturn this.app1Offset + 6 + this.thumbnailOffset;\n\t},\n\tgetThumbnailLength: function() {\n\t\treturn this.thumbnailLength;\n\t},\n\tgetThumbnailBuffer: function() {\n\t\treturn this._getThumbnailStream().nextBuffer(this.thumbnailLength);\n\t},\n\t_getThumbnailStream: function() {\n\t\treturn this.startMarker.openWithOffset(this.getThumbnailOffset());\n\t},\n\tgetImageSize: function() {\n\t\treturn this.imageSize;\n\t},\n\tgetThumbnailSize: function() {\n\t\tvar stream = this._getThumbnailStream(), size;\n\t\tjpeg.parseSections(stream, function(sectionType, sectionStream) {\n\t\t\tif(jpeg.getSectionName(sectionType).name === 'SOF') {\n\t\t\t\tsize = jpeg.getSizeFromSOFSection(sectionStream);\n\t\t\t}\n\t\t});\n\t\treturn size;\n\t}\n};\n\nfunction Parser(stream) {\n\tthis.stream = stream;\n\tthis.flags = {\n\t\treadBinaryTags: false,\n\t\tresolveTagNames: true,\n\t\tsimplifyValues: true,\n\t\timageSize: true,\n\t\thidePointers: true,\n\t\treturnTags: true\n\t};\n}\n\nParser.prototype = {\n\tenableBinaryFields: function(enable) {\n\t\tthis.flags.readBinaryTags = !!enable;\n\t\treturn this;\n\t},\n\tenablePointers: function(enable) {\n\t\tthis.flags.hidePointers = !enable;\n\t\treturn this;\n\t},\n\tenableTagNames: function(enable) {\n\t\tthis.flags.resolveTagNames = !!enable;\n\t\treturn this;\n\t},\n\tenableImageSize: function(enable) {\n\t\tthis.flags.imageSize = !!enable;\n\t\treturn this;\n\t},\n\tenableReturnTags: function(enable) {\n\t\tthis.flags.returnTags = !!enable;\n\t\treturn this;\n\t},\n\tenableSimpleValues: function(enable) {\n\t\tthis.flags.simplifyValues = !!enable;\n\t\treturn this;\n\t},\n\tparse: function() {\n\t\tvar start = this.stream.mark(),\n\t\t\tstream = start.openWithOffset(0),\n\t\t\tflags = this.flags,\n\t\t\ttags,\n\t\t\timageSize,\n\t\t\tthumbnailOffset,\n\t\t\tthumbnailLength,\n\t\t\tthumbnailType,\n\t\t\tapp1Offset,\n\t\t\ttagNames,\n\t\t\tgetTagValue, setTagValue;\n\t\tif(flags.resolveTagNames) {\n\t\t\ttagNames = require('./exif-tags');\n\t\t}\n\t\tif(flags.resolveTagNames) {\n\t\t\ttags = {};\n\t\t\tgetTagValue = function(t) {\n\t\t\t\treturn tags[t.name];\n\t\t\t};\n\t\t\tsetTagValue = function(t, value) {\n\t\t\t\ttags[t.name] = value;\n\t\t\t};\n\t\t} else {\n\t\t\ttags = [];\n\t\t\tgetTagValue = function(t) {\n\t\t\t\tvar i;\n\t\t\t\tfor(i = 0; i < tags.length; ++i) {\n\t\t\t\t\tif(tags[i].type === t.type && tags[i].section === t.section) {\n\t\t\t\t\t\treturn tags.value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tsetTagValue = function(t, value) {\n\t\t\t\tvar i;\n\t\t\t\tfor(i = 0; i < tags.length; ++i) {\n\t\t\t\t\tif(tags[i].type === t.type && tags[i].section === t.section) {\n\t\t\t\t\t\ttags.value = value;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tjpeg.parseSections(stream, function(sectionType, sectionStream) {\n\t\t\tvar validExifHeaders, sectionOffset = sectionStream.offsetFrom(start);\n\t\t\tif(sectionType === 0xE1) {\n\t\t\t\tvalidExifHeaders = exif.parseTags(sectionStream, function(ifdSection, tagType, value, format) {\n\t\t\t\t\t//ignore binary fields if disabled\n\t\t\t\t\tif(!flags.readBinaryTags && format === 7) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif(tagType === 0x0201) {\n\t\t\t\t\t\tthumbnailOffset = value[0];\n\t\t\t\t\t\tif(flags.hidePointers) {return;}\n\t\t\t\t\t} else if(tagType === 0x0202) {\n\t\t\t\t\t\tthumbnailLength = value[0];\n\t\t\t\t\t\tif(flags.hidePointers) {return;}\n\t\t\t\t\t} else if(tagType === 0x0103) {\n\t\t\t\t\t\tthumbnailType = value[0];\n\t\t\t\t\t\tif(flags.hidePointers) {return;}\n\t\t\t\t\t}\n\t\t\t\t\t//if flag is set to not store tags, return here after storing pointers\n\t\t\t\t\tif(!flags.returnTags) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif(flags.simplifyValues) {\n\t\t\t\t\t\tvalue = simplify.simplifyValue(value, format);\n\t\t\t\t\t}\n\t\t\t\t\tif(flags.resolveTagNames) {\n\t\t\t\t\t\tvar sectionTagNames = ifdSection === exif.GPSIFD ? tagNames.gps : tagNames.exif;\n\t\t\t\t\t\tvar name = sectionTagNames[tagType];\n\t\t\t\t\t\tif(!name) {\n\t\t\t\t\t\t\tname = tagNames.exif[tagType];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!tags.hasOwnProperty(name)) {\n\t\t\t\t\t\t\ttags[name] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttags.push({\n\t\t\t\t\t\t\tsection: ifdSection,\n\t\t\t\t\t\t\ttype: tagType,\n\t\t\t\t\t\t\tvalue: value\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif(validExifHeaders) {\n\t\t\t\t\tapp1Offset = sectionOffset;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if(flags.imageSize && jpeg.getSectionName(sectionType).name === 'SOF') {\n\t\t\t\timageSize = jpeg.getSizeFromSOFSection(sectionStream);\n\t\t\t}\n\t\t});\n\n\t\tif(flags.simplifyValues) {\n\t\t\tsimplify.castDegreeValues(getTagValue, setTagValue);\n\t\t\tsimplify.castDateValues(getTagValue, setTagValue);\n\t\t}\n\n\t\treturn new ExifResult(start, tags, imageSize, thumbnailOffset, thumbnailLength, thumbnailType, app1Offset);\n\t}\n};\n\n\n\nmodule.exports = Parser;\n"
  },
  {
    "path": "lib/simplify.js",
    "content": "var exif = require('./exif');\nvar date = require('./date');\n\nvar degreeTags = [{\n\tsection: exif.GPSIFD,\n\ttype: 0x0002,\n\tname: 'GPSLatitude',\n\trefType: 0x0001,\n\trefName: 'GPSLatitudeRef',\n\tposVal: 'N'\n},\n{\n\tsection: exif.GPSIFD,\n\ttype: 0x0004,\n\tname: 'GPSLongitude',\n\trefType: 0x0003,\n\trefName: 'GPSLongitudeRef',\n\tposVal: 'E'\n}];\nvar dateTags = [{\n\tsection: exif.SubIFD,\n\ttype: 0x0132,\n\tname: 'ModifyDate'\n},\n{\n\tsection: exif.SubIFD,\n\ttype: 0x9003,\n\tname: 'DateTimeOriginal'\n},\n{\n\tsection: exif.SubIFD,\n\ttype: 0x9004,\n\tname: 'CreateDate'\n},\n{\n\tsection: exif.SubIFD,\n\ttype: 0x0132,\n\tname : 'ModifyDate',\n}];\n\nmodule.exports = {\n\tcastDegreeValues: function(getTagValue, setTagValue) {\n\t\tdegreeTags.forEach(function(t) {\n\t\t\tvar degreeVal = getTagValue(t);\n\t\t\tif(degreeVal) {\n\t\t\t\tvar degreeRef = getTagValue({section: t.section, type: t.refType, name: t.refName});\n\t\t\t\tvar degreeNumRef = degreeRef === t.posVal ? 1 : -1;\n\t\t\t\tvar degree = (degreeVal[0] + (degreeVal[1] / 60) + (degreeVal[2] / 3600)) * degreeNumRef;\n\t\t\t\tsetTagValue(t, degree);\n\t\t\t}\n\t\t});\n\t},\n\tcastDateValues: function(getTagValue, setTagValue) {\n\t\tdateTags.forEach(function(t) {\n\t\t\tvar dateStrVal = getTagValue(t);\n\t\t\tif(dateStrVal) {\n\t\t\t\t//some easy checks to determine two common date formats\n\t\t\t\tvar timestamp = date.parseExifDate(dateStrVal);\n\t\t\t\tif(typeof timestamp !== 'undefined') {\n\t\t\t\t\tsetTagValue(t, timestamp);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\tsimplifyValue: function(values, format) {\n\t\tif(Array.isArray(values)) {\n\t\t\tvalues = values.map(function(value) {\n\t\t\t\tif(format === 10 || format === 5) {\n\t\t\t\t\treturn value[0] / value[1];\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t\t});\n\t\t\tif(values.length === 1) {\n\t\t\t\tvalues = values[0];\n\t\t\t}\n\t\t}\n\t\treturn values;\n\t}\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"exif-parser\",\n  \"version\": \"0.1.12\",\n  \"description\": \"A javascript library to extract Exif metadata from images, in node and in the browser.\",\n  \"author\": \"Bruno Windels <bruno.windels@gmail.com>\",\n  \"keywords\": [\n    \"exif\",\n    \"image\",\n    \"jpeg\",\n    \"jpg\",\n    \"tiff\",\n    \"gps\"\n  ],\n  \"main\": \"index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"http://github.com/bwindels/exif-parser.git\"\n  },\n  \"devDependencies\": {\n    \"browserify\": \"^7.0.0\",\n    \"uglify-js\": \"^2.4.15\"\n  }\n}\n"
  },
  {
    "path": "test/expected-exif-tags.json",
    "content": "[\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 271,\n    \"value\": \"Nokia\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 272,\n    \"value\": \"Lumia 820\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 274,\n    \"value\": [\n      1\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 282,\n    \"value\": [\n      [\n        72,\n        1\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 283,\n    \"value\": [\n      [\n        72,\n        1\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 296,\n    \"value\": [\n      2\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 305,\n    \"value\": \"Windows Phone\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 531,\n    \"value\": [\n      1\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 1,\n    \"tagType\": 59932,\n    \"value\": \"b:2060\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 259,\n    \"value\": [\n      6\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 282,\n    \"value\": [\n      [\n        72,\n        1\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 283,\n    \"value\": [\n      [\n        72,\n        1\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 296,\n    \"value\": [\n      2\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 513,\n    \"value\": [\n      12210\n    ],\n    \"format\": 4\n  },\n  {\n    \"ifdSection\": 2,\n    \"tagType\": 514,\n    \"value\": [\n      18032\n    ],\n    \"format\": 4\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 0,\n    \"value\": [\n      2,\n      2,\n      0,\n      0\n    ],\n    \"format\": 1\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 1,\n    \"value\": \"N\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 2,\n    \"value\": [\n      [\n        55,\n        1\n      ],\n      [\n        2,\n        1\n      ],\n      [\n        19521,\n        1000\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 3,\n    \"value\": \"E\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 4,\n    \"value\": [\n      [\n        8,\n        1\n      ],\n      [\n        27,\n        1\n      ],\n      [\n        25886,\n        1000\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 5,\n    \"value\": [\n      0\n    ],\n    \"format\": 1\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 6,\n    \"value\": [\n      [\n        10000,\n        200000\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 10,\n    \"value\": \"3\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 11,\n    \"value\": [\n      [\n        10000,\n        80000\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 3,\n    \"tagType\": 59932,\n    \"value\": \"b:2060\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 33434,\n    \"value\": [\n      [\n        3139,\n        1000000\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 33437,\n    \"value\": [\n      [\n        220,\n        100\n      ]\n    ],\n    \"format\": 5\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 34855,\n    \"value\": [\n      100\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 36864,\n    \"value\": \"b:4\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 36867,\n    \"value\": \"2013:05:10 15:21:35\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 36868,\n    \"value\": \"2013:05:10 15:21:35\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 37121,\n    \"value\": \"b:4\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 37380,\n    \"value\": [\n      [\n        0,\n        6\n      ]\n    ],\n    \"format\": 10\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 37385,\n    \"value\": [\n      24\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 37500,\n    \"value\": \"b:5243\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 40960,\n    \"value\": \"b:4\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 40961,\n    \"value\": [\n      1\n    ],\n    \"format\": 3\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 40962,\n    \"value\": [\n      3552\n    ],\n    \"format\": 4\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 40963,\n    \"value\": [\n      2000\n    ],\n    \"format\": 4\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 59932,\n    \"value\": \"b:2060\",\n    \"format\": 7\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 59933,\n    \"value\": [\n      12\n    ],\n    \"format\": 9\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 1,\n    \"value\": \"R98\",\n    \"format\": 2\n  },\n  {\n    \"ifdSection\": 5,\n    \"tagType\": 2,\n    \"value\": \"b:4\",\n    \"format\": 7\n  }\n]"
  },
  {
    "path": "test/test-date.js",
    "content": "var date = require('../lib/date');\n\nvar minutes = 60;\nvar hours = minutes * 60;\nvar days = hours * 24;\nvar years = days * 365;\nvar leapYears = days * 366;\n\nmodule.exports = {\n\t'test parse unix epoch without timezone': function(test) {\n\t\tvar dateStr = '1970:01:01 00:00:00';\n\t\tvar timestamp = date.parseDateWithSpecFormat(dateStr);\n\t\ttest.strictEqual(timestamp, 0);\n\t\ttest.done();\n\t},\n\t'test parse given date without timezone': function(test) {\n\t\tvar dateStr = '1990:02:14 14:30:14';\n\t\tvar timestamp = date.parseDateWithSpecFormat(dateStr);\n\t\t//between 1970 and 1990 there were 5 leap years: 1972, 1976, 1980, 1984, 1988\n\t\tvar expectedTimestamp = (15 * years) + (5 * leapYears) +\n\t\t\t((31 + 13) * days) + (14 * hours) + (30 * minutes) + 14;\n\t\ttest.strictEqual(timestamp, expectedTimestamp);\n\t\ttest.done();\n\t},\n\t'test parse invalid date without timezone should not return anything': function(test) {\n\t\tvar dateStr = '1990:AA:14 14:30:14';\n\t\tvar timestamp = date.parseDateWithSpecFormat(dateStr);\n\t\ttest.strictEqual(timestamp, undefined);\n\t\ttest.done();\n\t},\n\t'test parse given date with timezone': function(test) {\n\t\tvar dateStr = '2004-09-04T23:39:06-08:00';\n\t\tvar timestamp = date.parseDateWithTimezoneFormat(dateStr);\n\t\tvar yearsFromEpoch = 2004 - 1970;\n\t\t//1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000\n\t\tvar leapYearsCount = 8;\n\t\t//2004 is a leap year as well, hence 29 days for february\n\t\tvar dayCount = 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 3;\n\t\tvar expectedTimestamp = (yearsFromEpoch - leapYearsCount) * years +\n\t\t\tleapYearsCount * leapYears +\n\t\t\tdayCount * days +\n\t\t\t23 * hours +\n\t\t\t39 * minutes +\n\t\t\t6 +\n\t\t\t8 * hours;\t//for timezone\n\t\ttest.strictEqual(timestamp, expectedTimestamp);\n\t\ttest.done();\n\t},\n\t'test parse invalid date with timezone': function(test) {\n\t\tvar dateStr = '2004-09-04T23:39:06A08:00';\n\t\tvar timestamp = date.parseDateWithTimezoneFormat(dateStr);\n\t\ttest.strictEqual(timestamp, undefined);\n\t\ttest.done();\n\t},\n\t'test parseExifDate': function(test) {\n\t\ttest.strictEqual(date.parseExifDate('1970:01:01 00:00:00'), 0);\n\t\ttest.strictEqual(date.parseExifDate('1970-01-01T00:00:00-01:00'), 3600);\n\t\ttest.done();\n\t}\n}"
  },
  {
    "path": "test/test-exif.js",
    "content": "var testCase = require('nodeunit').testCase;\nvar exif = require('../lib/exif.js');\nvar BufferStream = require('../lib/bufferstream.js');\nvar buf = require('fs').readFileSync(__dirname + '/starfish.jpg');\n\nmodule.exports = testCase({\n\t\"test parseTags\": function(test) {\n\t\tvar expectedTags = require('./expected-exif-tags.json');\n\t\tvar index = 0;\n\t\texif.parseTags(new BufferStream(buf, 24, 23960), function(ifdSection, tagType, value, format) {\n\t\t\tvar t = expectedTags[index];\n\t\t\ttest.strictEqual(t.ifdSection, ifdSection);\n\t\t\ttest.strictEqual(t.tagType, tagType);\n\t\t\ttest.strictEqual(t.format, format);\n\t\t\tif(typeof t.value === 'string' && t.value.indexOf('b:') === 0) {\n\t\t\t\ttest.ok(Buffer.isBuffer(value));\n\t\t\t\ttest.strictEqual(parseInt(t.value.substr(2), 10), value.length);\n\t\t\t} else {\n\t\t\t\ttest.deepEqual(t.value, value);\n\t\t\t}\n\t\t\t++index;\n\t\t});\n\t\ttest.strictEqual(index, expectedTags.length, 'all tags should be passed to the iterator');\n\t\ttest.done();\t\n\t}\n});"
  },
  {
    "path": "test/test-jpeg.js",
    "content": "var testCase = require('nodeunit').testCase;\nvar jpeg = require('../lib/jpeg.js');\nvar BufferStream = require('../lib/bufferstream.js');\nvar buf = require('fs').readFileSync(__dirname + '/test.jpg');\n\nmodule.exports = testCase({\n\t\"test parseSections\": function(test) {\n\t\tvar expectedSections = [\n\t\t\t{ type: 216, offset: 2, len: 0 },\n\t\t\t{ type: 224, offset: 6, len: 14 },\n\t\t\t{ type: 226, offset: 24, len: 3158 },\n\t\t\t{ type: 225, offset: 3186, len: 200 },\n\t\t\t{ type: 225, offset: 3390, len: 374 },\n\t\t\t{ type: 219, offset: 3768, len: 65 },\n\t\t\t{ type: 219, offset: 3837, len: 65 },\n\t\t\t{ type: 192, offset: 3906, len: 15 },\n\t\t\t{ type: 196, offset: 3925, len: 29 },\n\t\t\t{ type: 196, offset: 3958, len: 179 },\n\t\t\t{ type: 196, offset: 4141, len: 29 },\n\t\t\t{ type: 196, offset: 4174, len: 179 },\n\t\t\t{ type: 218, offset: 4355, len: 0 }\n\t\t];\n\t\tvar index = 0;\n\t\tvar jpegStream = new BufferStream(buf), start = jpegStream.mark();\n\t\tjpeg.parseSections(jpegStream, function(type, sectionStream) {\n\t\t\ttest.strictEqual(type, expectedSections[index].type);\n\t\t\ttest.strictEqual(sectionStream.offsetFrom(start), expectedSections[index].offset);\n\t\t\ttest.strictEqual(sectionStream.remainingLength(), expectedSections[index].len);\n\t\t\t++index;\n\t\t});\n\t\ttest.strictEqual(index, expectedSections.length, 'all sections should be passed to the iterator');\n\t\ttest.done();\t\n\t},\n\t\"test getSizeFromSOFSection\": function(test) {\n\t\tvar size = jpeg.getSizeFromSOFSection(new BufferStream(buf, 3906, 15, true));\n\t\ttest.strictEqual(size.width, 2);\n\t\ttest.strictEqual(size.height, 1);\n\t\ttest.done();\n\t},\n\t\"test getSectionName\": function(test) {\n\t\ttest.deepEqual({name: 'SOI'}, jpeg.getSectionName(0xD8));\n\t\ttest.deepEqual({name: 'APP', index: 15}, jpeg.getSectionName(0xEF));\n\t\ttest.deepEqual({name: 'DHT'}, jpeg.getSectionName(0xC4));\n\t\ttest.done();\n\t}\n});"
  },
  {
    "path": "test/test-simplify.js",
    "content": "var simplify = require('../lib/simplify');\n\nmodule.exports = {\n\t'test castDateValues': function(test) {\n\t\tvar values = {\n\t\t\t'DateTimeOriginal': '1970:01:01 00:00:00',\n\t\t\t'CreateDate': '1970-01-01T00:00:00-05:00',\n\t\t\t'ModifyDate': '1970-01-01T00:00:00-05:00'\n\t\t};\n\t\tvar setValues = {};\n\t\tfunction getTagValue(tag) {\n\t\t\treturn values[tag.name];\n\t\t}\n\t\tfunction setTagValue(tag, value) {\n\t\t\tsetValues[tag.name] = value;\n\t\t}\n\t\tsimplify.castDateValues(getTagValue, setTagValue);\n\t\ttest.strictEqual(Object.keys(setValues).length, 3);\n\t\ttest.strictEqual(setValues.DateTimeOriginal, 0);\n\t\ttest.strictEqual(setValues.CreateDate, 5 * 3600);\n\t\ttest.strictEqual(setValues.ModifyDate, 5 * 3600);\n\t\ttest.done();\n\t}\n}"
  }
]