Initial release

This commit is contained in:
icezhb 2025-01-03 19:07:17 +08:00
commit a83111a2eb
11 changed files with 935 additions and 0 deletions

162
README.md Normal file
View File

@ -0,0 +1,162 @@
# Image EXIF Reader
一个简单的图片 EXIF 数据读取器,支持读取 JPEG、TIFF 等格式图片的 EXIF 元数据。
## 功能特点
- 支持多种图片格式JPEG/JPG、TIFF、HEIC/HEIF、PNG、WebP
- 读取常见 EXIF 数据:相机信息、拍摄参数、镜头信息等
- 简单易用的 API
- 支持浏览器直接使用
- 轻量级,无依赖
## 快速开始
### 1. 直接使用
html
<!-- 引入插件 -->
<script src="dist/imageExifReader.min.js"></script>
<!-- HTML -->
<input type="file" id="imageInput" accept="image/jpeg,image/jpg">
<div id="exifData"></div>
<script>
// 初始化读取器
const reader = new ImageExifReader();
// 处理文件选择
document.getElementById('imageInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
reader.readExifData(file, function(error, exifData) {
if (error) {
console.error('读取 EXIF 数据时出错:', error);
return;
}
console.log('EXIF 数据:', exifData);
});
});
</script>
### 2. NPM 安装
bash
npm install image-exif-reader
javascript
import ImageExifReader from 'image-exif-reader';
const reader = new ImageExifReader();
function handleImageUpload(file) {
reader.readExifData(file, (error, exifData) => {
if (error) {
console.error('读取失败:', error);
return;
}
console.log('EXIF 数据:', exifData);
});
}
## API 文档
### ImageExifReader
#### readExifData(file, callback)
读取图片的 EXIF 数据。
参数:
- file: File - 图片文件对象
- callback: Function(error, exifData)
- error: Error | null - 错误信息
- exifData: Object - EXIF 数据对象
返回数据示例:
javascript
{
Make: "Panasonic",
Model: "DC-G9",
DateTime: "2022-03-26 18:20:19",
ExposureTime: "1/125",
FNumber: "f/1.7",
FocalLength: "25mm",
ISOSpeedRatings: 400,
LensModel: "LUMIX G 25mm F1.7"
}
## 支持的 EXIF 标签
### 基本信息
- Make: 相机制造商
- Model: 相机型号
- DateTime: 拍摄时间
- Software: 软件信息
### 拍摄参数
- ExposureTime: 曝光时间
- FNumber: 光圈值
- ISOSpeedRatings: ISO 感光度
- FocalLength: 焦距
- ExposureMode: 曝光模式
- WhiteBalance: 白平衡
### 镜头信息
- LensModel: 镜头型号
- LensSerialNumber: 镜头序列号
- FocalLengthIn35mmFilm: 35mm 等效焦距
### 图像信息
- XResolution: X 轴分辨率
- YResolution: Y 轴分辨率
- ResolutionUnit: 分辨率单位
## 开发
bash
安装依赖
npm install
构建项目
npm run build
运行示例
npm run dev
运行测试
npm test
## 示例
查看 `examples` 目录中的完整示例代码。
## 浏览器兼容性
- Chrome 50+
- Firefox 50+
- Safari 11+
- Edge 18+
## 许可证
MIT
## 作者
[你的名字]
## 贡献
欢迎提交 Issue 和 Pull Request。
## 更新日志
### 1.0.0
- 初始版本发布
- 支持 JPEG/TIFF 格式
- 基础 EXIF 数据读取

4
examples/app.js Normal file
View File

@ -0,0 +1,4 @@
document.addEventListener('DOMContentLoaded', function() {
const exifReader = new ImageExifReader();
// ... 保持原有代码 ...
});

