index.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. // @ts-nocheck
  2. import { base64ToArrayBuffer } from '../base64ToArrayBuffer';
  3. import { pathToBase64 } from '../pathToBase64';
  4. import { isBase64 } from '../isBase64';
  5. import { isString } from '../isString';
  6. interface File {
  7. exifdata : any
  8. iptcdata : any
  9. xmpdata : any
  10. src : string
  11. }
  12. class EXIF {
  13. isXmpEnabled = false
  14. debug = false
  15. Tags = {
  16. // version tags
  17. 0x9000: "ExifVersion", // EXIF version
  18. 0xA000: "FlashpixVersion", // Flashpix format version
  19. // colorspace tags
  20. 0xA001: "ColorSpace", // Color space information tag
  21. // image configuration
  22. 0xA002: "PixelXDimension", // Valid width of meaningful image
  23. 0xA003: "PixelYDimension", // Valid height of meaningful image
  24. 0x9101: "ComponentsConfiguration", // Information about channels
  25. 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel
  26. // user information
  27. 0x927C: "MakerNote", // Any desired information written by the manufacturer
  28. 0x9286: "UserComment", // Comments by user
  29. // related file
  30. 0xA004: "RelatedSoundFile", // Name of related sound file
  31. // date and time
  32. 0x9003: "DateTimeOriginal", // Date and time when the original image was generated
  33. 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally
  34. 0x9290: "SubsecTime", // Fractions of seconds for DateTime
  35. 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
  36. 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
  37. // picture-taking conditions
  38. 0x829A: "ExposureTime", // Exposure time (in seconds)
  39. 0x829D: "FNumber", // F number
  40. 0x8822: "ExposureProgram", // Exposure program
  41. 0x8824: "SpectralSensitivity", // Spectral sensitivity
  42. 0x8827: "ISOSpeedRatings", // ISO speed rating
  43. 0x8828: "OECF", // Optoelectric conversion factor
  44. 0x9201: "ShutterSpeedValue", // Shutter speed
  45. 0x9202: "ApertureValue", // Lens aperture
  46. 0x9203: "BrightnessValue", // Value of brightness
  47. 0x9204: "ExposureBias", // Exposure bias
  48. 0x9205: "MaxApertureValue", // Smallest F number of lens
  49. 0x9206: "SubjectDistance", // Distance to subject in meters
  50. 0x9207: "MeteringMode", // Metering mode
  51. 0x9208: "LightSource", // Kind of light source
  52. 0x9209: "Flash", // Flash status
  53. 0x9214: "SubjectArea", // Location and area of main subject
  54. 0x920A: "FocalLength", // Focal length of the lens in mm
  55. 0xA20B: "FlashEnergy", // Strobe energy in BCPS
  56. 0xA20C: "SpatialFrequencyResponse", //
  57. 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
  58. 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
  59. 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
  60. 0xA214: "SubjectLocation", // Location of subject in image
  61. 0xA215: "ExposureIndex", // Exposure index selected on camera
  62. 0xA217: "SensingMethod", // Image sensor type
  63. 0xA300: "FileSource", // Image source (3 == DSC)
  64. 0xA301: "SceneType", // Scene type (1 == directly photographed)
  65. 0xA302: "CFAPattern", // Color filter array geometric pattern
  66. 0xA401: "CustomRendered", // Special processing
  67. 0xA402: "ExposureMode", // Exposure mode
  68. 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual
  69. 0xA404: "DigitalZoomRation", // Digital zoom ratio
  70. 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
  71. 0xA406: "SceneCaptureType", // Type of scene
  72. 0xA407: "GainControl", // Degree of overall image gain adjustment
  73. 0xA408: "Contrast", // Direction of contrast processing applied by camera
  74. 0xA409: "Saturation", // Direction of saturation processing applied by camera
  75. 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera
  76. 0xA40B: "DeviceSettingDescription", //
  77. 0xA40C: "SubjectDistanceRange", // Distance to subject
  78. // other tags
  79. 0xA005: "InteroperabilityIFDPointer",
  80. 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image
  81. }
  82. TiffTags = {
  83. 0x0100: "ImageWidth",
  84. 0x0101: "ImageHeight",
  85. 0x8769: "ExifIFDPointer",
  86. 0x8825: "GPSInfoIFDPointer",
  87. 0xA005: "InteroperabilityIFDPointer",
  88. 0x0102: "BitsPerSample",
  89. 0x0103: "Compression",
  90. 0x0106: "PhotometricInterpretation",
  91. 0x0112: "Orientation",
  92. 0x0115: "SamplesPerPixel",
  93. 0x011C: "PlanarConfiguration",
  94. 0x0212: "YCbCrSubSampling",
  95. 0x0213: "YCbCrPositioning",
  96. 0x011A: "XResolution",
  97. 0x011B: "YResolution",
  98. 0x0128: "ResolutionUnit",
  99. 0x0111: "StripOffsets",
  100. 0x0116: "RowsPerStrip",
  101. 0x0117: "StripByteCounts",
  102. 0x0201: "JPEGInterchangeFormat",
  103. 0x0202: "JPEGInterchangeFormatLength",
  104. 0x012D: "TransferFunction",
  105. 0x013E: "WhitePoint",
  106. 0x013F: "PrimaryChromaticities",
  107. 0x0211: "YCbCrCoefficients",
  108. 0x0214: "ReferenceBlackWhite",
  109. 0x0132: "DateTime",
  110. 0x010E: "ImageDescription",
  111. 0x010F: "Make",
  112. 0x0110: "Model",
  113. 0x0131: "Software",
  114. 0x013B: "Artist",
  115. 0x8298: "Copyright"
  116. }
  117. GPSTags = {
  118. 0x0000: "GPSVersionID",
  119. 0x0001: "GPSLatitudeRef",
  120. 0x0002: "GPSLatitude",
  121. 0x0003: "GPSLongitudeRef",
  122. 0x0004: "GPSLongitude",
  123. 0x0005: "GPSAltitudeRef",
  124. 0x0006: "GPSAltitude",
  125. 0x0007: "GPSTimeStamp",
  126. 0x0008: "GPSSatellites",
  127. 0x0009: "GPSStatus",
  128. 0x000A: "GPSMeasureMode",
  129. 0x000B: "GPSDOP",
  130. 0x000C: "GPSSpeedRef",
  131. 0x000D: "GPSSpeed",
  132. 0x000E: "GPSTrackRef",
  133. 0x000F: "GPSTrack",
  134. 0x0010: "GPSImgDirectionRef",
  135. 0x0011: "GPSImgDirection",
  136. 0x0012: "GPSMapDatum",
  137. 0x0013: "GPSDestLatitudeRef",
  138. 0x0014: "GPSDestLatitude",
  139. 0x0015: "GPSDestLongitudeRef",
  140. 0x0016: "GPSDestLongitude",
  141. 0x0017: "GPSDestBearingRef",
  142. 0x0018: "GPSDestBearing",
  143. 0x0019: "GPSDestDistanceRef",
  144. 0x001A: "GPSDestDistance",
  145. 0x001B: "GPSProcessingMethod",
  146. 0x001C: "GPSAreaInformation",
  147. 0x001D: "GPSDateStamp",
  148. 0x001E: "GPSDifferential"
  149. }
  150. // EXIF 2.3 Spec
  151. IFD1Tags = {
  152. 0x0100: "ImageWidth",
  153. 0x0101: "ImageHeight",
  154. 0x0102: "BitsPerSample",
  155. 0x0103: "Compression",
  156. 0x0106: "PhotometricInterpretation",
  157. 0x0111: "StripOffsets",
  158. 0x0112: "Orientation",
  159. 0x0115: "SamplesPerPixel",
  160. 0x0116: "RowsPerStrip",
  161. 0x0117: "StripByteCounts",
  162. 0x011A: "XResolution",
  163. 0x011B: "YResolution",
  164. 0x011C: "PlanarConfiguration",
  165. 0x0128: "ResolutionUnit",
  166. 0x0201: "JpegIFOffset", // When image format is JPEG, this value show offset to JPEG data stored.(aka "ThumbnailOffset" or "JPEGInterchangeFormat")
  167. 0x0202: "JpegIFByteCount", // When image format is JPEG, this value shows data size of JPEG image (aka "ThumbnailLength" or "JPEGInterchangeFormatLength")
  168. 0x0211: "YCbCrCoefficients",
  169. 0x0212: "YCbCrSubSampling",
  170. 0x0213: "YCbCrPositioning",
  171. 0x0214: "ReferenceBlackWhite"
  172. }
  173. StringValues = {
  174. ExposureProgram: {
  175. 0: "Not defined",
  176. 1: "Manual",
  177. 2: "Normal program",
  178. 3: "Aperture priority",
  179. 4: "Shutter priority",
  180. 5: "Creative program",
  181. 6: "Action program",
  182. 7: "Portrait mode",
  183. 8: "Landscape mode"
  184. },
  185. MeteringMode: {
  186. 0: "Unknown",
  187. 1: "Average",
  188. 2: "CenterWeightedAverage",
  189. 3: "Spot",
  190. 4: "MultiSpot",
  191. 5: "Pattern",
  192. 6: "Partial",
  193. 255: "Other"
  194. },
  195. LightSource: {
  196. 0: "Unknown",
  197. 1: "Daylight",
  198. 2: "Fluorescent",
  199. 3: "Tungsten (incandescent light)",
  200. 4: "Flash",
  201. 9: "Fine weather",
  202. 10: "Cloudy weather",
  203. 11: "Shade",
  204. 12: "Daylight fluorescent (D 5700 - 7100K)",
  205. 13: "Day white fluorescent (N 4600 - 5400K)",
  206. 14: "Cool white fluorescent (W 3900 - 4500K)",
  207. 15: "White fluorescent (WW 3200 - 3700K)",
  208. 17: "Standard light A",
  209. 18: "Standard light B",
  210. 19: "Standard light C",
  211. 20: "D55",
  212. 21: "D65",
  213. 22: "D75",
  214. 23: "D50",
  215. 24: "ISO studio tungsten",
  216. 255: "Other"
  217. },
  218. Flash: {
  219. 0x0000: "Flash did not fire",
  220. 0x0001: "Flash fired",
  221. 0x0005: "Strobe return light not detected",
  222. 0x0007: "Strobe return light detected",
  223. 0x0009: "Flash fired, compulsory flash mode",
  224. 0x000D: "Flash fired, compulsory flash mode, return light not detected",
  225. 0x000F: "Flash fired, compulsory flash mode, return light detected",
  226. 0x0010: "Flash did not fire, compulsory flash mode",
  227. 0x0018: "Flash did not fire, auto mode",
  228. 0x0019: "Flash fired, auto mode",
  229. 0x001D: "Flash fired, auto mode, return light not detected",
  230. 0x001F: "Flash fired, auto mode, return light detected",
  231. 0x0020: "No flash function",
  232. 0x0041: "Flash fired, red-eye reduction mode",
  233. 0x0045: "Flash fired, red-eye reduction mode, return light not detected",
  234. 0x0047: "Flash fired, red-eye reduction mode, return light detected",
  235. 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode",
  236. 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
  237. 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
  238. 0x0059: "Flash fired, auto mode, red-eye reduction mode",
  239. 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
  240. 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode"
  241. },
  242. SensingMethod: {
  243. 1: "Not defined",
  244. 2: "One-chip color area sensor",
  245. 3: "Two-chip color area sensor",
  246. 4: "Three-chip color area sensor",
  247. 5: "Color sequential area sensor",
  248. 7: "Trilinear sensor",
  249. 8: "Color sequential linear sensor"
  250. },
  251. SceneCaptureType: {
  252. 0: "Standard",
  253. 1: "Landscape",
  254. 2: "Portrait",
  255. 3: "Night scene"
  256. },
  257. SceneType: {
  258. 1: "Directly photographed"
  259. },
  260. CustomRendered: {
  261. 0: "Normal process",
  262. 1: "Custom process"
  263. },
  264. WhiteBalance: {
  265. 0: "Auto white balance",
  266. 1: "Manual white balance"
  267. },
  268. GainControl: {
  269. 0: "None",
  270. 1: "Low gain up",
  271. 2: "High gain up",
  272. 3: "Low gain down",
  273. 4: "High gain down"
  274. },
  275. Contrast: {
  276. 0: "Normal",
  277. 1: "Soft",
  278. 2: "Hard"
  279. },
  280. Saturation: {
  281. 0: "Normal",
  282. 1: "Low saturation",
  283. 2: "High saturation"
  284. },
  285. Sharpness: {
  286. 0: "Normal",
  287. 1: "Soft",
  288. 2: "Hard"
  289. },
  290. SubjectDistanceRange: {
  291. 0: "Unknown",
  292. 1: "Macro",
  293. 2: "Close view",
  294. 3: "Distant view"
  295. },
  296. FileSource: {
  297. 3: "DSC"
  298. },
  299. Components: {
  300. 0: "",
  301. 1: "Y",
  302. 2: "Cb",
  303. 3: "Cr",
  304. 4: "R",
  305. 5: "G",
  306. 6: "B"
  307. }
  308. }
  309. enableXmp() {
  310. this.isXmpEnabled = true
  311. }
  312. disableXmp() {
  313. this.isXmpEnabled = false;
  314. }
  315. /**
  316. * 获取图片数据
  317. * @param img 图片地址
  318. * @param callback 回调 返回图片数据
  319. * */
  320. getData(img : any, callback : Function) {
  321. // if (((self.Image && img instanceof self.Image) || (self.HTMLImageElement && img instanceof self.HTMLImageElement)) && !img.complete)
  322. // return false;
  323. let file : File = {
  324. src: '',
  325. exifdata: null,
  326. iptcdata: null,
  327. xmpdata: null,
  328. }
  329. if (isBase64(img)) {
  330. file.src = img
  331. } else if (img.path) {
  332. file.src = img.path
  333. } else if (isString(img)) {
  334. file.src = img
  335. } else {
  336. return false;
  337. }
  338. if (!imageHasData(file)) {
  339. getImageData(file, callback);
  340. } else {
  341. if (callback) {
  342. callback.call(file);
  343. }
  344. }
  345. return true;
  346. }
  347. /**
  348. * 获取图片tag
  349. * @param img 图片数据
  350. * @param tag tag 类型
  351. * */
  352. getTag(img : File, tag : string) {
  353. if (!imageHasData(img)) return;
  354. return img.exifdata[tag];
  355. }
  356. getIptcTag(img : File, tag : string) {
  357. if (!imageHasData(img)) return;
  358. return img.iptcdata[tag];
  359. }
  360. getAllTags(img : File) {
  361. if (!imageHasData(img)) return {};
  362. let a,
  363. data = img.exifdata,
  364. tags = {};
  365. for (a in data) {
  366. if (data.hasOwnProperty(a)) {
  367. tags[a] = data[a];
  368. }
  369. }
  370. return tags;
  371. }
  372. getAllIptcTags(img : File) {
  373. if (!imageHasData(img)) return {};
  374. let a,
  375. data = img.iptcdata,
  376. tags = {};
  377. for (a in data) {
  378. if (data.hasOwnProperty(a)) {
  379. tags[a] = data[a];
  380. }
  381. }
  382. return tags;
  383. }
  384. pretty(img : File) {
  385. if (!imageHasData(img)) return "";
  386. let a,
  387. data = img.exifdata,
  388. strPretty = "";
  389. for (a in data) {
  390. if (data.hasOwnProperty(a)) {
  391. if (typeof data[a] == "object") {
  392. if (data[a] instanceof Number) {
  393. strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a]
  394. .denominator + "]\r\n";
  395. } else {
  396. strPretty += a + " : [" + data[a].length + " values]\r\n";
  397. }
  398. } else {
  399. strPretty += a + " : " + data[a] + "\r\n";
  400. }
  401. }
  402. }
  403. return strPretty;
  404. }
  405. readFromBinaryFile(file: ArrayBuffer) {
  406. return findEXIFinJPEG(file);
  407. }
  408. }
  409. export const exif = new EXIF()
  410. // export function getData(img, callback) {
  411. // const exif = new EXIF()
  412. // exif.getData(img, callback)
  413. // }
  414. // export default {getData}
  415. const ExifTags = exif.Tags
  416. const TiffTags = exif.TiffTags
  417. const IFD1Tags = exif.IFD1Tags
  418. const GPSTags = exif.GPSTags
  419. const StringValues = exif.StringValues
  420. function imageHasData(img : File) : boolean {
  421. return !!(img.exifdata);
  422. }
  423. function objectURLToBlob(url : string, callback : Function) {
  424. try {
  425. const http = new XMLHttpRequest();
  426. http.open("GET", url, true);
  427. http.responseType = "blob";
  428. http.onload = function (e) {
  429. if (this.status == 200 || this.status === 0) {
  430. callback(this.response);
  431. }
  432. };
  433. http.send();
  434. } catch (e) {
  435. console.warn(e)
  436. }
  437. }
  438. function getImageData(img : File, callback : Function) {
  439. function handleBinaryFile(binFile: ArrayBuffer) {
  440. const data = findEXIFinJPEG(binFile);
  441. img.exifdata = data ?? {};
  442. const iptcdata = findIPTCinJPEG(binFile);
  443. img.iptcdata = iptcdata ?? {};
  444. if (exif.isXmpEnabled) {
  445. const xmpdata = findXMPinJPEG(binFile);
  446. img.xmpdata = xmpdata ?? {};
  447. }
  448. if (callback) {
  449. callback.call(img);
  450. }
  451. }
  452. if (img.src) {
  453. if (/^data\:/i.test(img.src)) { // Data URI
  454. // var arrayBuffer = base64ToArrayBuffer(img.src);
  455. handleBinaryFile(base64ToArrayBuffer(img.src));
  456. } else if (/^blob\:/i.test(img.src) && typeof FileReader !== 'undefined') { // Object URL
  457. var fileReader = new FileReader();
  458. fileReader.onload = function (e) {
  459. handleBinaryFile(e.target.result);
  460. };
  461. objectURLToBlob(img.src, function (blob : Blob) {
  462. fileReader.readAsArrayBuffer(blob);
  463. });
  464. } else if (typeof XMLHttpRequest !== 'undefined') {
  465. var http = new XMLHttpRequest();
  466. http.onload = function () {
  467. if (this.status == 200 || this.status === 0) {
  468. handleBinaryFile(http.response);
  469. } else {
  470. throw "Could not load image";
  471. }
  472. http = null;
  473. };
  474. http.open("GET", img.src, true);
  475. http.responseType = "arraybuffer";
  476. http.send(null);
  477. } else {
  478. pathToBase64(img.src).then(res => {
  479. handleBinaryFile(base64ToArrayBuffer(res));
  480. })
  481. }
  482. } else if (typeof FileReader !== 'undefined' && self.FileReader && (img instanceof self.Blob || img instanceof self.File)) {
  483. var fileReader = new FileReader();
  484. fileReader.onload = function (e : any) {
  485. if (exif.debug) console.log("Got file of length " + e.target.result.byteLength);
  486. handleBinaryFile(e.target.result);
  487. };
  488. fileReader.readAsArrayBuffer(img);
  489. }
  490. }
  491. function findEXIFinJPEG(file: ArrayBuffer) {
  492. const dataView = new DataView(file);
  493. if (exif.debug) console.log("Got file of length " + file.byteLength);
  494. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  495. if (exif.debug) console.log("Not a valid JPEG");
  496. return false; // not a valid jpeg
  497. }
  498. let offset = 2,
  499. length = file.byteLength,
  500. marker;
  501. while (offset < length) {
  502. if (dataView.getUint8(offset) != 0xFF) {
  503. if (exif.debug) console.log("Not a valid marker at offset " + offset + ", found: " + dataView.getUint8(
  504. offset));
  505. return false; // not a valid marker, something is wrong
  506. }
  507. marker = dataView.getUint8(offset + 1);
  508. if (exif.debug) console.log(marker);
  509. // we could implement handling for other markers here,
  510. // but we're only looking for 0xFFE1 for EXIF data
  511. if (marker == 225) {
  512. if (exif.debug) console.log("Found 0xFFE1 marker");
  513. return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
  514. // offset += 2 + file.getShortAt(offset+2, true);
  515. } else {
  516. offset += 2 + dataView.getUint16(offset + 2);
  517. }
  518. }
  519. }
  520. function findIPTCinJPEG(file: ArrayBuffer) {
  521. const dataView = new DataView(file);
  522. if (exif.debug) console.log("Got file of length " + file.byteLength);
  523. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  524. if (exif.debug) console.log("Not a valid JPEG");
  525. return false; // not a valid jpeg
  526. }
  527. let offset = 2,
  528. length = file.byteLength;
  529. const isFieldSegmentStart = function (dataView, offset: number) {
  530. return (
  531. dataView.getUint8(offset) === 0x38 &&
  532. dataView.getUint8(offset + 1) === 0x42 &&
  533. dataView.getUint8(offset + 2) === 0x49 &&
  534. dataView.getUint8(offset + 3) === 0x4D &&
  535. dataView.getUint8(offset + 4) === 0x04 &&
  536. dataView.getUint8(offset + 5) === 0x04
  537. );
  538. };
  539. while (offset < length) {
  540. if (isFieldSegmentStart(dataView, offset)) {
  541. // Get the length of the name header (which is padded to an even number of bytes)
  542. var nameHeaderLength = dataView.getUint8(offset + 7);
  543. if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1;
  544. // Check for pre photoshop 6 format
  545. if (nameHeaderLength === 0) {
  546. // Always 4
  547. nameHeaderLength = 4;
  548. }
  549. var startOffset = offset + 8 + nameHeaderLength;
  550. var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength);
  551. return readIPTCData(file, startOffset, sectionLength);
  552. break;
  553. }
  554. // Not the marker, continue searching
  555. offset++;
  556. }
  557. }
  558. const IptcFieldMap = {
  559. 0x78: 'caption',
  560. 0x6E: 'credit',
  561. 0x19: 'keywords',
  562. 0x37: 'dateCreated',
  563. 0x50: 'byline',
  564. 0x55: 'bylineTitle',
  565. 0x7A: 'captionWriter',
  566. 0x69: 'headline',
  567. 0x74: 'copyright',
  568. 0x0F: 'category'
  569. };
  570. function readIPTCData(file: ArrayBuffer, startOffset: number, sectionLength: number) {
  571. const dataView = new DataView(file);
  572. let data = {};
  573. let fieldValue, fieldName, dataSize, segmentType, segmentSize;
  574. let segmentStartPos = startOffset;
  575. while (segmentStartPos < startOffset + sectionLength) {
  576. if (dataView.getUint8(segmentStartPos) === 0x1C && dataView.getUint8(segmentStartPos + 1) === 0x02) {
  577. segmentType = dataView.getUint8(segmentStartPos + 2);
  578. if (segmentType in IptcFieldMap) {
  579. dataSize = dataView.getInt16(segmentStartPos + 3);
  580. segmentSize = dataSize + 5;
  581. fieldName = IptcFieldMap[segmentType];
  582. fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize);
  583. // Check if we already stored a value with this name
  584. if (data.hasOwnProperty(fieldName)) {
  585. // Value already stored with this name, create multivalue field
  586. if (data[fieldName] instanceof Array) {
  587. data[fieldName].push(fieldValue);
  588. } else {
  589. data[fieldName] = [data[fieldName], fieldValue];
  590. }
  591. } else {
  592. data[fieldName] = fieldValue;
  593. }
  594. }
  595. }
  596. segmentStartPos++;
  597. }
  598. return data;
  599. }
  600. function readTags(file: DataView, tiffStart: number, dirStart: number, strings: any[], bigEnd: number) {
  601. let entries = file.getUint16(dirStart, !bigEnd),
  602. tags = {},
  603. entryOffset, tag;
  604. for (let i = 0; i < entries; i++) {
  605. entryOffset = dirStart + i * 12 + 2;
  606. tag = strings[file.getUint16(entryOffset, !bigEnd)];
  607. if (!tag && exif.debug) console.log("Unknown tag: " + file.getUint16(entryOffset, !bigEnd));
  608. tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
  609. }
  610. return tags;
  611. }
  612. function readTagValue(file: DataView, entryOffset: number, tiffStart: number, dirStart: number, bigEnd: number) {
  613. let type = file.getUint16(entryOffset + 2, !bigEnd),
  614. numValues = file.getUint32(entryOffset + 4, !bigEnd),
  615. valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart,
  616. offset,
  617. vals, val, n,
  618. numerator, denominator;
  619. switch (type) {
  620. case 1: // byte, 8-bit unsigned int
  621. case 7: // undefined, 8-bit byte, value depending on field
  622. if (numValues == 1) {
  623. return file.getUint8(entryOffset + 8, !bigEnd);
  624. } else {
  625. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  626. vals = [];
  627. for (n = 0; n < numValues; n++) {
  628. vals[n] = file.getUint8(offset + n);
  629. }
  630. return vals;
  631. }
  632. case 2: // ascii, 8-bit byte
  633. offset = numValues > 4 ? valueOffset : (entryOffset + 8);
  634. return getStringFromDB(file, offset, numValues - 1);
  635. case 3: // short, 16 bit int
  636. if (numValues == 1) {
  637. return file.getUint16(entryOffset + 8, !bigEnd);
  638. } else {
  639. offset = numValues > 2 ? valueOffset : (entryOffset + 8);
  640. vals = [];
  641. for (n = 0; n < numValues; n++) {
  642. vals[n] = file.getUint16(offset + 2 * n, !bigEnd);
  643. }
  644. return vals;
  645. }
  646. case 4: // long, 32 bit int
  647. if (numValues == 1) {
  648. return file.getUint32(entryOffset + 8, !bigEnd);
  649. } else {
  650. vals = [];
  651. for (n = 0; n < numValues; n++) {
  652. vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd);
  653. }
  654. return vals;
  655. }
  656. case 5: // rational = two long values, first is numerator, second is denominator
  657. if (numValues == 1) {
  658. numerator = file.getUint32(valueOffset, !bigEnd);
  659. denominator = file.getUint32(valueOffset + 4, !bigEnd);
  660. val = new Number(numerator / denominator);
  661. val.numerator = numerator;
  662. val.denominator = denominator;
  663. return val;
  664. } else {
  665. vals = [];
  666. for (n = 0; n < numValues; n++) {
  667. numerator = file.getUint32(valueOffset + 8 * n, !bigEnd);
  668. denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd);
  669. vals[n] = new Number(numerator / denominator);
  670. vals[n].numerator = numerator;
  671. vals[n].denominator = denominator;
  672. }
  673. return vals;
  674. }
  675. case 9: // slong, 32 bit signed int
  676. if (numValues == 1) {
  677. return file.getInt32(entryOffset + 8, !bigEnd);
  678. } else {
  679. vals = [];
  680. for (n = 0; n < numValues; n++) {
  681. vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd);
  682. }
  683. return vals;
  684. }
  685. case 10: // signed rational, two slongs, first is numerator, second is denominator
  686. if (numValues == 1) {
  687. return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd);
  688. } else {
  689. vals = [];
  690. for (n = 0; n < numValues; n++) {
  691. vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 *
  692. n, !bigEnd);
  693. }
  694. return vals;
  695. }
  696. }
  697. }
  698. /**
  699. * Given an IFD (Image File Directory) start offset
  700. * returns an offset to next IFD or 0 if it's the last IFD.
  701. */
  702. function getNextIFDOffset(dataView: DataView, dirStart: number, bigEnd: number) {
  703. //the first 2bytes means the number of directory entries contains in this IFD
  704. var entries = dataView.getUint16(dirStart, !bigEnd);
  705. // After last directory entry, there is a 4bytes of data,
  706. // it means an offset to next IFD.
  707. // If its value is '0x00000000', it means this is the last IFD and there is no linked IFD.
  708. return dataView.getUint32(dirStart + 2 + entries * 12, !bigEnd); // each entry is 12 bytes long
  709. }
  710. function readThumbnailImage(dataView: DataView, tiffStart: number, firstIFDOffset: number, bigEnd: number) {
  711. // get the IFD1 offset
  712. const IFD1OffsetPointer = getNextIFDOffset(dataView, tiffStart + firstIFDOffset, bigEnd);
  713. if (!IFD1OffsetPointer) {
  714. // console.log('******** IFD1Offset is empty, image thumb not found ********');
  715. return {};
  716. } else if (IFD1OffsetPointer > dataView.byteLength) { // this should not happen
  717. // console.log('******** IFD1Offset is outside the bounds of the DataView ********');
  718. return {};
  719. }
  720. // console.log('******* thumbnail IFD offset (IFD1) is: %s', IFD1OffsetPointer);
  721. let thumbTags : any = readTags(dataView, tiffStart, tiffStart + IFD1OffsetPointer, IFD1Tags, bigEnd)
  722. // EXIF 2.3 specification for JPEG format thumbnail
  723. // If the value of Compression(0x0103) Tag in IFD1 is '6', thumbnail image format is JPEG.
  724. // Most of Exif image uses JPEG format for thumbnail. In that case, you can get offset of thumbnail
  725. // by JpegIFOffset(0x0201) Tag in IFD1, size of thumbnail by JpegIFByteCount(0x0202) Tag.
  726. // Data format is ordinary JPEG format, starts from 0xFFD8 and ends by 0xFFD9. It seems that
  727. // JPEG format and 160x120pixels of size are recommended thumbnail format for Exif2.1 or later.
  728. if (thumbTags['Compression'] && typeof Blob !== 'undefined') {
  729. // console.log('Thumbnail image found!');
  730. switch (thumbTags['Compression']) {
  731. case 6:
  732. // console.log('Thumbnail image format is JPEG');
  733. if (thumbTags.JpegIFOffset && thumbTags.JpegIFByteCount) {
  734. // extract the thumbnail
  735. var tOffset = tiffStart + thumbTags.JpegIFOffset;
  736. var tLength = thumbTags.JpegIFByteCount;
  737. thumbTags['blob'] = new Blob([new Uint8Array(dataView.buffer, tOffset, tLength)], {
  738. type: 'image/jpeg'
  739. });
  740. }
  741. break;
  742. case 1:
  743. console.log("Thumbnail image format is TIFF, which is not implemented.");
  744. break;
  745. default:
  746. console.log("Unknown thumbnail image format '%s'", thumbTags['Compression']);
  747. }
  748. } else if (thumbTags['PhotometricInterpretation'] == 2) {
  749. console.log("Thumbnail image format is RGB, which is not implemented.");
  750. }
  751. return thumbTags;
  752. }
  753. function getStringFromDB(buffer: DataView, start: number, length: number) {
  754. let outstr = "";
  755. for (let n = start; n < start + length; n++) {
  756. outstr += String.fromCharCode(buffer.getUint8(n));
  757. }
  758. return outstr;
  759. }
  760. function readEXIFData(file: DataView, start: number) {
  761. if (getStringFromDB(file, start, 4) != "Exif") {
  762. if (exif.debug) console.log("Not valid EXIF data! " + getStringFromDB(file, start, 4));
  763. return false;
  764. }
  765. let bigEnd,
  766. tags, tag,
  767. exifData, gpsData,
  768. tiffOffset = start + 6;
  769. // test for TIFF validity and endianness
  770. if (file.getUint16(tiffOffset) == 0x4949) {
  771. bigEnd = false;
  772. } else if (file.getUint16(tiffOffset) == 0x4D4D) {
  773. bigEnd = true;
  774. } else {
  775. if (exif.debug) console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
  776. return false;
  777. }
  778. if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002A) {
  779. if (exif.debug) console.log("Not valid TIFF data! (no 0x002A)");
  780. return false;
  781. }
  782. const firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd);
  783. if (firstIFDOffset < 0x00000008) {
  784. if (exif.debug) console.log("Not valid TIFF data! (First offset less than 8)", file.getUint32(tiffOffset + 4,
  785. !bigEnd));
  786. return false;
  787. }
  788. tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd);
  789. if (tags.ExifIFDPointer) {
  790. exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
  791. for (tag in exifData) {
  792. switch (tag) {
  793. case "LightSource":
  794. case "Flash":
  795. case "MeteringMode":
  796. case "ExposureProgram":
  797. case "SensingMethod":
  798. case "SceneCaptureType":
  799. case "SceneType":
  800. case "CustomRendered":
  801. case "WhiteBalance":
  802. case "GainControl":
  803. case "Contrast":
  804. case "Saturation":
  805. case "Sharpness":
  806. case "SubjectDistanceRange":
  807. case "FileSource":
  808. exifData[tag] = StringValues[tag][exifData[tag]];
  809. break;
  810. case "ExifVersion":
  811. case "FlashpixVersion":
  812. exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2],
  813. exifData[tag][3]);
  814. break;
  815. case "ComponentsConfiguration":
  816. exifData[tag] =
  817. StringValues.Components[exifData[tag][0]] +
  818. StringValues.Components[exifData[tag][1]] +
  819. StringValues.Components[exifData[tag][2]] +
  820. StringValues.Components[exifData[tag][3]];
  821. break;
  822. }
  823. tags[tag] = exifData[tag];
  824. }
  825. }
  826. if (tags.GPSInfoIFDPointer) {
  827. gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
  828. for (tag in gpsData) {
  829. switch (tag) {
  830. case "GPSVersionID":
  831. gpsData[tag] = gpsData[tag][0] +
  832. "." + gpsData[tag][1] +
  833. "." + gpsData[tag][2] +
  834. "." + gpsData[tag][3];
  835. break;
  836. }
  837. tags[tag] = gpsData[tag];
  838. }
  839. }
  840. // extract thumbnail
  841. tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd);
  842. return tags;
  843. }
  844. function findXMPinJPEG(file: ArrayBuffer) {
  845. if (!('DOMParser' in self)) {
  846. // console.warn('XML parsing not supported without DOMParser');
  847. return;
  848. }
  849. const dataView = new DataView(file);
  850. if (exif.debug) console.log("Got file of length " + file.byteLength);
  851. if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
  852. if (exif.debug) console.log("Not a valid JPEG");
  853. return false; // not a valid jpeg
  854. }
  855. let offset = 2,
  856. length = file.byteLength,
  857. dom = new DOMParser();
  858. while (offset < (length - 4)) {
  859. if (getStringFromDB(dataView, offset, 4) == "http") {
  860. const startOffset = offset - 1;
  861. const sectionLength = dataView.getUint16(offset - 2) - 1;
  862. let xmpString = getStringFromDB(dataView, startOffset, sectionLength)
  863. const xmpEndIndex = xmpString.indexOf('xmpmeta>') + 8;
  864. xmpString = xmpString.substring(xmpString.indexOf('<x:xmpmeta'), xmpEndIndex);
  865. const indexOfXmp = xmpString.indexOf('x:xmpmeta') + 10
  866. //Many custom written programs embed xmp/xml without any namespace. Following are some of them.
  867. //Without these namespaces, XML is thought to be invalid by parsers
  868. xmpString = xmpString.slice(0, indexOfXmp) +
  869. 'xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" ' +
  870. 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
  871. 'xmlns:tiff="http://ns.adobe.com/tiff/1.0/" ' +
  872. 'xmlns:plus="http://schemas.android.com/apk/lib/com.google.android.gms.plus" ' +
  873. 'xmlns:ext="http://www.gettyimages.com/xsltExtension/1.0" ' +
  874. 'xmlns:exif="http://ns.adobe.com/exif/1.0/" ' +
  875. 'xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" ' +
  876. 'xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" ' +
  877. 'xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" ' +
  878. 'xmlns:xapGImg="http://ns.adobe.com/xap/1.0/g/img/" ' +
  879. 'xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" ' +
  880. xmpString.slice(indexOfXmp)
  881. var domDocument = dom.parseFromString(xmpString, 'text/xml');
  882. return xml2Object(domDocument);
  883. } else {
  884. offset++;
  885. }
  886. }
  887. }
  888. function xml2json(xml: any) {
  889. var json = {};
  890. if (xml.nodeType == 1) { // element node
  891. if (xml.attributes.length > 0) {
  892. json['@attributes'] = {};
  893. for (var j = 0; j < xml.attributes.length; j++) {
  894. var attribute = xml.attributes.item(j);
  895. json['@attributes'][attribute.nodeName] = attribute.nodeValue;
  896. }
  897. }
  898. } else if (xml.nodeType == 3) { // text node
  899. return xml.nodeValue;
  900. }
  901. // deal with children
  902. if (xml.hasChildNodes()) {
  903. for (var i = 0; i < xml.childNodes.length; i++) {
  904. var child = xml.childNodes.item(i);
  905. var nodeName = child.nodeName;
  906. if (json[nodeName] == null) {
  907. json[nodeName] = xml2json(child);
  908. } else {
  909. if (json[nodeName].push == null) {
  910. var old = json[nodeName];
  911. json[nodeName] = [];
  912. json[nodeName].push(old);
  913. }
  914. json[nodeName].push(xml2json(child));
  915. }
  916. }
  917. }
  918. return json;
  919. }
  920. function xml2Object(xml: any) {
  921. try {
  922. var obj = {};
  923. if (xml.children.length > 0) {
  924. for (var i = 0; i < xml.children.length; i++) {
  925. var item = xml.children.item(i);
  926. var attributes = item.attributes;
  927. for (var idx in attributes) {
  928. var itemAtt = attributes[idx];
  929. var dataKey = itemAtt.nodeName;
  930. var dataValue = itemAtt.nodeValue;
  931. if (dataKey !== undefined) {
  932. obj[dataKey] = dataValue;
  933. }
  934. }
  935. var nodeName = item.nodeName;
  936. if (typeof (obj[nodeName]) == "undefined") {
  937. obj[nodeName] = xml2json(item);
  938. } else {
  939. if (typeof (obj[nodeName].push) == "undefined") {
  940. var old = obj[nodeName];
  941. obj[nodeName] = [];
  942. obj[nodeName].push(old);
  943. }
  944. obj[nodeName].push(xml2json(item));
  945. }
  946. }
  947. } else {
  948. obj = xml.textContent;
  949. }
  950. return obj;
  951. } catch (e) {
  952. console.log(e.message);
  953. }
  954. }