exiftool-rs-wrapper-0.1.1 has been yanked.
ExifTool Rust Wrapper

中文 | English
一个高性能、类型安全的 Rust ExifTool 封装库,提供 100% ExifTool 功能覆盖
简介
exiftool-rs-wrapper 是一个现代化的 Rust 库,用于读取、写入和管理图像、视频、音频等多媒体文件的元数据。本库封装了强大的 ExifTool 命令行工具,提供符合 Rust 习惯的 API 设计。
核心特点
- 100% 功能覆盖:完整支持 ExifTool 的所有读取、写入和高级功能
- 高性能:使用
-stay_open 模式保持进程运行,避免频繁启动开销
- 类型安全:完整的标签类型系统和强类型 API
- 异步支持:基于 Tokio 的异步 API(可选特性)
- 连接池:内置连接池支持高并发场景
- Builder 模式:流畅的 API 设计,链式调用
功能特性
元数据读取
- 读取 EXIF、IPTC、XMP 等标准元数据
- 支持 200+ 文件格式(JPEG、PNG、RAW、MP4、PDF 等)
- 选择性读取特定标签
- 批量查询多个文件
- 原始数值和格式化值
- 按类别分组输出
元数据写入
- 写入任意 ExifTool 支持的标签
- 删除特定标签
- 批量写入操作
- 条件写入(仅在满足条件时修改)
- 日期时间偏移调整
- 复制标签(从其他文件复制元数据)
- 支持备份和覆盖模式
高级功能
- 文件操作:基于元数据重命名、组织文件
- 地理信息:GPS 坐标读写、反向地理编码
- 二进制数据:缩略图、预览图提取
- 格式转换:多种输出格式(JSON、XML、CSV 等)
- 校验和:计算文件校验和(MD5、SHA256 等)
- 流式处理:大文件处理支持进度跟踪
- 错误恢复:可配置的重试策略
性能优化
- 连接池支持并发访问
- LRU 缓存减少重复查询
- 批量操作优化
- 流式处理大文件
安装
1. 安装 ExifTool
在使用本库之前,需要先在系统上安装 ExifTool:
macOS:
brew install exiftool
Ubuntu/Debian:
sudo apt-get install libimage-exiftool-perl
Windows:
下载并安装 Windows 版本
验证安装:
exiftool -ver
2. 添加依赖
在 Cargo.toml 中添加:
[dependencies]
exiftool-rs-wrapper = "0.1.0"
exiftool-rs-wrapper = { version = "0.1.0", features = ["async"] }
快速开始
基本示例
use exiftool_rs_wrapper::ExifTool;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let metadata = exiftool.query("photo.jpg").execute()?;
if let Some(make) = metadata.get("Make") {
println!("相机制造商: {}", make);
}
if let Some(model) = metadata.get("Model") {
println!("相机型号: {}", model);
}
let width: i64 = exiftool.read_tag("photo.jpg", "ImageWidth")?;
let height: i64 = exiftool.read_tag("photo.jpg", "ImageHeight")?;
println!("图像尺寸: {} x {}", width, height);
Ok(())
}
写入元数据
use exiftool_rs_wrapper::ExifTool;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
exiftool.write("photo.jpg")
.tag("Copyright", "© 2026 My Company")
.tag("Artist", "John Doe")
.execute()?;
exiftool.write("photo.jpg")
.tag("Comment", "Processed with Rust")
.overwrite_original(true)
.execute()?;
exiftool.write("photo.jpg")
.delete("GPSPosition")
.overwrite_original(true)
.execute()?;
Ok(())
}
批量处理
use exiftool_rs_wrapper::ExifTool;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let paths = vec!["photo1.jpg", "photo2.jpg", "photo3.jpg"];
let results = exiftool.query_batch(&paths)
.tag("FileName")
.tag("ImageSize")
.tag("DateTimeOriginal")
.execute()?;
for (path, metadata) in results {
println!("{}: {:?}", path.display(), metadata.get("FileName"));
}
Ok(())
}
详细 API 使用示例
使用标签常量(类型安全)
use exiftool_rs_wrapper::{ExifTool, TagId};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let make: String = exiftool.read_tag("photo.jpg", TagId::MAKE)?;
let model: String = exiftool.read_tag("photo.jpg", TagId::MODEL)?;
let iso: i64 = exiftool.read_tag("photo.jpg", TagId::ISO)?;
println!("{} {} @ ISO {}", make, model, iso);
exiftool.write("photo.jpg")
.tag_id(TagId::COPYRIGHT, "© 2026")
.tag_id(TagId::ARTIST, "Photographer")
.overwrite_original(true)
.execute()?;
Ok(())
}
高级查询选项
use exiftool_rs_wrapper::ExifTool;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let metadata = exiftool.query("photo.jpg")
.include_unknown(true) .include_duplicates(true) .raw_values(true) .group_by_category(true) .tag("Make") .tag("Model")
.tag("DateTimeOriginal")
.exclude("MakerNotes") .execute()?;
let json = exiftool.query("photo.jpg")
.execute_json()?;
println!("{}", serde_json::to_string_pretty(&json)?);
#[derive(serde::Deserialize)]
struct PhotoInfo {
#[serde(rename = "FileName")]
file_name: String,
#[serde(rename = "ImageWidth")]
width: i64,
#[serde(rename = "ImageHeight")]
height: i64,
}
let info: PhotoInfo = exiftool.query("photo.jpg")
.execute_as()?;
Ok(())
}
异步 API
use exiftool_rs_wrapper::async_ext::AsyncExifTool;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = AsyncExifTool::new()?;
let metadata = exiftool.query("photo.jpg").await?;
println!("相机: {:?}", metadata.get("Make"));
let paths = vec!["photo1.jpg", "photo2.jpg", "photo3.jpg"];
let results = exiftool.query_batch(&paths).await?;
for (path, metadata) in results {
println!("{}: {:?}", path.display(), metadata.get("FileName"));
}
exiftool.write_tag("photo.jpg", "Copyright", "© 2026").await?;
exiftool.delete_tag("photo.jpg", "GPSPosition").await?;
Ok(())
}
连接池(高并发)
use exiftool_rs_wrapper::pool::ExifToolPool;
use std::thread;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = ExifToolPool::new(4)?;
let pool_clone = pool.clone();
let handles: Vec<_> = (0..8).map(|i| {
let pool = pool_clone.clone();
thread::spawn(move || {
let conn = pool.acquire()?;
let exiftool = conn.get().unwrap();
let metadata = exiftool.query(format!("photo{}.jpg", i))
.execute()?;
println!("线程 {}: 处理完成", i);
Ok::<(), exiftool_rs_wrapper::Error>(())
})
}).collect();
for handle in handles {
handle.join().unwrap()?;
}
Ok(())
}
文件组织和重命名
use exiftool_rs_wrapper::{
ExifTool,
file_ops::{FileOperations, RenamePattern}
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
exiftool.rename_by_pattern(
"photo.jpg",
RenamePattern::datetime("%Y%m%d_%H%M%S"),
)?;
exiftool.rename_by_pattern(
"photo.jpg",
RenamePattern::tag_with_suffix(
exiftool_rs_wrapper::TagId::MODEL,
"_IMG"
),
)?;
use exiftool_rs_wrapper::file_ops::OrganizeOptions;
let options = OrganizeOptions::new("/output/directory")
.subdir(RenamePattern::datetime("%Y/%m")) .filename(RenamePattern::datetime("%Y%m%d_%H%M%S"))
.extension("jpg");
exiftool.organize_files(&["photo1.jpg", "photo2.jpg"], &options)?;
Ok(())
}
地理信息处理
use exiftool_rs_wrapper::{ExifTool, geo::GeoOperations};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
if let Some(coord) = exiftool.get_gps_coordinates("photo.jpg")? {
println!("纬度: {}", coord.latitude);
println!("经度: {}", coord.longitude);
println!("海拔: {:?}", coord.altitude);
}
use exiftool_rs_wrapper::geo::GpsCoordinate;
let coord = GpsCoordinate::new(39.9042, 116.4074)
.altitude(43.5);
exiftool.set_gps_coordinates("photo.jpg", &coord)?;
if let Some(location) = exiftool.reverse_geocode(&coord)? {
println!("城市: {}", location.city);
println!("国家: {}", location.country);
}
Ok(())
}
错误处理和重试
use exiftool_rs_wrapper::{
ExifTool,
retry::{RetryPolicy, with_retry_sync}
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let policy = RetryPolicy::new()
.max_attempts(3)
.initial_delay(std::time::Duration::from_millis(100))
.exponential_backoff(true);
let metadata = with_retry_sync(&policy, || {
exiftool.query("photo.jpg").execute()
})?;
println!("成功读取元数据: {:?}", metadata.get("FileName"));
Ok(())
}
流式处理和进度跟踪
use exiftool_rs_wrapper::{
ExifTool,
stream::{StreamingOperations, StreamOptions}
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exiftool = ExifTool::new()?;
let on_progress = |processed: usize, total: usize, current: &str| {
let percent = (processed as f64 / total as f64) * 100.0;
println!("进度: {:.1}% ({}/{}): {}", percent, processed, total, current);
};
let options = StreamOptions::new()
.chunk_size(1024 * 1024) .progress_callback(on_progress);
let metadata = exiftool.stream_query("large_video.mp4", &options)?;
Ok(())
}
性能基准测试
以下是在典型硬件配置上的性能测试结果(仅供参考):
| 操作 |
单线程 |
连接池(4) |
异步 |
| 读取单个 JPEG 文件 |
~5ms |
- |
~5ms |
| 批量读取 100 个文件 |
450ms |
120ms |
110ms |
| 写入单个标签 |
~15ms |
- |
~15ms |
| 批量写入 100 个文件 |
1.5s |
450ms |
420ms |
优化建议
- 使用连接池:在高并发场景下,连接池可以显著提升性能
- 批量操作:尽量使用批量 API 而非单个文件循环
- 选择性查询:只查询需要的标签,避免读取完整元数据
- 启用缓存:对于重复查询,使用内置 LRU 缓存
命令行工具
本项目还提供了一个命令行工具:
cargo install exiftool-rs-wrapper
exiftool-rs-wrapper read photo.jpg
exiftool-rs-wrapper write photo.jpg Copyright "© 2026"
exiftool-rs-wrapper delete photo.jpg GPSPosition
exiftool-rs-wrapper batch --input-dir ./photos --output-dir ./organized
exiftool-rs-wrapper version
exiftool-rs-wrapper list-tags
贡献指南
我们欢迎所有形式的贡献!请遵循以下步骤:
提交 Issue
- 报告 Bug 时,请提供详细的复现步骤和环境信息
- 请求新功能时,请描述使用场景和预期行为
- 在提交前请先搜索是否已有相关 Issue
提交 Pull Request
- Fork 本仓库
- 创建功能分支:
git checkout -b feature/amazing-feature
- 提交更改:
git commit -m 'Add amazing feature'
- 推送分支:
git push origin feature/amazing-feature
- 提交 Pull Request
开发环境
git clone https://github.com/openappsys/exiftool-rs-wrapper.git
cd exiftool-rs-wrapper
cargo build --release
cargo test
cargo test --lib
cargo clippy --all-targets -- -D warnings
cargo fmt --check
代码规范
- 所有代码注释必须使用中文
- 遵循 Rust API Guidelines
- 确保通过
cargo clippy 和 cargo fmt 检查
- 为新功能添加测试
- 更新相关文档
许可证
本项目采用 MIT 或 Apache-2.0 双许可证,您可以选择任一许可证使用本项目。
致谢
- ExifTool by Phil Harvey - 强大的元数据处理工具
- Rust 社区 - 优秀的语言和生态系统
相关链接
注意:本库需要系统中已安装 ExifTool。ExifTool 是 Phil Harvey 开发的独立软件,拥有其自己的许可证。