22
examples/demo.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>EXIF Reader Demo</title>
</head>
<body>
<input type="file" id="imageInput" accept="image/jpeg,image/jpg">
<div id="result"></div>
<script src="../dist/imageExifReader.min.js"></script>
<script>
const reader = new ImageExifReader();
document.getElementById('imageInput').onchange = function(e) {
const file = e.target.files[0];
reader.readExifData(file, (error, data) => {
document.getElementById('result').innerHTML =
`<pre>${JSON.stringify(data, null, 2)}</pre>`;
});
};
</script>
</body>
</html>

21
examples/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>EXIF 读取器示例</title>
<style>
/* 保持原有样式 */
</style>
</head>
<body>
<div class="container">
<h2>EXIF 读取器示例</h2>
<input type="file" id="imageInput" accept="image/jpeg,image/jpg,image/tiff,image/heic,image/heif,image/png,image/webp">
<div id="imagePreview"></div>
<div id="exifData"></div>
</div>
<script src="../dist/imageExifReader.min.js"></script>
<script src="app.js"></script>
</body>
</html>

486
imageExifReader.js Normal file
View File

@ -0,0 +1,486 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node.js
module.exports = factory();
} else {
// Browser globals
root.ImageExifReader = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
function ImageExifReader() {
this.debug = true;
this.tags = {
// 基本 EXIF 标签
0x0100: "ImageWidth",
0x0101: "ImageHeight",
0x0112: "Orientation",
0x0132: "DateTime",
0x010F: "Make",
0x0110: "Model",
// 富士相机特有的标签
0x0128: "ResolutionUnit",
0x011A: "XResolution",
0x011B: "YResolution",
0x0213: "YCbCrPositioning",
// EXIF IFD 指针和常用标签
0x8769: "ExifIFDPointer",
0x829A: "ExposureTime",
0x829D: "FNumber",
0x8827: "ISOSpeedRatings",
0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized",
0x920A: "FocalLength",
0xA402: "ExposureMode",
0xA403: "WhiteBalance",
0xA406: "SceneCaptureType",
0x9286: "UserComment",
0xA405: "FocalLengthIn35mmFilm",
0xA420: "ImageUniqueID",
0xA431: "SerialNumber",
0xA432: "LensInfo",
0xA433: "LensMake",
0xA434: "LensModel",
0xA435: "LensSerialNumber"
};
// 添加标签类型定义
this.tagTypes = {
Make: 'string',
Model: 'string',
DateTime: 'datetime',
DateTimeOriginal: 'datetime',
DateTimeDigitized: 'datetime',
ExposureTime: 'exposuretime',
FNumber: 'fnumber',
FocalLength: 'focallength',
ISOSpeedRatings: 'number',
LensModel: 'string',
LensSerialNumber: 'string',
XResolution: 'resolution',
YResolution: 'resolution'
};
}
ImageExifReader.prototype.readExifData = function(file, callback) {
// 支持的图片类型
const supportedTypes = [
'image/jpeg',
'image/jpg',
'image/tiff',
'image/heic',
'image/heif',
'image/png',
'image/webp'
];
if (!supportedTypes.some(type => file.type.startsWith(type))) {
callback(new Error(`不支持的文件类型: ${file.type}`));
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const exifData = this.parseExif(e.target.result, file.type);
callback(null, exifData);
} catch (error) {
callback(error);
}
};
reader.onerror = (error) => {
callback(error);
};
reader.readAsArrayBuffer(file);
};
ImageExifReader.prototype.parseExif = function(arrayBuffer, fileType) {
const dataView = new DataView(arrayBuffer);
let offset = 0;
const length = dataView.byteLength;
let exifData = {};
try {
switch (fileType) {
case 'image/jpeg':
case 'image/jpg':
if (dataView.getUint16(0, false) !== 0xFFD8) {
throw new Error('不是有效的 JPEG 图片');
}
// 搜索 APP1 标记
offset = 2;
while (offset < length) {
const marker = dataView.getUint16(offset, false);
offset += 2;
if (marker === 0xFFE1) {
const segmentLength = dataView.getUint16(offset, false);
const exifHeader = this.getStringFromBuffer(dataView, offset + 2, 4);
if (exifHeader === 'Exif') {
const tiffOffset = offset + 8;
const byteOrder = dataView.getUint16(tiffOffset, false);
const bigEnd = byteOrder === 0x4D4D;
if (dataView.getUint16(tiffOffset + 2, !bigEnd) === 42) {
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, !bigEnd);
if (firstIFDOffset > 0) {
this.readIFD(dataView, tiffOffset + firstIFDOffset, !bigEnd, exifData);
}
}
}
offset += segmentLength;
} else if (marker === 0xFFDA) {
break; // 到达图像数据
} else {
offset += dataView.getUint16(offset, false);
}
}
break;
case 'image/tiff':
this.parseTiffHeader(dataView, exifData);
break;
case 'image/heic':
case 'image/heif':
// HEIF 文件以 ftyp 开头
const brand = this.getStringFromBuffer(dataView, 4, 4);
if (!['heic', 'heix', 'hevc', 'hevx'].includes(brand)) {
throw new Error('不是有效的 HEIC/HEIF 图片');
}
this.parseHeicMetadata(dataView, exifData);
return exifData;
case 'image/png':
// PNG 文件以 89 50 4E 47 0D 0A 1A 0A 开头
if (dataView.getUint32(0, false) !== 0x89504E47) {
throw new Error('不是有效的 PNG 图片');
}
this.parsePngMetadata(dataView, exifData);
return exifData;
case 'image/webp':
// WebP 文件以 RIFF....WEBP 开头
const webpHeader = this.getStringFromBuffer(dataView, 0, 4);
if (webpHeader !== 'RIFF') {
throw new Error('不是有效的 WebP 图片');
}
this.parseWebpMetadata(dataView, exifData);
return exifData;
default:
throw new Error(`不支持的文件类型: ${fileType}`);
}
return exifData;
} catch (error) {
console.error('解析元数据时出错:', error);
return exifData;
}
};
ImageExifReader.prototype.parseTiffHeader = function(dataView, exifData) {
try {
// 检查字节序
const byteOrder = dataView.getUint16(0, false);
const bigEnd = byteOrder === 0x4D4D; // 'MM' = big endian
// 验证 TIFF 版本号 (应该是 42)
const tiffMagic = dataView.getUint16(2, !bigEnd);
if (tiffMagic !== 42) {
throw new Error('无效的 TIFF 版本号');
}
// 获取第一个 IFD 的偏移量
const ifdOffset = dataView.getUint32(4, !bigEnd);
if (ifdOffset < 8 || ifdOffset > dataView.byteLength) {
throw new Error('无效的 IFD 偏移量');
}
// 读取 IFD
this.readIFD(dataView, ifdOffset, !bigEnd, exifData);
return exifData;
} catch (error) {
console.error('解析 TIFF 头部时出错:', error);
return exifData;
}
};
ImageExifReader.prototype.parseHeicMetadata = function(dataView, exifData) {
// HEIC/HEIF 解析逻辑
};
ImageExifReader.prototype.parsePngMetadata = function(dataView, exifData) {
// PNG 解析逻辑
};
ImageExifReader.prototype.parseWebpMetadata = function(dataView, exifData) {
// WebP 解析逻辑
};
ImageExifReader.prototype.readIFD = function(dataView, startOffset, bigEnd, exifData) {
try {
const entries = dataView.getUint16(startOffset, bigEnd);
console.log(`IFD 位置: ${startOffset}, 条目数: ${entries}`);
if (entries > 50 || entries === 0) {
console.warn(`可疑的 IFD 条目数量: ${entries}`);
return;
}
let offset = startOffset + 2;
const baseOffset = startOffset - 8;
for (let i = 0; i < entries; i++) {
const tag = dataView.getUint16(offset, bigEnd);
const type = dataView.getUint16(offset + 2, bigEnd);
const numValues = dataView.getUint32(offset + 4, bigEnd);
const valueOffset = dataView.getUint32(offset + 8, bigEnd);
if (type < 1 || type > 12 || numValues > 65535 || valueOffset > dataView.byteLength) {
offset += 12;
continue;
}
try {
if (this.tags[tag]) {
const value = this.readTagValue(dataView, type, numValues, valueOffset, bigEnd, offset, tag);
if (value !== undefined) {
exifData[this.tags[tag]] = value;
console.log(`读取标签 ${this.tags[tag]}: ${value}`);
}
}
if (tag === 0x8769) {
console.log('找到 EXIF IFD 指针,继续解析...');
const exifOffset = baseOffset + valueOffset;
if (exifOffset > 0 && exifOffset < dataView.byteLength) {
this.readIFD(dataView, exifOffset, bigEnd, exifData);
}
}
} catch (e) {
console.error(`处理标签 0x${tag.toString(16)} 时出错:`, e);
}
offset += 12;
}
} catch (e) {
console.error('解析 IFD 时出错:', e);
}
};
ImageExifReader.prototype.readTagValue = function(dataView, type, numValues, valueOffset, bigEnd, baseOffset, tag) {
try {
const tagName = this.tags[tag];
const tagType = this.tagTypes[tagName];
const tiffOffset = 12;
let actualOffset;
if (type === 2) {
actualOffset = numValues <= 4 ? baseOffset + 8 : tiffOffset + valueOffset;
} else if (type === 5) {
actualOffset = tiffOffset + valueOffset;
} else {
actualOffset = numValues <= 4 ? baseOffset + 8 : tiffOffset + valueOffset;
}
let value;
switch (type) {
case 2: // ascii string
if (numValues <= 4) {
const bytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
bytes[i] = (valueOffset >> (i * 8)) & 0xFF;
}
value = this.decodeString(bytes.slice(0, numValues - 1));
} else {
value = this.getStringFromBuffer(dataView, actualOffset, numValues - 1);
}
break;
case 5: // unsigned rational
try {
const numerator = dataView.getUint32(actualOffset, bigEnd);
const denominator = dataView.getUint32(actualOffset + 4, bigEnd);
if (denominator === 0) {
return undefined;
}
value = numerator / denominator;
switch (tagName) {
case 'FocalLength':
if (value > 0 && value < 1000) {
value = Math.round(value);
} else {
return undefined;
}
break;
case 'FNumber':
if (value > 0) {
value = Math.round(value * 10) / 10;
} else {
return undefined;
}
break;
case 'ExposureTime':
if (value > 0) {
if (value < 1) {
const denomRounded = Math.round(1/value);
value = `1/${denomRounded}`;
} else {
value = value.toFixed(1);
}
} else {
return undefined;
}
break;
case 'XResolution':
case 'YResolution':
if (value > 0) {
value = Math.round(value);
} else {
return undefined;
}
break;
}
} catch (e) {
console.error('读取 Rational 值时出错:', e);
return undefined;
}
break;
default:
value = this.readBasicType(dataView, type, numValues, actualOffset, bigEnd);
}
if (value !== undefined && value !== '' && tagType) {
switch (tagType) {
case 'datetime':
if (typeof value === 'string' && value.length >= 19) {
const match = value.match(/(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})/);
if (match) {
value = `${match[1]}-${match[2]}-${match[3]} ${match[4]}:${match[5]}:${match[6]}`;
} else {
return undefined;
}
} else {
return undefined;
}
break;
case 'fnumber':
value = `f/${value}`;
break;
case 'focallength':
value = `${value}mm`;
break;
case 'string':
value = this.cleanString(value);
if (!value) {
return undefined;
}
break;
}
}
return value || undefined;
} catch (error) {
console.error('读取标签值时出错:', error, {
tag: `0x${tag.toString(16)}`,
tagName,
type,
numValues,
valueOffset
});
return undefined;
}
};
ImageExifReader.prototype.readBasicType = function(dataView, type, numValues, offset, bigEnd) {
switch (type) {
case 1: // unsigned byte
if (numValues === 1) {
return dataView.getUint8(offset);
}
var values = new Array(numValues);
for (var i = 0; i < numValues; i++) {
values[i] = dataView.getUint8(offset + i);
}
return values;
case 3: // unsigned short
if (numValues === 1) {
return dataView.getUint16(offset, bigEnd);
}
var values = new Array(numValues);
for (var i = 0; i < numValues; i++) {
values[i] = dataView.getUint16(offset + i * 2, bigEnd);
}
return values;
case 4: // unsigned long
if (numValues === 1) {
return dataView.getUint32(offset, bigEnd);
}
var values = new Array(numValues);
for (var i = 0; i < numValues; i++) {
values[i] = dataView.getUint32(offset + i * 4, bigEnd);
}
return values;
default:
return undefined;
}
};
ImageExifReader.prototype.getStringFromBuffer = function(dataView, start, length) {
try {
const bytes = [];
for (let i = 0; i < length; i++) {
const byte = dataView.getUint8(start + i);
if (byte === 0) break;
bytes.push(byte);
}
return this.decodeString(new Uint8Array(bytes));
} catch (error) {
console.error('读取字符串时出错:', error, {start, length});
return '';
}
};
// 改进字符串清理方法
ImageExifReader.prototype.cleanString = function(str) {
if (typeof str !== 'string') return '';
// 移除控制字符、不可打印字符和特殊字符
return str.replace(/[\x00-\x1F\x7F-\x9F]/g, '')
.replace(/[^\x20-\x7E\u4E00-\u9FFF]/g, '') // 只保留基本ASCII和中文字符
.replace(/\u0000/g, '')
.trim();
};
// 改进字符串解码方法
ImageExifReader.prototype.decodeString = function(bytes) {
try {
// 兼容性更好的字符串解码
return bytes.reduce((str, byte) => {
return str + String.fromCharCode(byte);
}, '');
} catch (e) {
console.error('字符串解码失败:', e);
return '';
}
};
// 添加一个辅助方法来检查数据块的有效性
ImageExifReader.prototype.isValidChunk = function(offset, length, totalLength) {
return offset >= 0 &&
length > 0 &&
offset + length <= totalLength &&
length < totalLength;
};
return ImageExifReader;
}));

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "image-exif-reader",
"version": "1.0.0",
"description": "一个简单的图片 EXIF 数据读取器",
"main": "dist/imageExifReader.min.js",
"module": "src/imageExifReader.js",
"types": "types/index.d.ts",
"scripts": {
"build": "webpack --mode production",
"prepare": "npm run build"
},
"files": [
"dist",
"src",
"types"
],
"repository": {
"type": "git",
"url": "git+https://github.com/yourusername/image-exif-reader.git"
},
"keywords": ["exif", "image", "metadata"],
"author": "zhengice",
"license": "MIT",
"devDependencies": {
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}

