ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

10种Exif标签类型一文讲透:node-exif核心算法extractExifEntry源码深度剖析

10种Exif标签类型一文讲透:node-exif核心算法extractExifEntry源码深度剖析 10种Exif标签类型一文讲透node-exif核心算法extractExifEntry源码深度剖析【免费下载链接】node-exifA node.js library to extract Exif metadata from images.项目地址: https://gitcode.com/gh_mirrors/no/node-exifnode-exif是一个 Node.js 的Exif 元数据提取库只需一行代码就能从 JPEG 图片中解析出相机型号、拍摄时间、光圈快门、GPS 坐标等信息。本文将深入 lib/exif/ExifImage.js 中的核心函数extractExifEntry把 Exif 标签的10 种数据类型一次讲透并带你完整走一遍从 JPEG 字节流到结构化 JSON 的解析全流程。上图是测试目录中的一张索尼 ILCE-6000 样片node-exif 能从中提取出曝光参数、镜头信息E PZ 16-50mm等完整 Exif 标签。一、30秒认识 node-exif安装与快速上手安装只需一条命令npm install exif使用方式极其简单完整说明见 README.mdvar ExifImage require(exif).ExifImage; new ExifImage({ image: myImage.jpg }, function (error, exifData) { console.log(exifData); // 结构化后的全部 Exif 数据 });解析结果固定分为 6 个分组对应 JPEG 中不同的 IFD信息字典区域分组对应区域典型标签imageIFD0主图信息Make、Model、OrientationthumbnailIFD1缩略图ThumbnailOffset、ThumbnailLengthexifExif IFDFNumber、ISO、FocalLengthgpsGPS IFDGPSLatitude、GPSLongitudeinteroperabilityInterop IFDInteropIndexmakernote厂商私有区富士/奥林巴斯等私有标签二、解析全流程从 JPEG 字节流到标签表在理解 10 种标签类型之前先看清 node-exif 是如何找到 Exif 数据的。整个入口是 lib/exif/ExifImage.js 中的processImage校验 JPEG 头文件前 2 字节必须是0xFFD8否则抛出NOT_A_JPEG错误扫描 APP1 段循环读取以0xFF开头的各段找到0xFFE1APP1 段即停验证 Exif 魔数段内前 6 字节必须是Exif\0\0判定字节序TIFF 头为0x4949II小端或0x4D4DMM大端并校验0x002A魔数。文件名short-ascii-II正是小端II编码的佳能 S40 样片与大端MM样片short-ascii-MM.jpg一起覆盖了两种字节序的测试场景预期结果保存在同目录的 test/short-ascii-II.jpg.json 中。字节序判定完成后extractExifData依次解析 IFD0 → IFD1 → Exif IFD → GPS IFD → Interop IFD → Makernote每个 IFD 循环调用的都是同一个函数——extractExifEntry。这就是本文的主角。三、核心算法extractExifEntry 如何解码一条标签先建立背景知识Exif 数据本质是TIFF 格式每个 IFD 是一条字典链表而每条标签记录固定 12 个字节┌─────────┬─────────┬──────────┬────────────────────┐ │ Tag ID │ Format │ Count │ Value / Offset │ │ 2 字节 │ 2 字节 │ 4 字节 │ 4 字节 │ └─────────┴─────────┴──────────┴────────────────────┘Tag ID标签编号如0x829D是光圈 FNumberFormat数据类型也就是本文重点的10 种标签类型Count该类型的数据个数Value值本身或指向真实存储位置的偏移指针。extractExifEntry的骨架lib/exif/ExifImage.js分三步// 第1步读出 Tag ID、Format、Count 三个字段 var entry { tagId : null, format : data.getShort(entryOffset 2, isBigEndian), components: data.getLong(entryOffset 4, isBigEndian), ... }; // 第2步在 TAGS 字典中查名字查不到直接丢弃 if (tags tags[entry.tagId] typeof tags[entry.tagId] function) { entry.tagName tags[entry.tagId].call(this, entry); } else if (tags tags[entry.tagId]) { entry.tagName tags[entry.tagId]; } else { return false; // 未知标签安全跳过 } // 第3步根据 format 的 10 种类型走 switch 分支解码 switch (entry.format) { case 0x0001: // BYTE ... case 0x0002: // ASCII ... // 共 10 个 case } 注意isBigEndian参数被传入了每一次字节读取——这正是大端/小端通吃的关键。底层的getShort、getLong、getString等方法都由 lib/exif/Buffer.js 扩展在 Buffer 原型上例如getLong会按字节序拼接 4 个字节并做无符号化处理。标签名查表用的字典是ExifImage.TAGSlib/exif/ExifImage.js包含约 400 个 Exif/通用 TIFF 标签和 32 个 GPS 标签。3.1 10种Exif标签类型完整清单switch 分支里恰好覆盖 TIFF 规范定义的10 种数据格式这是理解一切 Exif 解析器的钥匙序号常量类型名每元素大小源码解码方式真实标签举例10x0001BYTE无符号字节1 字节循环getByteResolutionUnit20x0002ASCII字符串1 字节getString 去尾部\0MakeSONY30x0003SHORT无符号短整型2 字节循环getShortOrientation、ISO40x0004LONG无符号长整型4 字节循环getLongExifOffset、ThumbnailLength50x0005RATIONAL无符号分数8 字节分子getLong÷ 分母getLongExposureTime 1/8060x0006SBYTE有符号字节1 字节getSignedByte有符号小值70x0007UNDEFINED原始字节1 字节slice整段保留ExifVersion0230二进制形式80x0008SSHORT有符号短整型2 字节getSignedShort有符号中值90x0009SLONG有符号长整型4 字节getSignedLong有符号大值100x000ASRATIONAL有符号分数8 字节有符号分子÷分母ExposureCompensation三个值得记住的设计细节分数即两整数RATIONAL 由 4 字节分子 4 字节分母组成源码直接相除得到浮点数。所以索尼样片的ExposureTime: 6、FNumber: 8都来自这一步除法UNDEFINED 不解释类型 7 表示规范未定义的字节串如 ExifVersionnode-exif 用slice原样保留为 Buffer不做臆测——你在 test/sony-alpha-6000.jpg.json 中能看到ExifVersion:{type:Buffer,data:[48,50,51,48]}即 ASCII 的0230未知类型直接返回 falseswitch 的default分支丢弃该条保证遇到脏数据不会崩溃。3.2 黄金法则值放哪内联还是指针第 4 个字段的 4 字节到底是值本身还是指针源码给出了每条类型的判定阈值这是整个算法最精妙的部分case 0x0001: // BYTE≤4 个元素直接内联 entry.valueOffset (entry.components 4) ? entryOffset 8 : data.getLong(entryOffset 8, isBigEndian) tiffOffset; case 0x0005: // RATIONAL一个元素就要 8 字节永远走指针 entry.valueOffset data.getLong(entryOffset 8, isBigEndian) tiffOffset;规则一句话总结值总字节数 ≤ 4 就内联写在第 9~12 字节否则第 9~12 字节存的是相对 TIFF 头的偏移量。注意指针还原时要 tiffOffset把相对 TIFF 头的偏移换算成相对文件开头的偏移——缩略图数据定位就靠它。四、IFD 链与 MakerNote标签的六大归宿extractExifDatalib/exif/ExifImage.js中每个 IFD 都是相同的三段式循环读条目数 → 逐条调用extractExifEntry步长 12 字节→ 写入对应分组。evil1.jpg 是一张带缩略图的佳能样片解析时thumbnail分组会得到 ThumbnailOffset/ThumbnailLength可用于从原文件中切出缩略图。4.1 安全防线maxEntries 防恶意图片每个 IFD 的条目数都受选项钳制如ifd0MaxEntries、maxEntries默认上限DEFAULT_MAX_ENTRIES 128。这是典型的DoS 防护恶意构造的图片若声明百万条标签解析也最多只跑 128 条就停。4.2 MakerNote六大厂商私有格式0x927CMakerNote比较特殊——它不是普通标签而是一整块厂商私有数据。源码在提取时记下它的偏移然后按头部分流头部特征厂商解析模块OLYMP\0奥林巴斯lib/exif/makernotes/olympus.jsAGFA \0爱克发lib/exif/makernotes/agfa.jsEPSON\0爱普生lib/exif/makernotes/epson.jsFUJIFILM富士lib/exif/makernotes/fujifilm.jsPanasonic松下lib/exif/makernotes/panasonic.jsSANYO三洋lib/exif/makernotes/sanyo.js⚠️ 一个真实的反面教材索尼样片 sony-alpha-6000.jpg 的 MakerNote 头部是SONY DSC不在支持列表中所以其 test/sony-alpha-6000.jpg.json 里makernote分组是makernote: { error: Unable to extract Makernote information as it is in an unsupported or unrecognized format. }想支持新厂商在makernotes/目录仿写一个模块、在分流处加一行头部判断即可。五、动手验证4 张官方测试图覆盖全场景test/api-test.js 中的测试思路值得借鉴每张 jpg 都有一份.jpg.json预期结果断言实际输出与预期逐字节一致。四张图恰好覆盖了算法的四个关键路径测试图特点验证点evil1.jpg佳能 S400带缩略图APP1 扫描 IFD1 缩略图short-ascii-II.jpg佳能 S40小端 II字节序处理short-ascii-MM.jpg大端 MM字节序处理right.jpg极简元数据空 IFD 边界情况right.jpg 的 Exif 区只有 Orientation、XResolution 等寥寥数条标签其 test/right.jpg.json 中 thumbnail/exif/gps 分组全为空对象——extractExifEntry对空 IFD 的优雅降级正是靠返回false后continue实现的。六、总结一张图记住 extractExifEntry 的精髓12 字节一条Tag ID Format Count Value/OffsetIFD 就是这样的记录数组10 种 FormatBYTE / ASCII / SHORT / LONG / RATIONAL / SBYTE / UNDEFINED / SSHORT / SLONG / SRATIONALswitch 分支逐一解码4 字节内联法则值 ≤ 4 字节直接内联否则存指针记得 tiffOffset防御式解析未知标签返回false、maxEntries限流、未识别 MakerNote 降级为 error 字段——健壮性来自对脏数据的预设扩展点清晰加标签改TAGS字典加厂商写makernotes/模块。掌握这套12 字节 × 10 类型的心智模型后你不仅读懂了 node-exif也具备了阅读任何语言 Exif 解析器的底层能力。【免费下载链接】node-exifA node.js library to extract Exif metadata from images.项目地址: https://gitcode.com/gh_mirrors/no/node-exif创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表