├── LICENSE
├── README.md
├── bower.json
├── demo.html
├── demo.js
├── demo.py
├── jpegmeta.js
└── package.json
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009-2013 Ben Leslie
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | = JPEG Meta Data in JavaScript =
2 |
3 | To try it out run `python demo.py` and go to http://localhost:8000
4 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsjpegmeta",
3 | "description": "Get exif info from Jpeg files",
4 | "version": "1.0.1",
5 | "main": "jpegmeta.js",
6 | "ignore": [
7 | "**/.*",
8 | "demo.html",
9 | "demo.js",
10 | "demo.py",
11 | "package.json"
12 | ],
13 | "dependencies": {
14 |
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JsJpegMeta Demo
7 |
8 |
9 |
10 |
11 |
12 | JsJpegMeta Demo
13 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | /* Imports */
3 | var $j = this.JpegMeta.JpegFile;
4 |
5 | /* Implementation */
6 | function $(x) {
7 | return document.getElementById(x);
8 | }
9 |
10 | function dragEnterHandler(e) {
11 | e.preventDefault();
12 | }
13 |
14 | function dragOverHandler(e) {
15 | e.preventDefault();
16 | }
17 |
18 | function dropHandler(e) {
19 | e.preventDefault();
20 | loadFiles(e.dataTransfer.files);
21 | }
22 |
23 | function strComp(a, b) {
24 | return (a > b) ? 1 : (a == b) ? 0 : -1;
25 | }
26 |
27 | function loadFiles(files) {
28 | var dataurl_reader = new FileReader();
29 |
30 | function display(data, filename) {
31 | var jpeg = new $j(data, filename);
32 | var groups = new Array;
33 | var props;
34 | var group;
35 | var prop;
36 | $("status").innerHTML += "JPEG File " + jpeg + "
";
37 |
38 | if (jpeg.gps && jpeg.gps.longitude) {
39 | $("status").innerHTML += "Locate on map (opens a new window)
";
40 | }
41 |
42 | for (group in jpeg.metaGroups) {
43 | if (jpeg.metaGroups.hasOwnProperty(group)) {
44 | groups.push(jpeg.metaGroups[group]);
45 | }
46 | }
47 |
48 | groups.sort(function (a, b) {
49 | if (a.description == "General") {
50 | return -1;
51 | } else if (b.description == "General") {
52 | return 1;
53 | } else {
54 | return strComp(a.description, b.description);
55 | }
56 | });
57 |
58 | for (var i = 0; i < groups.length; i++) {
59 | group = groups[i];
60 | props = new Array();
61 | $("status").innerHTML += "" + group.description + "
";
62 | for (prop in group.metaProps) {
63 | if (group.metaProps.hasOwnProperty(prop)) {
64 | props.push(group.metaProps[prop]);
65 | }
66 | }
67 | props.sort(function (a, b) { return strComp(a.description, b.description); });
68 | for (var j = 0; j < props.length; j++) {
69 | prop = props[j];
70 | $("status").innerHTML += "" + prop.description + ": " + prop.value + "
";
71 | }
72 | }
73 | }
74 |
75 | dataurl_reader.onloadend = function() {
76 | $("img").src = this.result;
77 | display(atob(this.result.replace(/^.*?,/,'')), files[0]);
78 | }
79 |
80 | $("status").innerHTML = "";
81 | $("img").src = "";
82 | dataurl_reader.readAsDataURL(files[0]);
83 | $("form").reset();
84 | }
85 |
86 | window.onload = function() {
87 | var drop_el = $("dropbox");
88 | var file_el = $("fileWidget");
89 | drop_el.addEventListener("dragenter", dragEnterHandler, false);
90 | drop_el.addEventListener("dragover", dragOverHandler, true);
91 | drop_el.addEventListener("drop", dropHandler, true);
92 | file_el.addEventListener("change", function() { loadFiles(this.files); }, true);
93 | }
94 | /* No exports */
95 | })();
96 |
--------------------------------------------------------------------------------
/demo.py:
--------------------------------------------------------------------------------
1 | import SimpleHTTPServer
2 | import SocketServer
3 |
4 | # minimal web server. serves files relative to the
5 | # current directory.
6 | def main():
7 | import sys
8 | try:
9 | port = int(sys.argv[1])
10 | except:
11 | port = 8000
12 |
13 | Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
14 | httpd = SocketServer.TCPServer(("", port), Handler)
15 |
16 | print "serving at port", port
17 | httpd.serve_forever()
18 |
19 | if __name__ == "__main__":
20 | main()
21 |
--------------------------------------------------------------------------------
/jpegmeta.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2009 Ben Leslie
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 |
23 | /*
24 | This JavaScript library is used to parse meta-data from files
25 | with mime-type image/jpeg.
26 |
27 | Include it with something like:
28 |
29 |
30 |
31 | This adds a single 'module' object called 'JpegMeta' to the global
32 | namespace.
33 |
34 | Public Functions
35 | ----------------
36 | JpegMeta.parseNum - parse unsigned integers from binary data
37 | JpegMeta.parseSnum - parse signed integers from binary data
38 |
39 | Public Classes
40 | --------------
41 | JpegMeta.Rational - A rational number class
42 | JpegMeta.JfifSegment
43 | JpegMeta.ExifSegment
44 | JpegMeta.JpegFile - Primary class for Javascript parsing
45 | */
46 |
47 | if (this.JpegMeta) {
48 | throw Error("Library included multiple times");
49 | }
50 |
51 | var JpegMeta = {};
52 |
53 | JpegMeta.stringIsClean = function stringIsClean(str) {
54 | for (var i = 0; i < str.length; i++) {
55 | if (str.charCodeAt(i) < 0x20) {
56 | return false;
57 | }
58 | }
59 | return true;
60 | }
61 |
62 | /*
63 | parse an unsigned number of size bytes at offset in some binary string data.
64 | If endian
65 | is "<" parse the data as little endian, if endian
66 | is ">" parse as big-endian.
67 | */
68 | JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
69 | var i;
70 | var ret;
71 | var big_endian = (endian === ">");
72 | if (offset === undefined) offset = 0;
73 | if (size === undefined) size = data.length - offset;
74 | for (big_endian ? i = offset : i = offset + size - 1;
75 | big_endian ? i < offset + size : i >= offset;
76 | big_endian ? i++ : i--) {
77 | ret <<= 8;
78 | ret += data.charCodeAt(i);
79 | }
80 | return ret;
81 | };
82 |
83 | /*
84 | parse an signed number of size bytes at offset in some binary string data.
85 | If endian
86 | is "<" parse the data as little endian, if endian
87 | is ">" parse as big-endian.
88 | */
89 | JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
90 | var i;
91 | var ret;
92 | var neg;
93 | var big_endian = (endian === ">");
94 | if (offset === undefined) offset = 0;
95 | if (size === undefined) size = data.length - offset;
96 | for (big_endian ? i = offset : i = offset + size - 1;
97 | big_endian ? i < offset + size : i >= offset;
98 | big_endian ? i++ : i--) {
99 | if (neg === undefined) {
100 | /* Negative if top bit is set */
101 | neg = (data.charCodeAt(i) & 0x80) === 0x80;
102 | }
103 | ret <<= 8;
104 | /* If it is negative we invert the bits */
105 | ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
106 | }
107 | if (neg) {
108 | /* If it is negative we do two's complement */
109 | ret += 1;
110 | ret *= -1;
111 | }
112 | return ret;
113 | };
114 |
115 | /* Rational number class */
116 | JpegMeta.Rational = function Rational(num, den)
117 | {
118 | this.num = num;
119 | this.den = den || 1;
120 | return this;
121 | };
122 |
123 | /* Rational number methods */
124 | JpegMeta.Rational.prototype.toString = function toString() {
125 | if (this.num === 0) {
126 | return "" + this.num;
127 | }
128 | if (this.den === 1) {
129 | return "" + this.num;
130 | }
131 | if (this.num === 1) {
132 | return this.num + " / " + this.den;
133 | }
134 | return this.num / this.den; // + "/" + this.den;
135 | };
136 |
137 | JpegMeta.Rational.prototype.asFloat = function asFloat() {
138 | return this.num / this.den;
139 | };
140 |
141 |
142 | /* MetaGroup class */
143 | JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
144 | this.fieldName = fieldName;
145 | this.description = description;
146 | this.metaProps = {};
147 | return this;
148 | };
149 |
150 | JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
151 | var property = new JpegMeta.MetaProp(fieldName, description, value);
152 | if (typeof this[property.fieldName] == 'undefined') {
153 | this[property.fieldName] = property;
154 | this.metaProps[property.fieldName] = property;
155 | } else {
156 | var currentProperty = this[property.fieldName];
157 | var currentMetaProperty = this.metaProps[property.fieldName];
158 | if (! Array.isArray(currentProperty)) {
159 | this[property.fieldName] = new Array();
160 | this[property.fieldName].push(currentProperty);
161 | this[property.fieldName].push(property);
162 | this.metaProps[property.fieldName] = new Array();
163 | this.metaProps[property.fieldName].push(currentProperty);
164 | this.metaProps[property.fieldName].push(property);
165 | } else {
166 | currentProperty.push(property);
167 | currentMetaProperty.push(property);
168 | }
169 | }
170 | };
171 |
172 | JpegMeta.MetaGroup.prototype.toString = function toString() {
173 | return "[MetaGroup " + this.description + "]";
174 | };
175 |
176 |
177 | /* MetaProp class */
178 | JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
179 | this.fieldName = fieldName;
180 | this.description = description;
181 | this.value = value;
182 | return this;
183 | };
184 |
185 | JpegMeta.MetaProp.prototype.toString = function toString() {
186 | return "" + this.value;
187 | };
188 |
189 |
190 |
191 | /* JpegFile class */
192 | this.JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
193 | /* Change this to EOI if we want to parse. */
194 | var break_segment = this._SOS;
195 |
196 | this.metaGroups = {};
197 | this._binary_data = binary_data;
198 | this.filename = filename;
199 |
200 | /* Go through and parse. */
201 | var pos = 0;
202 | var pos_start_of_segment = 0;
203 | var delim;
204 | var mark;
205 | var _mark;
206 | var segsize;
207 | var headersize;
208 | var mark_code;
209 | var mark_fn;
210 |
211 | /* Check to see if this looks like a JPEG file */
212 | if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
213 | throw new Error("Doesn't look like a JPEG file. First two bytes are " +
214 | this._binary_data.charCodeAt(0) + "," +
215 | this._binary_data.charCodeAt(1) + ".");
216 | }
217 |
218 | pos += 2;
219 |
220 | while (pos < this._binary_data.length) {
221 | delim = this._binary_data.charCodeAt(pos++);
222 | mark = this._binary_data.charCodeAt(pos++);
223 |
224 | pos_start_of_segment = pos;
225 |
226 | if (delim != this._DELIM) {
227 | break;
228 | }
229 |
230 | if (mark === break_segment) {
231 | break;
232 | }
233 |
234 | headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
235 |
236 | /* Find the end */
237 | pos += headersize;
238 | while (pos < this._binary_data.length) {
239 | delim = this._binary_data.charCodeAt(pos++);
240 | if (delim == this._DELIM) {
241 | _mark = this._binary_data.charCodeAt(pos++);
242 | if (_mark != 0x0) {
243 | pos -= 2;
244 | break;
245 | }
246 | }
247 | }
248 |
249 | segsize = pos - pos_start_of_segment;
250 |
251 | if (this._markers[mark]) {
252 | mark_code = this._markers[mark][0];
253 | mark_fn = this._markers[mark][1];
254 | } else {
255 | mark_code = "UNKN";
256 | mark_fn = undefined;
257 | }
258 |
259 | if (mark_fn) {
260 | this[mark_fn](mark, pos_start_of_segment + 2, segsize - 2);
261 | }
262 |
263 | }
264 |
265 | if (this.general === undefined) {
266 | throw Error("Invalid JPEG file.");
267 | }
268 |
269 | return this;
270 | };
271 |
272 | this.JpegMeta.JpegFile.prototype.toString = function () {
273 | return "[JpegFile " + this.filename + " " +
274 | this.general.type + " " +
275 | this.general.pixelWidth + "x" +
276 | this.general.pixelHeight +
277 | " Depth: " + this.general.depth + "]";
278 | };
279 |
280 | /* Some useful constants */
281 | this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
282 | this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
283 | this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
284 | this.JpegMeta.JpegFile.prototype._SOS = 0xda;
285 |
286 | this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
287 | if (this.general !== undefined) {
288 | throw Error("Unexpected multiple-frame image");
289 | }
290 |
291 | this._addMetaGroup("general", "General");
292 | this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
293 | this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
294 | this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
295 | this.general._addProperty("type", "Type", this._markers[mark][2]);
296 | };
297 |
298 | this.JpegMeta.JpegFile.prototype._commentHandler = function _commentHandler (mark, pos, size) {
299 |
300 | var _pos, result;
301 | pos++;
302 | size--;
303 | _pos = pos;
304 | result = "";
305 |
306 | while(_pos < pos+size) {
307 | result += String.fromCharCode(this._binary_data.charCodeAt(_pos));
308 | _pos++;
309 | }
310 |
311 | this._addMetaGroup("comment", "Comment");
312 | this.comment._addProperty("comment", "Comment", result);
313 | };
314 |
315 |
316 | /* JFIF idents */
317 | this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
318 | this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
319 |
320 | /* EXIF idents */
321 | this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
322 |
323 | /* TIFF types */
324 | this.JpegMeta.JpegFile.prototype._types = {
325 | /* The format is identifier : ["type name", type_size_in_bytes ] */
326 | 1 : ["BYTE", 1],
327 | 2 : ["ASCII", 1],
328 | 3 : ["SHORT", 2],
329 | 4 : ["LONG", 4],
330 | 5 : ["RATIONAL", 8],
331 | 6 : ["SBYTE", 1],
332 | 7 : ["UNDEFINED", 1],
333 | 8 : ["SSHORT", 2],
334 | 9 : ["SLONG", 4],
335 | 10 : ["SRATIONAL", 8],
336 | 11 : ["FLOAT", 4],
337 | 12 : ["DOUBLE", 8]
338 | };
339 |
340 | this.JpegMeta.JpegFile.prototype._tifftags = {
341 | /* A. Tags relating to image data structure */
342 | 256 : ["Image width", "ImageWidth"],
343 | 257 : ["Image height", "ImageLength"],
344 | 258 : ["Number of bits per component", "BitsPerSample"],
345 | 259 : ["Compression scheme", "Compression",
346 | {1 : "uncompressed", 6 : "JPEG compression" }],
347 | 262 : ["Pixel composition", "PhotmetricInerpretation",
348 | {2 : "RGB", 6 : "YCbCr"}],
349 | 274 : ["Orientation of image", "Orientation",
350 | /* FIXME: Check the mirror-image / reverse encoding and rotation */
351 | {1 : "Normal", 2 : "Reverse?",
352 | 3 : "Upside-down", 4 : "Upside-down Reverse",
353 | 5 : "90 degree CW", 6 : "90 degree CW reverse",
354 | 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
355 | 277 : ["Number of components", "SamplesPerPixel"],
356 | 284 : ["Image data arrangement", "PlanarConfiguration",
357 | {1 : "chunky format", 2 : "planar format"}],
358 | 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
359 | 531 : ["Y and C positioning", "YCbCrPositioning",
360 | {1 : "centered", 2 : "co-sited"}],
361 | 282 : ["X Resolution", "XResolution"],
362 | 283 : ["Y Resolution", "YResolution"],
363 | 296 : ["Resolution Unit", "ResolutionUnit",
364 | {2 : "inches", 3 : "centimeters"}],
365 | /* B. Tags realting to recording offset */
366 | 273 : ["Image data location", "StripOffsets"],
367 | 278 : ["Number of rows per strip", "RowsPerStrip"],
368 | 279 : ["Bytes per compressed strip", "StripByteCounts"],
369 | 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
370 | 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
371 | /* C. Tags relating to image data characteristics */
372 | 301 : ["Transfer function", "TransferFunction"],
373 | 318 : ["White point chromaticity", "WhitePoint"],
374 | 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
375 | 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
376 | 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
377 | /* D. Other tags */
378 | 306 : ["Date and time", "DateTime"],
379 | 270 : ["Image title", "ImageDescription"],
380 | 271 : ["Make", "Make"],
381 | 272 : ["Model", "Model"],
382 | 305 : ["Software", "Software"],
383 | 315 : ["Person who created the image", "Artist"],
384 | 316 : ["Host Computer", "HostComputer"],
385 | 33432 : ["Copyright holder", "Copyright"],
386 |
387 | 34665 : ["Exif tag", "ExifIfdPointer"],
388 | 34853 : ["GPS tag", "GPSInfoIfdPointer"]
389 | };
390 |
391 | this.JpegMeta.JpegFile.prototype._exiftags = {
392 | /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
393 | /* A. Tags Relating to Version */
394 | 36864 : ["Exif Version", "ExifVersion"],
395 | 40960 : ["FlashPix Version", "FlashpixVersion"],
396 |
397 | /* B. Tag Relating to Image Data Characteristics */
398 | 40961 : ["Color Space", "ColorSpace"],
399 |
400 | /* C. Tags Relating to Image Configuration */
401 | 37121 : ["Meaning of each component", "ComponentsConfiguration"],
402 | 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
403 | 40962 : ["Pixel X Dimension", "PixelXDimension"],
404 | 40963 : ["Pixel Y Dimension", "PixelYDimension"],
405 |
406 | /* D. Tags Relating to User Information */
407 | 37500 : ["Manufacturer notes", "MakerNote"],
408 | 37510 : ["User comments", "UserComment"],
409 |
410 | /* E. Tag Relating to Related File Information */
411 | 40964 : ["Related audio file", "RelatedSoundFile"],
412 |
413 | /* F. Tags Relating to Date and Time */
414 | 36867 : ["Date Time Original", "DateTimeOriginal"],
415 | 36868 : ["Date Time Digitized", "DateTimeDigitized"],
416 | 37520 : ["DateTime subseconds", "SubSecTime"],
417 | 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
418 | 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
419 |
420 | /* G. Tags Relating to Picture-Taking Conditions */
421 | 33434 : ["Exposure time", "ExposureTime"],
422 | 33437 : ["FNumber", "FNumber"],
423 | 34850 : ["Exposure program", "ExposureProgram"],
424 | 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
425 | 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
426 | 34856 : ["Optoelectric coefficient", "OECF"],
427 | 37377 : ["Shutter Speed", "ShutterSpeedValue"],
428 | 37378 : ["Aperture Value", "ApertureValue"],
429 | 37379 : ["Brightness", "BrightnessValue"],
430 | 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
431 | 37381 : ["Max Aperture Value", "MaxApertureValue"],
432 | 37382 : ["Subject Distance", "SubjectDistance"],
433 | 37383 : ["Metering Mode", "MeteringMode"],
434 | 37384 : ["Light Source", "LightSource"],
435 | 37385 : ["Flash", "Flash"],
436 | 37386 : ["Focal Length", "FocalLength"],
437 | 37396 : ["Subject Area", "SubjectArea"],
438 | 41483 : ["Flash Energy", "FlashEnergy"],
439 | 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
440 | 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
441 | 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
442 | 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
443 | 41492 : ["Subject Location", "SubjectLocation"],
444 | 41493 : ["Exposure Index", "ExposureIndex"],
445 | 41495 : ["Sensing Method", "SensingMethod"],
446 | 41728 : ["File Source", "FileSource"],
447 | 41729 : ["Scene Type", "SceneType"],
448 | 41730 : ["CFA Pattern", "CFAPattern"],
449 | 41985 : ["Custom Rendered", "CustomRendered"],
450 | 41986 : ["Exposure Mode", "Exposure Mode"],
451 | 41987 : ["White Balance", "WhiteBalance"],
452 | 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
453 | 41989 : ["Focal length in 35 mm film", "FocalLengthIn35mmFilm"],
454 | 41990 : ["Scene Capture Type", "SceneCaptureType"],
455 | 41991 : ["Gain Control", "GainControl"],
456 | 41992 : ["Contrast", "Contrast"],
457 | 41993 : ["Saturation", "Saturation"],
458 | 41994 : ["Sharpness", "Sharpness"],
459 | 41995 : ["Device settings description", "DeviceSettingDescription"],
460 | 41996 : ["Subject distance range", "SubjectDistanceRange"],
461 |
462 | /* H. Other Tags */
463 | 42016 : ["Unique image ID", "ImageUniqueID"],
464 |
465 | 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
466 | };
467 |
468 | this.JpegMeta.JpegFile.prototype._gpstags = {
469 | /* A. Tags Relating to GPS */
470 | 0 : ["GPS tag version", "GPSVersionID"],
471 | 1 : ["North or South Latitude", "GPSLatitudeRef"],
472 | 2 : ["Latitude", "GPSLatitude"],
473 | 3 : ["East or West Longitude", "GPSLongitudeRef"],
474 | 4 : ["Longitude", "GPSLongitude"],
475 | 5 : ["Altitude reference", "GPSAltitudeRef"],
476 | 6 : ["Altitude", "GPSAltitude"],
477 | 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
478 | 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
479 | 9 : ["GPS receiver status", "GPSStatus"],
480 | 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
481 | 11 : ["Measurement precision", "GPSDOP"],
482 | 12 : ["Speed unit", "GPSSpeedRef"],
483 | 13 : ["Speed of GPS receiver", "GPSSpeed"],
484 | 14 : ["Reference for direction of movement", "GPSTrackRef"],
485 | 15 : ["Direction of movement", "GPSTrack"],
486 | 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
487 | 17 : ["Direction of image", "GPSImgDirection"],
488 | 18 : ["Geodetic survey data used", "GPSMapDatum"],
489 | 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
490 | 20 : ["Latitude of destination", "GPSDestLatitude"],
491 | 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
492 | 22 : ["Longitude of destination", "GPSDestLongitude"],
493 | 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
494 | 24 : ["Bearing of destination", "GPSDestBearing"],
495 | 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
496 | 26 : ["Distance to destination", "GPSDestDistance"],
497 | 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
498 | 28 : ["Name of GPS area", "GPSAreaInformation"],
499 | 29 : ["GPS Date", "GPSDateStamp"],
500 | 30 : ["GPS differential correction", "GPSDifferential"]
501 | };
502 |
503 | this.JpegMeta.JpegFile.prototype._iptctags = {
504 | 0 : ['Record Version', 'recordVersion'],
505 | 3 : ['Object Type Reference', 'objectType'],
506 | 4 : ['Object Attribute Reference', 'objectAttribute'],
507 | 5 : ['Object Name', 'objectName'],
508 | 7 : ['Edit Status', 'editStatus'],
509 | 8 : ['Editorial Update', 'editorialUpdate'],
510 | 10 : ['Urgency', 'urgency'],
511 | 12 : ['Subject Reference', 'subjectRef'],
512 | 15 : ['Category', 'category'],
513 | 20 : ['Supplemental Category', 'supplCategory'],
514 | 22 : ['Fixture Identifier', 'fixtureID'],
515 | 25 : ['Keywords', 'keywords'],
516 | 26 : ['Content Location Code', 'contentLocCode'],
517 | 27 : ['Content Location Name', 'contentLocName'],
518 | 30 : ['Release Date', 'releaseDate'],
519 | 35 : ['Release Time', 'releaseTime'],
520 | 37 : ['Expiration Date', 'expirationDate'],
521 | 38 : ['Expiration Time', 'expirationTime'],
522 | 40 : ['Special Instructions', 'specialInstructions'],
523 | 42 : ['Action Advised', 'actionAdvised'],
524 | 45 : ['Reference Service', 'refService'],
525 | 47 : ['Reference Date', 'refDate'],
526 | 50 : ['Reference Number', 'refNumber'],
527 | 55 : ['Date Created', 'dateCreated'],
528 | 60 : ['Time Created', 'timeCreated'],
529 | 62 : ['Digital Creation Date', 'digitalCreationDate'],
530 | 63 : ['Digital Creation Time', 'digitalCreationTime'],
531 | 65 : ['Originating Program', 'originatingProgram'],
532 | 70 : ['Program Version', 'programVersion'],
533 | 75 : ['Object Cycle', 'objectCycle'],
534 | 80 : ['By-line', 'byline'],
535 | 85 : ['By-line Title', 'bylineTitle'],
536 | 90 : ['City', 'city'],
537 | 92 : ['Sub-location', 'sublocation'],
538 | 95 : ['Province/State', 'state'],
539 | 100 : ['Country Code', 'countryCode'],
540 | 101 : ['Country Name', 'countryName'],
541 | 103 : ['Original Transmission Reference', 'origTransRef'],
542 | 105 : ['Headline', 'headline'],
543 | 110 : ['Credit', 'credit'],
544 | 115 : ['Source', 'source'],
545 | 116 : ['Copyright Notice', 'copyrightNotice'],
546 | 118 : ['Contact', 'contact'],
547 | 120 : ['Caption/Abstract', 'caption'],
548 | 122 : ['Writer/Editor', 'writerEditor'],
549 | 125 : ['Rasterized Caption', 'rasterizedCaption'],
550 | 130 : ['Image Type', 'imageType'],
551 | 131 : ['Image Orientation', 'imageOrientation'],
552 | 135 : ['Language Identifier', 'languageID'],
553 | 150 : ['Audio Type', 'audioType'],
554 | 151 : ['Audio Sampling Rate', 'audioSamplingRate'],
555 | 152 : ['Audio Sampling Resolution', 'audioSamplingRes'],
556 | 153 : ['Audio Duration', 'audioDuration'],
557 | 154 : ['Audio Outcue', 'audioOutcue'],
558 | 200 : ['Preview File Format', 'previewFileFormat'],
559 | 201 : ['Preview File Format Version', 'previewFileFormatVer'],
560 | 202 : ['Preview Data', 'previewData']
561 | };
562 |
563 | this.JpegMeta.JpegFile.prototype._markers = {
564 | /* Start Of Frame markers, non-differential, Huffman coding */
565 | 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
566 | 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
567 | 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
568 | 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
569 |
570 | /* Start Of Frame markers, differential, Huffman coding */
571 | 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
572 | 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
573 | 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
574 |
575 | /* Start Of Frame markers, non-differential, arithmetic coding */
576 | 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
577 | 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
578 | 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
579 | 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
580 |
581 | /* Start Of Frame markers, differential, arithmetic coding */
582 | 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
583 | 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
584 | 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
585 |
586 | /* Huffman table specification */
587 | 0xc4: ["DHT", null, "Define Huffman table(s)"],
588 | 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
589 |
590 | /* Restart interval termination" */
591 | 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
592 | 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
593 | 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
594 | 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
595 | 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
596 | 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
597 | 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
598 | 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
599 |
600 | /* Other markers */
601 | 0xd8: ["SOI", null, "Start of image"],
602 | 0xd9: ["EOI", null, "End of image"],
603 | 0xda: ["SOS", null, "Start of scan"],
604 | 0xdb: ["DQT", null, "Define quantization table(s)"],
605 | 0xdc: ["DNL", null, "Define number of lines"],
606 | 0xdd: ["DRI", null, "Define restart interval"],
607 | 0xde: ["DHP", null, "Define hierarchical progression"],
608 | 0xdf: ["EXP", null, "Expand reference component(s)"],
609 | 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
610 | 0xe1: ["APP1", "_app1Handler"],
611 | 0xe2: ["APP2", null],
612 | 0xe3: ["APP3", null],
613 | 0xe4: ["APP4", null],
614 | 0xe5: ["APP5", null],
615 | 0xe6: ["APP6", null],
616 | 0xe7: ["APP7", null],
617 | 0xe8: ["APP8", null],
618 | 0xe9: ["APP9", null],
619 | 0xea: ["APP10", null],
620 | 0xeb: ["APP11", null],
621 | 0xec: ["APP12", null],
622 | 0xed: ["IPTC", "_iptcHandler", "IPTC Photo Metadata"],
623 | 0xee: ["APP14", null],
624 | 0xef: ["APP15", null],
625 | 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
626 | 0xf1: ["JPG1", null],
627 | 0xf2: ["JPG2", null],
628 | 0xf3: ["JPG3", null],
629 | 0xf4: ["JPG4", null],
630 | 0xf5: ["JPG5", null],
631 | 0xf6: ["JPG6", null],
632 | 0xf7: ["JPG7", null],
633 | 0xf8: ["JPG8", null],
634 | 0xf9: ["JPG9", null],
635 | 0xfa: ["JPG10", null],
636 | 0xfb: ["JPG11", null],
637 | 0xfc: ["JPG12", null],
638 | 0xfd: ["JPG13", null],
639 | 0xfe: ["COM", "_commentHandler", "Comment"], /* Comment */
640 |
641 | /* Reserved markers */
642 | 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
643 | /* 02 -> bf are reserverd */
644 | };
645 |
646 | /* Private methods */
647 | this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
648 | var group = new JpegMeta.MetaGroup(name, description);
649 | this[group.fieldName] = group;
650 | this.metaGroups[group.fieldName] = group;
651 | return group;
652 | };
653 |
654 | this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
655 | var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
656 | /* Per tag variables */
657 | var tag_base;
658 | var tag_field;
659 | var type, type_field, type_size;
660 | var num_values;
661 | var value_offset;
662 | var value;
663 | var _val;
664 | var num;
665 | var den;
666 |
667 | var group;
668 |
669 | group = this._addMetaGroup(name, description);
670 |
671 | for (var i = 0; i < num_fields; i++) {
672 | /* parse the field */
673 | tag_base = base + ifd_offset + 2 + (i * 12);
674 | tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
675 | type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
676 | num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
677 | value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
678 | if (this._types[type_field] === undefined) {
679 | continue;
680 | }
681 | type = this._types[type_field][0];
682 | type_size = this._types[type_field][1];
683 |
684 | if (type_size * num_values <= 4) {
685 | /* Data is in-line */
686 | value_offset = tag_base + 8;
687 | } else {
688 | value_offset = base + value_offset;
689 | }
690 |
691 | /* Read the value */
692 | if (type == "UNDEFINED") {
693 | /* FIXME: This should be done better */
694 | /*value = _binary_data.slice(value_offset, value_offset + num_values); */
695 | value = undefined;
696 | } else if (type == "ASCII") {
697 | value = _binary_data.slice(value_offset, value_offset + num_values);
698 | value = value.split('\x00')[0];
699 | if (!JpegMeta.stringIsClean(value)) {
700 | value = "";
701 | }
702 | /* strip trail nul */
703 | } else {
704 | value = new Array();
705 | for (var j = 0; j < num_values; j++, value_offset += type_size) {
706 | if (type == "BYTE" || type == "SHORT" || type == "LONG") {
707 | value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
708 | }
709 | if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
710 | value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
711 | }
712 | if (type == "RATIONAL") {
713 | num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
714 | den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
715 | value.push(new JpegMeta.Rational(num, den));
716 | }
717 | if (type == "SRATIONAL") {
718 | num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
719 | den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
720 | value.push(new JpegMeta.Rational(num, den));
721 | }
722 | value.push();
723 | }
724 | if (num_values === 1) {
725 | value = value[0];
726 | }
727 | }
728 | if (tags.hasOwnProperty(tag_field)) {
729 | group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
730 | } else {
731 | console.log("WARNING(jpegmeta.js): Unknown tag: ", tag_field);
732 | }
733 | }
734 | };
735 |
736 | this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
737 | if (this.jfif !== undefined) {
738 | throw Error("Multiple JFIF segments found");
739 | }
740 | this._addMetaGroup("jfif", "JFIF");
741 | this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
742 | this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
743 | this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
744 | this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
745 | this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
746 | this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
747 | this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
748 | this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
749 | };
750 |
751 |
752 | /* Handle app0 segments */
753 | this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
754 | var ident = this._binary_data.slice(pos, pos + 5);
755 | if (ident == this._JFIF_IDENT) {
756 | this._jfifHandler(mark, pos);
757 | } else if (ident == this._JFXX_IDENT) {
758 | /* Don't handle JFXX Ident yet */
759 | } else {
760 | /* Don't know about other idents */
761 | }
762 | };
763 |
764 |
765 | /* Handle app1 segments */
766 | this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
767 | var ident = this._binary_data.slice(pos, pos + 5);
768 | if (ident == this._EXIF_IDENT) {
769 | this._exifHandler(mark, pos + 6);
770 | } else {
771 | /* Don't know about other idents */
772 | }
773 | };
774 |
775 | /* Handle exif segments */
776 | JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
777 | if (this.exif !== undefined) {
778 | throw new Error("Multiple JFIF segments found");
779 | }
780 |
781 | /* Parse this TIFF header */
782 | var endian;
783 | var magic_field;
784 | var ifd_offset;
785 | var primary_ifd, exif_ifd, gps_ifd;
786 | var endian_field = this._binary_data.slice(pos, pos + 2);
787 |
788 | /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
789 | if (endian_field === "II") {
790 | endian = "<";
791 | } else if (endian_field === "MM") {
792 | endian = ">";
793 | } else {
794 | throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
795 | }
796 |
797 | magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
798 |
799 | if (magic_field !== 42) {
800 | throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
801 | }
802 |
803 | ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
804 |
805 | /* Parse 0th IFD */
806 | this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
807 |
808 | if (this.tiff.ExifIfdPointer) {
809 | this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
810 | }
811 |
812 | if (this.tiff.GPSInfoIfdPointer) {
813 | this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
814 | if (this.gps.GPSLatitude) {
815 | var latitude;
816 | latitude = this.gps.GPSLatitude.value[0].asFloat() +
817 | (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() +
818 | (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
819 | if (this.gps.GPSLatitudeRef.value === "S") {
820 | latitude = -latitude;
821 | }
822 | this.gps._addProperty("latitude", "Dec. Latitude", latitude);
823 | }
824 | if (this.gps.GPSLongitude) {
825 | var longitude;
826 | longitude = this.gps.GPSLongitude.value[0].asFloat() +
827 | (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() +
828 | (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
829 | if (this.gps.GPSLongitudeRef.value === "W") {
830 | longitude = -longitude;
831 | }
832 | this.gps._addProperty("longitude", "Dec. Longitude", longitude);
833 | }
834 | }
835 | };
836 |
837 | this.JpegMeta.JpegFile.prototype._iptcHandler = function _iptcHandler(mark, pos, segsize) {
838 | this._addMetaGroup("iptc", "IPTC");
839 |
840 | var endian = '<';
841 | var offset, fieldStart, title, value, tag;
842 | var length = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 1);
843 | var FILE_SEPARATOR_CHAR = 28,
844 | START_OF_TEXT_CHAR = 2;
845 |
846 | for (var i = 0; i < segsize; i++) {
847 | fieldStart = pos + i;
848 | if (JpegMeta.parseNum(endian, this._binary_data, fieldStart, 1) == START_OF_TEXT_CHAR) {
849 | tag = JpegMeta.parseNum(endian, this._binary_data, fieldStart + 1, 1);
850 | tag_desc = this._iptctags[tag];
851 |
852 | if (!tag_desc) continue;
853 | length = 0;
854 | offset = 2;
855 |
856 | while (
857 | offset < segsize &&
858 | JpegMeta.parseNum(endian, this._binary_data, fieldStart + offset, 1) != FILE_SEPARATOR_CHAR &&
859 | JpegMeta.parseNum(endian, this._binary_data, fieldStart + offset + 1, 1) != START_OF_TEXT_CHAR) {
860 | offset++;
861 | length++;
862 | }
863 |
864 | if (!length) continue;
865 |
866 | value = this._binary_data.slice(pos + i + 2, pos + i + 2 + length);
867 | value = value.replace('\000', '').trim();
868 |
869 | this.iptc._addProperty(tag_desc[1], tag_desc[0], value);
870 | i += length - 1;
871 | }
872 | }
873 | };
874 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsjpegmeta",
3 | "version": "1.0.1",
4 | "description": "Get exif info from Jpeg files",
5 | "main": "jpegmeta.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/Zacknero/jsjpegmeta.git"
12 | },
13 | "keywords": [
14 | "Exif",
15 | "Image"
16 | ],
17 | "author": "Ben Leslie","Zacknero"
18 | "license": "BSD",
19 | "dependencies": {
20 |
21 | }
22 | }
23 |
--------------------------------------------------------------------------------