135
src/imageExifReader.js Normal file
View File

@ -0,0 +1,135 @@
class ImageExifReader {
constructor() {
this.tags = {
// 基本 EXIF 标签
0x0100: "ImageWidth",
0x0101: "ImageHeight",
0x0112: "Orientation",
0x0132: "DateTime",
0x010F: "Make",
0x0110: "Model",
0x0128: "ResolutionUnit",
0x011A: "XResolution",
0x011B: "YResolution",
0x0213: "YCbCrPositioning",
0x8769: "ExifIFDPointer",
0x829A: "ExposureTime",
0x829D: "FNumber",
0x8827: "ISOSpeedRatings",
0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized",
0x920A: "FocalLength",
0xA402: "ExposureMode",
0xA403: "WhiteBalance",
0xA406: "SceneCaptureType",
0x9286: "UserComment",
0xA405: "FocalLengthIn35mmFilm",
0xA420: "ImageUniqueID",
0xA431: "SerialNumber",
0xA432: "LensInfo",
0xA433: "LensMake",
0xA434: "LensModel",
0xA435: "LensSerialNumber"
};
this.tagTypes = {
Make: 'string',
Model: 'string',
DateTime: 'datetime',
DateTimeOriginal: 'datetime',
DateTimeDigitized: 'datetime',
ExposureTime: 'exposuretime',
FNumber: 'fnumber',
FocalLength: 'focallength',
ISOSpeedRatings: 'number',
LensModel: 'string',
LensSerialNumber: 'string',
XResolution: 'resolution',
YResolution: 'resolution'
};
}
readExifData(file, callback) {
try {
const reader = new FileReader();
reader.onerror = () => callback(new Error('文件读取失败'));
reader.onload = (e) => {
try {
const exifData = this.parseExif(e.target.result, file.type);
callback(null, exifData);
} catch (error) {
callback(error);
}
};
reader.readAsArrayBuffer(file);
} catch (error) {
callback(error);
}
}
parseExif(arrayBuffer, fileType) {
const dataView = new DataView(arrayBuffer);
const exifData = {};
try {
switch (fileType) {
case 'image/jpeg':
case 'image/jpg':
if (dataView.getUint16(0, false) !== 0xFFD8) {
throw new Error('不是有效的 JPEG 图片');
}
let offset = 2;
while (offset < dataView.byteLength) {
const marker = dataView.getUint16(offset, false);
offset += 2;
if (marker === 0xFFE1) {
const segmentLength = dataView.getUint16(offset, false);
const exifHeader = this.getStringFromBuffer(dataView, offset + 2, 4);
if (exifHeader === 'Exif') {
const tiffOffset = offset + 8;
const byteOrder = dataView.getUint16(tiffOffset, false);
const bigEnd = byteOrder === 0x4D4D;
if (dataView.getUint16(tiffOffset + 2, !bigEnd) === 42) {
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, !bigEnd);
if (firstIFDOffset > 0) {
this.readIFD(dataView, tiffOffset + firstIFDOffset, !bigEnd, exifData);
}
}
}
offset += segmentLength;
} else if (marker === 0xFFDA) {
break;
} else {
offset += dataView.getUint16(offset, false);
}
}
break;
case 'image/tiff':
this.parseTiffHeader(dataView, exifData);
break;
}
return this.validateData(exifData) ? exifData : {};
} catch (error) {
return {};
}
}
// ... 其他方法保持不变,但删除所有 console 输出 ...
}
// 支持多种模块系统
if (typeof module !== 'undefined' && module.exports) {
module.exports = ImageExifReader;
}
export default ImageExifReader;
if (typeof window !== 'undefined') {
window.ImageExifReader = ImageExifReader;
}

