你花了多少钱?买过多少把智能手机?
你用智能手机拍过的照片,很有可能已经过万!
身为程序员,你就没想过写个程序管理这些照片?
0. 基础知识
libExiv2 可处理图像文件的,以下三种元数据:
-
EXIF : Exchangeable Image File Format,可交换图像文件格式,用于记录拍摄设备、环境参数、版权信息等关键数据。它最初由日本电子工业发展协会(JEIDA)于 1996 年发布,最新版本为 2.2(2002 年),目前仍在广泛应用;
-
IPTC : International Press Telecommunications Council(国际新闻电信理事会) 制定的元数据,广泛用于图像、视频等数据内容的管理与分发;
-
XMP : 可扩展元数据平台 (Extensible Metadata Platform) 是由 Adobe 开发的开放元数据标准,旨在为数字资产(如图像、文档、音视频)提供统一的元数据管理框架。它通过 XML 格式存储结构化信息,支持跨平台、跨应用的元数据交换与协作,是现代数字内容工作流的核心技术之一。
1. 课堂视频
2. 关键知识
2.1 打开图像
使用工厂方法 Exiv2::ImageFactor::open(“图像文件.jpg”),得到的是 std::unique_ptr<Exiv2::Image> 智能指针,代码如下:
// 1. 用 exiv2 来打开一个图像文件
Exiv2::Image::UniquePtr img = Exiv2::ImageFactory::open("bhcpp.jpg");
其中 Exiv2::Image::UniquePtr 即 std::unique_ptr<Exiv2::Image> 的类型别名。
2.2 读元数据
打开图像后,并不直接读取其元数据,需调用以下方法:
img->readMetadata();
再接着,调用对应方法,即可读取到不同类型的元数据(EXIF、IPTC、XMP):
// 1 读取 EXIF 信息
Exiv2::ExifData const& exifData = img->exifData();
// 2 读取 IPTC 信息
Exiv2::IptcData const& iptcData = img->iptcData();
// 3 读取 XMP 信息
Exiv2::XmpData const& xmpData = img->xmpData();
2.3 写元数据
三种数据均采用 [key] 形式读写具体的数据项,共中 key 可为 xml 数据项的路径,使用 . 分隔。
写元数据有几个关键点:
(一)、作为后期图像加工程序(而非图像的创建者,通常指相机、截屏软件、摄像头等),不去修改其 EXIF 数据,以确保 EXIF 忠实记录图像的生成信息;
(二)、IPTC 此类数据的键有标准约束,如程序使用标准中不存在键值,libExiv2 将抛出异常;
(三)、XMP 数据项需入特别注意所要写入的数据项,是否为“数组”类型,如是,将新数据内容将以追加而非覆盖的形式写入;
(四)、完成数据项编辑后,需调用 writeMetadata() 方法加以保存。
2.4 异常处理
课堂视频未展现 libExiv2 的异常处理,实际项目在读写元数据读写时,应考虑异常处理,如:
int main()
{
Exiv2::Image::UniquePtr img = Exiv2::ImageFactory::open("bhcpp.jpg");
if (!img) return -1;
try
{
write_ipct_data(img.get());
write_xmp_data(img.get());
read_info(img.get());
}
catch(Exiv2::Error const& e)
{
std::cerr << e.what() << std::endl;
}
}
3. 配套代码
- CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
project(HelloExiv2 CXX)
# c:/msys64/ucrt64/lib 请更换为你的 msys64 库路径
find_library(libExiv2 exiv2 PATHS c:/msys64/ucrt64/lib)
add_executable(HelloExiv2 main.cpp)
# c:/msys64/ucrt64/include 请更换为你的 msys64 头文件路径
target_include_directories(HelloExiv2 PRIVATE c:/msys64/ucrt64/include)
target_link_libraries(HelloExiv2 PRIVATE libExiv2)
- main.cpp
#include <cassert>
#include <iostream>
#include <exiv2/exiv2.hpp>
template<typename TData>
void print_data(TData const& t, char const* data_type)
{
if (t.empty())
{
std::cout << "此图像无 " << data_type << " 信息\n";
return;
}
std::cout << "==== 图像 " << data_type << " 信息 ====\n";
for (auto const& i : t)
{
std::cout << i.key() << " -> " << i.value() << "\n";
}
}
void write_ipct_data(Exiv2::Image *img)
{
// 先读出
img->readMetadata();
auto & iptcData = img->iptcData();
// 库会检查我们所添加的数据的键(key)是否合法,不合法将抛异常
// 版权声明
iptcData["Iptc.Application2.Copyright"] = "© 2025 南郁. 保留所有权利";
// 描述(备注)
iptcData["Iptc.Application2.Caption"] = "一本学习C++的好书!";
img->writeMetadata();
std::cout << "IPTC 元数据写入成功!" << std::endl;
}
void write_xmp_data(Exiv2::Image *img)
{
// 先读出
img->readMetadata();
auto & xmpData = img->xmpData();
// 删除指定键的 xmp 数据项
auto del_xmp_item = [&xmpData] (char const* key)
{
if (auto it = xmpData.findKey(Exiv2::XmpKey(key)); it != xmpData.end())
{
xmpData.erase(it);
}
};
// 创造者
del_xmp_item("Xmp.dc.creator");
xmpData["Xmp.dc.creator"] = "南郁";
// 关键词
del_xmp_item("Xmp.dc.subject");
xmpData["Xmp.dc.subject"] = "书";
xmpData["Xmp.dc.subject"] = "C++";
xmpData["Xmp.dc.subject"] = "练武(下)";
xmpData["Xmp.dc.subject"] = "第2学堂";
// XMP 标题
xmpData["Xmp.dc.title"] = "《白话C++ 练武篇(下)》";
// 打分
xmpData["Xmp.xmp.Rating"] = 3;
img->writeMetadata();
std::cout << "XMP 元数据写入成功!" << std::endl;
}
void read_info(Exiv2::Image *img)
{
assert(img != nullptr);
img->readMetadata();
// 1 读取 EXIF 信息
Exiv2::ExifData const& exifData = img->exifData();
print_data(exifData, "EXIF");
// 2 读取 IPTC 信息
Exiv2::IptcData const& iptcData = img->iptcData();
print_data(iptcData, "IPTC");
// 3 读取 XMP 信息
Exiv2::XmpData const& xmpData = img->xmpData();
print_data(xmpData, "XMP");
}
int main()
{
// 1. 用 exiv2 来打开一个图像文件
Exiv2::Image::UniquePtr img = Exiv2::ImageFactory::open("bhcpp.jpg");
if (!img)
{
std::cerr << "无法打开图片文件" << std::endl;
return -1;
}
std::cout << "图片文件已打开!" << std::endl;
write_ipct_data(img.get());
write_xmp_data(img.get());
read_info(img.get());
}