You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中不借助库读取fs.statSync之外的文件元数据?

原生Node.js读取图片深层元数据(以JPEG EXIF拍摄日期为例)

Node.js 没有原生提供直接提取图片/视频深层元数据的高层API,这类元数据(比如EXIF、文件格式内嵌信息)是按照特定二进制结构存储在文件内容中的,需要手动解析对应格式的二进制数据。下面以读取JPEG文件的拍摄日期(EXIF中的DateTimeOriginal)为例,给出同步实现的基础示例:

核心思路

JPEG文件的EXIF数据存储在APP1标记段(二进制标识为FF E1)中,该段内部包含Exif\0\0标识,后续是TIFF格式的元数据结构,我们需要定位到对应标签(DateTimeOriginal的标签ID为0x9003)并提取其ASCII值。

同步实现示例

const fs = require('fs');

function getJpegExifDateTaken(filePath) {
    // 同步读取文件为Buffer
    const buffer = fs.readFileSync(filePath);
    let offset = 0;

    // 验证是否为JPEG文件(开头为FF D8)
    if (buffer.readUInt16BE(offset) !== 0xFFD8) {
        throw new Error('不是JPEG格式文件');
    }
    offset += 2;

    // 遍历寻找APP1标记(FF E1)
    while (offset < buffer.length - 2) {
        const marker = buffer.readUInt16BE(offset);
        offset += 2;

        // 标记段长度(包含长度本身的2字节)
        const segmentLength = buffer.readUInt16BE(offset);
        offset += 2;

        // 找到APP1标记,检查是否为EXIF数据
        if (marker === 0xFFE1) {
            // 验证EXIF标识:"Exif\0\0"
            if (buffer.toString('ascii', offset, offset + 6) === 'Exif\0\0') {
                offset += 6;
                // TIFF头:字节序标识(4949=II=小端,4D4D=MM=大端)
                const endian = buffer.readUInt16BE(offset);
                const isLittleEndian = endian === 0x4949;
                offset += 2;

                // TIFF头的IFD0偏移(相对于TIFF头起始位置)
                const ifd0Offset = buffer.readUInt32(offset, isLittleEndian);
                offset += 4;

                // 跳转到IFD0位置(TIFF头起始是offset-6,所以总偏移是 (offset-6) + ifd0Offset)
                let ifdOffset = (offset - 6) + ifd0Offset;
                // 读取IFD0中的条目数量
                const entryCount = buffer.readUInt16(ifdOffset, isLittleEndian);
                ifdOffset += 2;

                // 遍历IFD0条目,寻找ExifIFD的偏移(标签ID 0x8769)
                let exifIfdOffset = null;
                for (let i = 0; i < entryCount; i++) {
                    const tagId = buffer.readUInt16(ifdOffset, isLittleEndian);
                    if (tagId === 0x8769) {
                        // ExifIFD的偏移值
                        exifIfdOffset = buffer.readUInt32(ifdOffset + 8, isLittleEndian);
                        break;
                    }
                    ifdOffset += 12; // 每个IFD条目占12字节
                }

                if (!exifIfdOffset) {
                    throw new Error('未找到ExifIFD');
                }

                // 跳转到ExifIFD位置
                ifdOffset = (offset - 6) + exifIfdOffset;
                const exifEntryCount = buffer.readUInt16(ifdOffset, isLittleEndian);
                ifdOffset += 2;

                // 遍历ExifIFD条目,寻找DateTimeOriginal(标签ID 0x9003)
                for (let i = 0; i < exifEntryCount; i++) {
                    const tagId = buffer.readUInt16(ifdOffset, isLittleEndian);
                    if (tagId === 0x9003) {
                        // 该标签是ASCII字符串,长度为20(YYYY:MM:DD HH:MM:SS\0)
                        const valueOffset = buffer.readUInt32(ifdOffset + 8, isLittleEndian);
                        const dateStr = buffer.toString('ascii', (offset - 6) + valueOffset, (offset - 6) + valueOffset + 19);
                        return dateStr;
                    }
                    ifdOffset += 12;
                }

                throw new Error('未找到拍摄日期');
            }
        }

        // 跳过当前标记段的剩余内容
        offset += segmentLength - 2;
    }

    throw new Error('未找到EXIF数据');
}

// 使用示例
try {
    const dateTaken = getJpegExifDateTaken('./test.jpg');
    console.log('拍摄日期:', dateTaken);
} catch (err) {
    console.error(err.message);
}

关键说明

  1. 二进制解析逻辑:所有元数据的读取都依赖对对应文件格式二进制规范的理解,比如JPEG的标记段、TIFF的字节序/IFD结构,这些都需要查阅对应格式的官方规范文档。
  2. 扩展性:如果需要读取图片尺寸,JPEG的尺寸存储在SOF0(FF C0)标记段中;PNG的尺寸在IHDR块的前8字节;视频/音频则需要解析对应容器格式(如MP4的moov原子、MP3的ID3标签)。
  3. 关于字符串匹配的误区:你之前设想的直接将文件转成字符串用正则匹配的方式不可靠,因为元数据是二进制结构化存储的,并非所有元数据都以明文"Date Taken:"形式存在,不同格式的存储方式差异极大,正则匹配无法覆盖所有场景且容易出错。

内容的提问来源于stack exchange,提问作者Native Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 00:45:57