38
test.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>EXIF 读取器测试</title>
<style>
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
font-family: Arial, sans-serif;
}
#exifData {
margin-top: 20px;
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
}
.preview-image {
max-width: 100%;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h2>EXIF 读取器测试</h2>
<input type="file" id="imageInput" accept="image/jpeg,image/jpg,image/tiff,image/heic,image/heif,image/png,image/webp">
<div id="imagePreview"></div>
<div id="exifData"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
<script src="imageExifReader.js"></script>
<script src="test.js"></script>
</body>
</html>

6
test.js Normal file
View File

@ -0,0 +1,6 @@
const ImageExifReader = require('image-exif-reader');
// 或
import ImageExifReader from 'image-exif-reader';
const reader = new ImageExifReader();
console.log('插件加载成功');

18
types/index.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
interface ExifData {
Make?: string;
Model?: string;
DateTime?: string;
ExposureTime?: string;
FNumber?: string;
FocalLength?: string;
ISOSpeedRatings?: number;
LensModel?: string;
[key: string]: any;
}
declare class ImageExifReader {
constructor();
readExifData(file: File, callback: (error: Error | null, data?: ExifData) => void): void;
}
export default ImageExifReader;

15
webpack.config.js Normal file
View File

@ -0,0 +1,15 @@
const path = require('path');
module.exports = {
entry: './src/imageExifReader.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'imageExifReader.min.js',
library: {
name: 'ImageExifReader',
type: 'umd',
export: 'default'
},
globalObject: 'this'
}
};