如何使用 NodeJs 实现 小米14 的徕卡水印框 🐳🐳🐳

1,147 阅读6分钟

我们在网上看到一些照片,例如小米 14 那种拍出来的照片,带有边框信息的,看着来很酷,那么我们是否也可以自己实现一个呢,接下来我们将借助 ExifTool 基于 NodeJs 来实现这样的工具来实现这样的功能。

ExifTool

ExifTool 是一个由 Phil Harvey 开发的、用于读取、编写和编辑文件元数据的命令行工具。它广泛支持各种文件格式,尤其是图像、音频和视频文件的元数据处理。ExifTool 是摄影、数字资产管理、数据取证等领域的事实标准工具,被广泛用于处理和分析 EXIF(可交换图像文件格式)数据。

ExifTool 基础知识

EXIF(Exchangeable Image File Format)数据是嵌入在图片和音频文件中的元数据,用于存储拍摄设备的信息和设置。例如:

  1. 拍摄设备信息:相机品牌、型号、序列号等。

  2. 拍摄参数:光圈、快门速度、ISO 感光度、焦距等。

  3. 拍摄时间:记录照片的拍摄日期和时间。

  4. 地理位置信息:通过 GPS 捕获的经纬度信息。

  5. 图像处理信息:白平衡、对比度、色彩空间等。

它主要能够执行以下操作:

  1. 读取元数据:从文件中提取和显示详细的元数据信息。

  2. 写入元数据:向文件中添加或修改元数据。

  3. 删除元数据:删除文件中的特定元数据或所有元数据。

  4. 转换格式:将元数据从一种格式转换为另一种格式,或在不同类型的文件间复制元数据。

  5. 批量处理:对大量文件进行批量元数据操作。

ExifTool 支持非常广泛的文件格式,涵盖了图片、音频、视频、文档等多种类型。例如:

  1. 图片格式:JPEG, TIFF, PNG, RAW 文件(如 CR2, NEF, DNG 等)。

  2. 音频格式:MP3, FLAC, WAV。

  3. 视频格式:MP4, MOV, AVI。

  4. 其他:PDF, DOCX, ZIP, ISO, 和许多其他文件类型。

要想使用,我们首先需要在操作系统上安装,如果你使用的是 mac,那么我们可以使用 Homebrew 来进行安装:

brew install exiftool

20240829084428

验证它是否安装成功,那么我们只需要执行以下命令即可:

exiftool -ver

最终输出结果如下图所示:

20240829084622

ExifTool 的基本用法

我们可以使用 ExifTool 来读取图片中的元数据:

exiftool DSC_0142_1.NEF

20240829084843

输出的内容可太多了,它的格式是键值对的形式,除了这些基础的之外,我们还可以修改或者添加元数据,例如修改作者信息:

exiftool -Artist="Moment" DSC_0142_1.NEF

要想删除图片中所有的元数据,那么我们可以执行:

exiftool -all= DSC_0142_1.NEF

当然我们也可以选择性地删除某些特定类型的元数据,例如 GPS 信息:

exiftool -gps:all= DSC_0142_1.NEF

除了前面这些能一下子输出所有信息之外,我们还可以定制输出的格式,比如只显示特定类型的元数据:

exiftool -make -model -exposuretime DSC_0142_1.NEF

这会输出相机品牌、型号和曝光时间。

20240829085742

总的来说,ExifTool 通过解析文件头和元数据块来读取和写入元数据。它支持的标签种类非常多,并且可以深入到文件的各个层级提取信息。它能够处理多种格式和厂商特定的扩展。例如,针对不同厂商的 RAW 格式,ExifTool 能够识别和提取特定的元数据。

借助这些东西,我们可以将它应用于以下场景:

  1. 摄影工作流:在摄影工作流中,用于整理、标记和优化图像文件。

  2. 数字取证:在调查中,用于分析图像和文件的来源及其修改历史。

  3. 数字资产管理:在大型媒体库中,用于管理和索引元数据。

  4. 隐私保护:在发布或共享照片前,用于删除敏感的 EXIF 数据(如 GPS 信息)。

如何在 NodeJs 项目中实现

要想实现在原有照片的基础上,添加一些边框来显示照片的其他信息,我们可以使用 exiftool-vendored 包,它是一个用于 Node.js 环境的库,它封装了 ExifTool 的功能,方便在 Node.js 项目中直接调用和使用 ExifTool。这个库的主要目的是让开发者在 Node.js 应用程序中处理图像的 EXIF 数据时更加简单和方便,而不必手动管理 ExifTool 的安装和配置。

它的基本使用示例如下图所示:

const { exiftool } = require("exiftool-vendored");
const path = require("path");

async function readExifData() {
  const imagePath = path.join(__dirname, "example.jpg");

  try {
    const exifData = await exiftool.read(imagePath);
    console.log("EXIF Data: ", exifData);
  } catch (error) {
    console.error("Error reading EXIF data:", error);
  } finally {
    await exiftool.end(); // 确保资源释放
  }
}

