CLDRPluralRuleParser: Move from src/ to lib/ without local patch
[lhc/web/wiklou.git] / resources / src / mediawiki.libs / mediawiki.libs.jpegmeta.js
1 /**
2 * This is JsJpegMeta v1.0
3 * From: https://code.google.com/p/jsjpegmeta/downloads/list
4 * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
5 *
6 * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
7 * Changes:
8 * - Add closure.
9 * - Add this.JpegMeta assignment to expose it as global.
10 * - Add export as module.
11 * - Add mw.libs.jpegmeta wrapper.
12 */
13
14 ( function ( mw ) {
15 /*
16 Copyright (c) 2009 Ben Leslie
17
18 Permission is hereby granted, free of charge, to any person obtaining a copy
19 of this software and associated documentation files (the "Software"), to deal
20 in the Software without restriction, including without limitation the rights
21 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 copies of the Software, and to permit persons to whom the Software is
23 furnished to do so, subject to the following conditions:
24
25 The above copyright notice and this permission notice shall be included in
26 all copies or substantial portions of the Software.
27
28 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
34 THE SOFTWARE.
35 */
36
37 /*
38 This JavaScript library is used to parse meta-data from files
39 with mime-type image/jpeg.
40
41 Include it with something like:
42
43 <script type="text/javascript" src="jpegmeta.js"></script>
44
45 This adds a single 'module' object called 'JpegMeta' to the global
46 namespace.
47
48 Public Functions
49 ----------------
50 JpegMeta.parseNum - parse unsigned integers from binary data
51 JpegMeta.parseSnum - parse signed integers from binary data
52
53 Public Classes
54 --------------
55 JpegMeta.Rational - A rational number class
56 JpegMeta.JfifSegment
57 JpegMeta.ExifSegment
58 JpegMeta.JpegFile - Primary class for Javascript parsing
59 */
60
61 var JpegMeta = {};
62 // MediaWiki: Expose as global
63 this.JpegMeta = JpegMeta;
64
65 /*
66 parse an unsigned number of size bytes at offset in some binary string data.
67 If endian
68 is "<" parse the data as little endian, if endian
69 is ">" parse as big-endian.
70 */
71 JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
72 var i;
73 var ret;
74 var big_endian = (endian === ">");
75 if (offset === undefined) offset = 0;
76 if (size === undefined) size = data.length - offset;
77 for (big_endian ? i = offset : i = offset + size - 1;
78 big_endian ? i < offset + size : i >= offset;
79 big_endian ? i++ : i--) {
80 ret <<= 8;
81 ret += data.charCodeAt(i);
82 }
83 return ret;
84 };
85
86 /*
87 parse an signed number of size bytes at offset in some binary string data.
88 If endian
89 is "<" parse the data as little endian, if endian
90 is ">" parse as big-endian.
91 */
92 JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
93 var i;
94 var ret;
95 var neg;
96 var big_endian = (endian === ">");
97 if (offset === undefined) offset = 0;
98 if (size === undefined) size = data.length - offset;
99 for (big_endian ? i = offset : i = offset + size - 1;
100 big_endian ? i < offset + size : i >= offset;
101 big_endian ? i++ : i--) {
102 if (neg === undefined) {
103 /* Negative if top bit is set */
104 neg = (data.charCodeAt(i) & 0x80) === 0x80;
105 }
106 ret <<= 8;
107 /* If it is negative we invert the bits */
108 ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
109 }
110 if (neg) {
111 /* If it is negative we do two's complement */
112 ret += 1;
113 ret *= -1;
114 }
115 return ret;
116 };
117
118 /* Rational number class */
119 JpegMeta.Rational = function Rational(num, den)
120 {
121 this.num = num;
122 this.den = den || 1;
123 return this;
124 };
125
126 /* Rational number methods */
127 JpegMeta.Rational.prototype.toString = function toString() {
128 if (this.num === 0) {
129 return "" + this.num;
130 }
131 if (this.den === 1) {
132 return "" + this.num;
133 }
134 if (this.num === 1) {
135 return this.num + " / " + this.den;
136 }
137 return this.num / this.den; // + "/" + this.den;
138 };
139
140 JpegMeta.Rational.prototype.asFloat = function asFloat() {
141 return this.num / this.den;
142 };
143
144 /* MetaGroup class */
145 JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
146 this.fieldName = fieldName;
147 this.description = description;
148 this.metaProps = {};
149 return this;
150 };
151
152 JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
153 var property = new JpegMeta.MetaProp(fieldName, description, value);
154 this[property.fieldName] = property;
155 this.metaProps[property.fieldName] = property;
156 };
157
158 JpegMeta.MetaGroup.prototype.toString = function toString() {
159 return "[MetaGroup " + this.description + "]";
160 };
161
162 /* MetaProp class */
163 JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
164 this.fieldName = fieldName;
165 this.description = description;
166 this.value = value;
167 return this;
168 };
169
170 JpegMeta.MetaProp.prototype.toString = function toString() {
171 return "" + this.value;
172 };
173
174 /* JpegFile class */
175 JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
176 /* Change this to EOI if we want to parse. */
177 var break_segment = this._SOS;
178
179 this.metaGroups = {};
180 this._binary_data = binary_data;
181 this.filename = filename;
182
183 /* Go through and parse. */
184 var pos = 0;
185 var pos_start_of_segment = 0;
186 var delim;
187 var mark;
188 var _mark;
189 var segsize;
190 var headersize;
191 var mark_code;
192 var mark_fn;
193
194 /* Check to see if this looks like a JPEG file */
195 if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
196 throw new Error("Doesn't look like a JPEG file. First two bytes are " +
197 this._binary_data.charCodeAt(0) + "," +
198 this._binary_data.charCodeAt(1) + ".");
199 }
200
201 pos += 2;
202
203 while (pos < this._binary_data.length) {
204 delim = this._binary_data.charCodeAt(pos++);
205 mark = this._binary_data.charCodeAt(pos++);
206
207 pos_start_of_segment = pos;
208
209 if (delim != this._DELIM) {
210 break;
211 }
212
213 if (mark === break_segment) {
214 break;
215 }
216
217 headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
218
219 /* Find the end */
220 pos += headersize;
221 while (pos < this._binary_data.length) {
222 delim = this._binary_data.charCodeAt(pos++);
223 if (delim == this._DELIM) {
224 _mark = this._binary_data.charCodeAt(pos++);
225 if (_mark != 0x0) {
226 pos -= 2;
227 break;
228 }
229 }
230 }
231
232 segsize = pos - pos_start_of_segment;
233
234 if (this._markers[mark]) {
235 mark_code = this._markers[mark][0];
236 mark_fn = this._markers[mark][1];
237 } else {
238 mark_code = "UNKN";
239 mark_fn = undefined;
240 }
241
242 if (mark_fn) {
243 this[mark_fn](mark, pos_start_of_segment + 2);
244 }
245
246 }
247
248 if (this.general === undefined) {
249 throw Error("Invalid JPEG file.");
250 }
251
252 return this;
253 };
254
255 this.JpegMeta.JpegFile.prototype.toString = function () {
256 return "[JpegFile " + this.filename + " " +
257 this.general.type + " " +
258 this.general.pixelWidth + "x" +
259 this.general.pixelHeight +
260 " Depth: " + this.general.depth + "]";
261 };
262
263 /* Some useful constants */
264 this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
265 this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
266 this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
267 this.JpegMeta.JpegFile.prototype._SOS = 0xda;
268
269 this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
270 if (this.general !== undefined) {
271 throw Error("Unexpected multiple-frame image");
272 }
273
274 this._addMetaGroup("general", "General");
275 this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
276 this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
277 this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
278 this.general._addProperty("type", "Type", this._markers[mark][2]);
279 };
280
281 /* JFIF idents */
282 this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
283 this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
284
285 /* Exif idents */
286 this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
287
288 /* TIFF types */
289 this.JpegMeta.JpegFile.prototype._types = {
290 /* The format is identifier : ["type name", type_size_in_bytes ] */
291 1 : ["BYTE", 1],
292 2 : ["ASCII", 1],
293 3 : ["SHORT", 2],
294 4 : ["LONG", 4],
295 5 : ["RATIONAL", 8],
296 6 : ["SBYTE", 1],
297 7 : ["UNDEFINED", 1],
298 8 : ["SSHORT", 2],
299 9 : ["SLONG", 4],
300 10 : ["SRATIONAL", 8],
301 11 : ["FLOAT", 4],
302 12 : ["DOUBLE", 8]
303 };
304
305 this.JpegMeta.JpegFile.prototype._tifftags = {
306 /* A. Tags relating to image data structure */
307 256 : ["Image width", "ImageWidth"],
308 257 : ["Image height", "ImageLength"],
309 258 : ["Number of bits per component", "BitsPerSample"],
310 259 : ["Compression scheme", "Compression",
311 {1 : "uncompressed", 6 : "JPEG compression" }],
312 262 : ["Pixel composition", "PhotmetricInerpretation",
313 {2 : "RGB", 6 : "YCbCr"}],
314 274 : ["Orientation of image", "Orientation",
315 /* FIXME: Check the mirror-image / reverse encoding and rotation */
316 {1 : "Normal", 2 : "Reverse?",
317 3 : "Upside-down", 4 : "Upside-down Reverse",
318 5 : "90 degree CW", 6 : "90 degree CW reverse",
319 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
320 277 : ["Number of components", "SamplesPerPixel"],
321 284 : ["Image data arrangement", "PlanarConfiguration",
322 {1 : "chunky format", 2 : "planar format"}],
323 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
324 531 : ["Y and C positioning", "YCbCrPositioning",
325 {1 : "centered", 2 : "co-sited"}],
326 282 : ["X Resolution", "XResolution"],
327 283 : ["Y Resolution", "YResolution"],
328 296 : ["Resolution Unit", "ResolutionUnit",
329 {2 : "inches", 3 : "centimeters"}],
330 /* B. Tags realting to recording offset */
331 273 : ["Image data location", "StripOffsets"],
332 278 : ["Number of rows per strip", "RowsPerStrip"],
333 279 : ["Bytes per compressed strip", "StripByteCounts"],
334 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
335 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
336 /* C. Tags relating to image data characteristics */
337 301 : ["Transfer function", "TransferFunction"],
338 318 : ["White point chromaticity", "WhitePoint"],
339 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
340 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
341 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
342 /* D. Other tags */
343 306 : ["Date and time", "DateTime"],
344 270 : ["Image title", "ImageDescription"],
345 271 : ["Make", "Make"],
346 272 : ["Model", "Model"],
347 305 : ["Software", "Software"],
348 315 : ["Person who created the image", "Artist"],
349 316 : ["Host Computer", "HostComputer"],
350 33432 : ["Copyright holder", "Copyright"],
351
352 34665 : ["Exif tag", "ExifIfdPointer"],
353 34853 : ["GPS tag", "GPSInfoIfdPointer"]
354 };
355
356 this.JpegMeta.JpegFile.prototype._exiftags = {
357 /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
358 /* A. Tags Relating to Version */
359 36864 : ["Exif Version", "ExifVersion"],
360 40960 : ["FlashPix Version", "FlashpixVersion"],
361
362 /* B. Tag Relating to Image Data Characteristics */
363 40961 : ["Color Space", "ColorSpace"],
364
365 /* C. Tags Relating to Image Configuration */
366 37121 : ["Meaning of each component", "ComponentsConfiguration"],
367 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
368 40962 : ["Pixel X Dimension", "PixelXDimension"],
369 40963 : ["Pixel Y Dimension", "PixelYDimension"],
370
371 /* D. Tags Relating to User Information */
372 37500 : ["Manufacturer notes", "MakerNote"],
373 37510 : ["User comments", "UserComment"],
374
375 /* E. Tag Relating to Related File Information */
376 40964 : ["Related audio file", "RelatedSoundFile"],
377
378 /* F. Tags Relating to Date and Time */
379 36867 : ["Date Time Original", "DateTimeOriginal"],
380 36868 : ["Date Time Digitized", "DateTimeDigitized"],
381 37520 : ["DateTime subseconds", "SubSecTime"],
382 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
383 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
384
385 /* G. Tags Relating to Picture-Taking Conditions */
386 33434 : ["Exposure time", "ExposureTime"],
387 33437 : ["FNumber", "FNumber"],
388 34850 : ["Exposure program", "ExposureProgram"],
389 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
390 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
391 34856 : ["Optoelectric coefficient", "OECF"],
392 37377 : ["Shutter Speed", "ShutterSpeedValue"],
393 37378 : ["Aperture Value", "ApertureValue"],
394 37379 : ["Brightness", "BrightnessValue"],
395 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
396 37381 : ["Max Aperture Value", "MaxApertureValue"],
397 37382 : ["Subject Distance", "SubjectDistance"],
398 37383 : ["Metering Mode", "MeteringMode"],
399 37384 : ["Light Source", "LightSource"],
400 37385 : ["Flash", "Flash"],
401 37386 : ["Focal Length", "FocalLength"],
402 37396 : ["Subject Area", "SubjectArea"],
403 41483 : ["Flash Energy", "FlashEnergy"],
404 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
405 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
406 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
407 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
408 41492 : ["Subject Location", "SubjectLocation"],
409 41493 : ["Exposure Index", "ExposureIndex"],
410 41495 : ["Sensing Method", "SensingMethod"],
411 41728 : ["File Source", "FileSource"],
412 41729 : ["Scene Type", "SceneType"],
413 41730 : ["CFA Pattern", "CFAPattern"],
414 41985 : ["Custom Rendered", "CustomRendered"],
415 41986 : ["Exposure Mode", "Exposure Mode"],
416 41987 : ["White Balance", "WhiteBalance"],
417 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
418 41990 : ["Scene Capture Type", "SceneCaptureType"],
419 41991 : ["Gain Control", "GainControl"],
420 41992 : ["Contrast", "Contrast"],
421 41993 : ["Saturation", "Saturation"],
422 41994 : ["Sharpness", "Sharpness"],
423 41995 : ["Device settings description", "DeviceSettingDescription"],
424 41996 : ["Subject distance range", "SubjectDistanceRange"],
425
426 /* H. Other Tags */
427 42016 : ["Unique image ID", "ImageUniqueID"],
428
429 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
430 };
431
432 this.JpegMeta.JpegFile.prototype._gpstags = {
433 /* A. Tags Relating to GPS */
434 0 : ["GPS tag version", "GPSVersionID"],
435 1 : ["North or South Latitude", "GPSLatitudeRef"],
436 2 : ["Latitude", "GPSLatitude"],
437 3 : ["East or West Longitude", "GPSLongitudeRef"],
438 4 : ["Longitude", "GPSLongitude"],
439 5 : ["Altitude reference", "GPSAltitudeRef"],
440 6 : ["Altitude", "GPSAltitude"],
441 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
442 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
443 9 : ["GPS receiver status", "GPSStatus"],
444 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
445 11 : ["Measurement precision", "GPSDOP"],
446 12 : ["Speed unit", "GPSSpeedRef"],
447 13 : ["Speed of GPS receiver", "GPSSpeed"],
448 14 : ["Reference for direction of movement", "GPSTrackRef"],
449 15 : ["Direction of movement", "GPSTrack"],
450 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
451 17 : ["Direction of image", "GPSImgDirection"],
452 18 : ["Geodetic survey data used", "GPSMapDatum"],
453 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
454 20 : ["Latitude of destination", "GPSDestLatitude"],
455 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
456 22 : ["Longitude of destination", "GPSDestLongitude"],
457 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
458 24 : ["Bearing of destination", "GPSDestBearing"],
459 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
460 26 : ["Distance to destination", "GPSDestDistance"],
461 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
462 28 : ["Name of GPS area", "GPSAreaInformation"],
463 29 : ["GPS Date", "GPSDateStamp"],
464 30 : ["GPS differential correction", "GPSDifferential"]
465 };
466
467 this.JpegMeta.JpegFile.prototype._markers = {
468 /* Start Of Frame markers, non-differential, Huffman coding */
469 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
470 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
471 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
472 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
473
474 /* Start Of Frame markers, differential, Huffman coding */
475 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
476 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
477 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
478
479 /* Start Of Frame markers, non-differential, arithmetic coding */
480 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
481 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
482 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
483 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
484
485 /* Start Of Frame markers, differential, arithmetic coding */
486 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
487 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
488 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
489
490 /* Huffman table specification */
491 0xc4: ["DHT", null, "Define Huffman table(s)"],
492 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
493
494 /* Restart interval termination" */
495 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
496 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
497 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
498 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
499 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
500 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
501 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
502 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
503
504 /* Other markers */
505 0xd8: ["SOI", null, "Start of image"],
506 0xd9: ["EOI", null, "End of image"],
507 0xda: ["SOS", null, "Start of scan"],
508 0xdb: ["DQT", null, "Define quantization table(s)"],
509 0xdc: ["DNL", null, "Define number of lines"],
510 0xdd: ["DRI", null, "Define restart interval"],
511 0xde: ["DHP", null, "Define hierarchical progression"],
512 0xdf: ["EXP", null, "Expand reference component(s)"],
513 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
514 0xe1: ["APP1", "_app1Handler"],
515 0xe2: ["APP2", null],
516 0xe3: ["APP3", null],
517 0xe4: ["APP4", null],
518 0xe5: ["APP5", null],
519 0xe6: ["APP6", null],
520 0xe7: ["APP7", null],
521 0xe8: ["APP8", null],
522 0xe9: ["APP9", null],
523 0xea: ["APP10", null],
524 0xeb: ["APP11", null],
525 0xec: ["APP12", null],
526 0xed: ["APP13", null],
527 0xee: ["APP14", null],
528 0xef: ["APP15", null],
529 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
530 0xf1: ["JPG1", null],
531 0xf2: ["JPG2", null],
532 0xf3: ["JPG3", null],
533 0xf4: ["JPG4", null],
534 0xf5: ["JPG5", null],
535 0xf6: ["JPG6", null],
536 0xf7: ["JPG7", null],
537 0xf8: ["JPG8", null],
538 0xf9: ["JPG9", null],
539 0xfa: ["JPG10", null],
540 0xfb: ["JPG11", null],
541 0xfc: ["JPG12", null],
542 0xfd: ["JPG13", null],
543 0xfe: ["COM", null], /* Comment */
544
545 /* Reserved markers */
546 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
547 /* 02 -> bf are reserverd */
548 };
549
550 /* Private methods */
551 this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
552 var group = new JpegMeta.MetaGroup(name, description);
553 this[group.fieldName] = group;
554 this.metaGroups[group.fieldName] = group;
555 return group;
556 };
557
558 this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
559 var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
560 /* Per tag variables */
561 var i, j;
562 var tag_base;
563 var tag_field;
564 var type, type_field, type_size;
565 var num_values;
566 var value_offset;
567 var value;
568 var _val;
569 var num;
570 var den;
571
572 var group;
573
574 group = this._addMetaGroup(name, description);
575
576 for (var i = 0; i < num_fields; i++) {
577 /* parse the field */
578 tag_base = base + ifd_offset + 2 + (i * 12);
579 tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
580 type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
581 num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
582 value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
583 if (this._types[type_field] === undefined) {
584 continue;
585 }
586 type = this._types[type_field][0];
587 type_size = this._types[type_field][1];
588
589 if (type_size * num_values <= 4) {
590 /* Data is in-line */
591 value_offset = tag_base + 8;
592 } else {
593 value_offset = base + value_offset;
594 }
595
596 /* Read the value */
597 if (type == "UNDEFINED") {
598 value = _binary_data.slice(value_offset, value_offset + num_values);
599 } else if (type == "ASCII") {
600 value = _binary_data.slice(value_offset, value_offset + num_values);
601 value = value.split('\x00')[0];
602 /* strip trail nul */
603 } else {
604 value = new Array();
605 for (j = 0; j < num_values; j++, value_offset += type_size) {
606 if (type == "BYTE" || type == "SHORT" || type == "LONG") {
607 value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
608 }
609 if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
610 value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
611 }
612 if (type == "RATIONAL") {
613 num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
614 den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
615 value.push(new JpegMeta.Rational(num, den));
616 }
617 if (type == "SRATIONAL") {
618 num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
619 den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
620 value.push(new JpegMeta.Rational(num, den));
621 }
622 value.push();
623 }
624 if (num_values === 1) {
625 value = value[0];
626 }
627 }
628 if (tags[tag_field] !== undefined) {
629 group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
630 }
631 }
632 };
633
634 this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
635 if (this.jfif !== undefined) {
636 throw Error("Multiple JFIF segments found");
637 }
638 this._addMetaGroup("jfif", "JFIF");
639 this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
640 this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
641 this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
642 this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
643 this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
644 this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
645 this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
646 this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
647 };
648
649 /* Handle app0 segments */
650 this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
651 var ident = this._binary_data.slice(pos, pos + 5);
652 if (ident == this._JFIF_IDENT) {
653 this._jfifHandler(mark, pos);
654 } else if (ident == this._JFXX_IDENT) {
655 /* Don't handle JFXX Ident yet */
656 } else {
657 /* Don't know about other idents */
658 }
659 };
660
661 /* Handle app1 segments */
662 this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
663 var ident = this._binary_data.slice(pos, pos + 5);
664 if (ident == this._EXIF_IDENT) {
665 this._exifHandler(mark, pos + 6);
666 } else {
667 /* Don't know about other idents */
668 }
669 };
670
671 /* Handle exif segments */
672 JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
673 if (this.exif !== undefined) {
674 throw new Error("Multiple JFIF segments found");
675 }
676
677 /* Parse this TIFF header */
678 var endian;
679 var magic_field;
680 var ifd_offset;
681 var primary_ifd, exif_ifd, gps_ifd;
682 var endian_field = this._binary_data.slice(pos, pos + 2);
683
684 /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
685 if (endian_field === "II") {
686 endian = "<";
687 } else if (endian_field === "MM") {
688 endian = ">";
689 } else {
690 throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
691 }
692
693 magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
694
695 if (magic_field !== 42) {
696 throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
697 }
698
699 ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
700
701 /* Parse 0th IFD */
702 this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
703
704 if (this.tiff.ExifIfdPointer) {
705 this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
706 }
707
708 if (this.tiff.GPSInfoIfdPointer) {
709 this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
710 if (this.gps.GPSLatitude) {
711 var latitude;
712 latitude = this.gps.GPSLatitude.value[0].asFloat() +
713 (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() +
714 (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
715 if (this.gps.GPSLatitudeRef.value === "S") {
716 latitude = -latitude;
717 }
718 this.gps._addProperty("latitude", "Dec. Latitude", latitude);
719 }
720 if (this.gps.GPSLongitude) {
721 var longitude;
722 longitude = this.gps.GPSLongitude.value[0].asFloat() +
723 (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() +
724 (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
725 if (this.gps.GPSLongitudeRef.value === "W") {
726 longitude = -longitude;
727 }
728 this.gps._addProperty("longitude", "Dec. Longitude", longitude);
729 }
730 }
731 };
732
733 // MediaWiki: Export as module
734 module.exports = function( fileReaderResult, fileName ) {
735 return new JpegMeta.JpegFile( fileReaderResult, fileName );
736 };
737
738 // MediaWiki: Add mw.libs wrapper
739 // @deprecated since 1.31
740 mw.log.deprecate( mw.libs, 'jpegmeta', module.exports );
741
742 }( mediaWiki ) );