readExifData();

执行文件,它的输出结果最终如下图所示:

20240829090459

接下来我们将实现一个完整的案例,最终如下代码所示:

const { exiftool } = require("exiftool-vendored");
const path = require("path");
const Jimp = require("jimp");
const { exec } = require("child_process");

const nefImagePath = path.join(__dirname, "DSC_0142_1.NEF");
const jpegImagePath = path.join(__dirname, "DSC_0142_1.jpg");

async function convertNefToJpeg() {
  return new Promise((resolve, reject) => {
    const command = `exiftool -b -JpgFromRaw -w .jpg "${nefImagePath}"`;
    console.log("正在执行命令:", command);
    exec(command, (error, stdout, stderr) => {
      if (error) {
        console.log(`将 NEF 转换为 JPEG 时出错: ${stderr}`);
        reject(`将 NEF 转换为 JPEG 时出错: ${stderr}`);
      } else {
        console.log("JPEG 转换成功。");
        resolve();
      }
    });
  });
}

async function readExifData() {
  try {
    // 第一步: 将 NEF 转换为 JPEG
    console.log("正在将 NEF 转换为 JPEG...");
    await convertNefToJpeg();

    // 第二步: 从原始 NEF 文件读取 EXIF 数据
    console.log("正在读取 EXIF 数据...");
    const exifData = await exiftool.read(nefImagePath);
    console.log("成功读取 EXIF 数据。");

    // 第三步: 提取所需的 EXIF 信息
    const cameraMake = exifData.Make || "未知制造商";
    const cameraModel = exifData.Model || "未知型号";
    const lensInfo = exifData.LensID || exifData.LensModel || "未知镜头";
    const fNumber = exifData.FNumber || "未知光圈值";
    const shutterSpeed = exifData.ShutterSpeed || "未知快门速度";
    const isoSpeed = exifData.ISO || "未知 ISO 值";
    const exposureTime = exifData.ExposureTime || "未知曝光时间";

    const infoText = `${cameraMake} ${cameraModel}\n镜头: ${lensInfo}\n光圈: ${fNumber}\n快门: ${shutterSpeed}\nISO: ${isoSpeed}\n曝光: ${exposureTime}`;

    // 第四步: 读取转换后的 JPEG 图像,并添加带有 EXIF 信息的边框
    console.log("正在读取转换后的 JPEG 图像...");
    const image = await Jimp.read(jpegImagePath);
    const borderSize = 100;

    // 创建一个带有边框的新图像
    const borderedImage = new Jimp(
      image.bitmap.width + borderSize * 2,
      image.bitmap.height + borderSize * 2,
      0xffffffff // 边框的白色背景
    );

    // 将原图像复合到新图像上(带边框)
    borderedImage.composite(image, borderSize, borderSize);

    // 加载字体并在边框上打印 EXIF 信息
    console.log("正在加载字体并将 EXIF 信息添加到边框...");
    const font = await Jimp.loadFont(Jimp.FONT_SANS_32_BLACK);
    borderedImage.print(
      font,
      10, // 文字的 x 位置
      10, // 文字的 y 位置
      {
        text: infoText,
        alignmentX: Jimp.HORIZONTAL_ALIGN_LEFT,
        alignmentY: Jimp.VERTICAL_ALIGN_TOP,
      },
      image.bitmap.width + borderSize * 2 - 20 // 文字的最大宽度
    );

    // 第五步: 保存最终的图像
    const outputImagePath = path.join(
      __dirname,
      "bordered_image_with_exif.jpg"
    );
    console.log("正在保存带边框的图像至:", outputImagePath);
    await borderedImage.writeAsync(outputImagePath);

    console.log(
      "图像已保存,带有边框和 EXIF 信息,保存路径为:",
      outputImagePath
    );
  } catch (error) {
    console.log("出错了: " + error.message);
  } finally {
    await exiftool.end(); // 释放资源
  }
}

readExifData();

在上面的代码中,主要的执行流程有以下几个方面:

  1. 转换图像格式:使用 exiftool 将 NEF 格式的照片转换为 JPEG 格式,因为 Jimp 不支持 NEF 格式。

  2. 读取 EXIF 数据:从 NEF 文件中提取 EXIF 元数据,如相机品牌、型号、镜头信息等。

  3. 处理图像:使用 Jimp 创建一个带有白色边框的图像,并在边框上添加从 EXIF 数据中提取的文本信息。

  4. 保存最终图像:将处理后的图像保存为一个新的 JPEG 文件。

执行命令后我们可以看到控制台下有如下输出:

20240829091352

最终的输出结果如下图所示:

20240829091310转存失败,建议直接上传图片文件

总结

ExifTool 可用于照片整理、按元数据分类管理;添加或修改版权信息;以及删除敏感数据如 GPS 信息以保护隐私。它在数字取证和照片管理中非常实用。

最后分享两个我的两个开源项目,它们分别是:

这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗

文章推荐 自从用了 GPT4o,原来要 20 分钟写完的组件,我 5 分钟就能写完成了