exiftool_rs_wrapper/lib.rs
1//! ExifTool Rust Wrapper
2//!
3//! 一个高性能、类型安全的 ExifTool Rust 封装库。
4//!
5//! # 特性
6//!
7//! - **`-stay_open` 模式**:保持进程运行以获得最佳性能
8//! - **类型安全**:完整的标签类型系统
9//! - **Builder 模式**:符合 Rust 习惯的 API
10//! - **线程安全**:支持多线程并发访问
11//! - **零拷贝**:最小化内存分配
12//!
13//! # 示例
14//!
15//! ```rust,no_run
16//! use exiftool_rs_wrapper::ExifTool;
17//!
18//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
19//! // 创建 ExifTool 实例
20//! let exiftool = ExifTool::new()?;
21//!
22//! // 读取元数据
23//! let metadata = exiftool.query("photo.jpg").execute()?;
24//! println!("相机制造商: {:?}", metadata.get("Make"));
25//!
26//! // 写入元数据
27//! exiftool.write("photo.jpg")
28//! .tag("Copyright", "© 2026")
29//! .overwrite_original(true)
30//! .execute()?;
31//! # Ok(())
32//! # }
33//! ```
34
35// 模块声明
36mod batch;
37mod binary;
38mod config;
39mod error;
40mod file_ops;
41mod format;
42mod geo;
43mod pool;
44mod process;
45mod query;
46mod repl;
47mod retry;
48mod stream;
49mod types;
50mod write;
51
52/// 高级功能模块
53pub mod advanced;
54
55/// 异步 API 模块
56#[cfg(feature = "async")]
57pub mod async_ext;
58
59// 公开导出
60pub use advanced::{
61 AdvancedWriteOperations, DateShiftDirection, DateTimeOffset, NumericOperation, TimeUnit,
62};
63pub use binary::{BinaryOperations, BinaryTag, BinaryWriteBuilder, BinaryWriteResult};
64pub use error::{Error, Result};
65pub use process::Response;
66pub use query::{BatchQueryBuilder, EscapeFormat, QueryBuilder};
67pub use types::{Metadata, TagId, TagValue};
68pub use write::{WriteBuilder, WriteMode, WriteResult};
69
70// 连接池
71pub use pool::{ExifToolPool, PoolConnection, batch_with_pool, with_pool};
72
73// 格式化输出
74pub use format::{FormatOperations, FormattedOutput, OutputFormat, ReadOptions};
75
76// 文件操作
77pub use file_ops::{FileOperations, OrganizeOptions, RenamePattern};
78
79// 地理信息
80pub use geo::{GeoOperations, GeocodeResult, GpsCoordinate};
81
82// 配置和校验
83pub use config::{
84 ChecksumAlgorithm, ChecksumResult, ConfigOperations, DiffResult, HexDumpOperations,
85 HexDumpOptions, VerboseOperations, VerboseOptions,
86};
87
88// 流式处理和性能优化
89pub use stream::{
90 Cache, PerformanceStats, ProgressCallback, ProgressReader, ProgressTracker, StreamOptions,
91 StreamingOperations,
92};
93
94// 错误恢复和重试
95pub use retry::{BatchResult, Recoverable, RetryPolicy, with_retry_sync};
96
97#[cfg(feature = "async")]
98pub use retry::with_retry;
99
100// 批处理脚本和管道
101pub use batch::{BatchResult as ScriptBatchResult, BatchScript, PipeProcessor, example_script};
102
103// REPL 交互式 shell
104pub use repl::{ReplShell, run_repl};
105
106#[cfg(feature = "async")]
107pub use async_ext::{AsyncExifTool, process_files_parallel, read_metadata_parallel};
108
109use process::ExifToolInner;
110use std::path::{Path, PathBuf};
111use std::sync::{Arc, Mutex};
112use tracing::{debug, info};
113
114/// ExifTool 主结构体
115///
116/// 使用 `-stay_open` 模式保持 ExifTool 进程运行,
117/// 避免每次操作都重新启动进程的开销。
118///
119/// # 线程安全
120///
121/// `ExifTool` 是线程安全的,可以在多个线程间共享。
122/// 内部使用 `Arc<Mutex>` 保护进程通信。
123#[derive(Debug, Clone)]
124pub struct ExifTool {
125 inner: Arc<Mutex<ExifToolInner>>,
126}
127
128impl ExifTool {
129 /// 创建新的 ExifTool 实例
130 ///
131 /// 启动一个 `-stay_open` 模式的 ExifTool 进程。
132 ///
133 /// # 错误
134 ///
135 /// 如果 ExifTool 未安装或无法启动,返回 `Error::ExifToolNotFound`。
136 ///
137 /// # 示例
138 ///
139 /// ```rust,no_run
140 /// use exiftool_rs_wrapper::ExifTool;
141 ///
142 /// let exiftool = ExifTool::new()?;
143 /// # Ok::<(), exiftool_rs_wrapper::Error>(())
144 /// ```
145 pub fn new() -> Result<Self> {
146 info!("Creating new ExifTool instance");
147
148 let inner = ExifToolInner::new()?;
149
150 Ok(Self {
151 inner: Arc::new(Mutex::new(inner)),
152 })
153 }
154
155 /// 查询单个文件的元数据
156 ///
157 /// 返回一个 `QueryBuilder`,可以使用 Builder 模式配置查询选项。
158 ///
159 /// # 示例
160 ///
161 /// ```rust,no_run
162 /// use exiftool_rs_wrapper::ExifTool;
163 ///
164 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
165 /// let exiftool = ExifTool::new()?;
166 ///
167 /// // 基本查询
168 /// let metadata = exiftool.query("photo.jpg").execute()?;
169 ///
170 /// // 高级查询
171 /// let metadata = exiftool.query("photo.jpg")
172 /// .include_unknown(true)
173 /// .tag("Make")
174 /// .tag("Model")
175 /// .execute()?;
176 /// # Ok(())
177 /// # }
178 /// ```
179 pub fn query<P: AsRef<Path>>(&self, path: P) -> QueryBuilder<'_> {
180 QueryBuilder::new(self, path)
181 }
182
183 /// 批量查询多个文件的元数据
184 ///
185 /// # 示例
186 ///
187 /// ```rust,no_run
188 /// use exiftool_rs_wrapper::ExifTool;
189 ///
190 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
191 /// let exiftool = ExifTool::new()?;
192 ///
193 /// let paths = vec!["photo1.jpg", "photo2.jpg", "photo3.jpg"];
194 /// let results = exiftool.query_batch(&paths)
195 /// .tag("FileName")
196 /// .tag("ImageSize")
197 /// .execute()?;
198 ///
199 /// for (path, metadata) in results {
200 /// println!("{}: {:?}", path.display(), metadata.get("FileName"));
201 /// }
202 /// # Ok(())
203 /// # }
204 /// ```
205 pub fn query_batch<P: AsRef<Path>>(&self, paths: &[P]) -> BatchQueryBuilder<'_> {
206 let path_bufs: Vec<PathBuf> = paths.iter().map(|p| p.as_ref().to_path_buf()).collect();
207 BatchQueryBuilder::new(self, path_bufs)
208 }
209
210 /// 写入元数据到文件
211 ///
212 /// 返回一个 `WriteBuilder`,可以使用 Builder 模式配置写入选项。
213 ///
214 /// # 警告
215 ///
216 /// 默认情况下,ExifTool 会创建备份文件(`filename_original`)。
217 /// 使用 `overwrite_original(true)` 可以不创建备份直接覆盖原文件。
218 ///
219 /// # 示例
220 ///
221 /// ```rust,no_run
222 /// use exiftool_rs_wrapper::ExifTool;
223 ///
224 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
225 /// let exiftool = ExifTool::new()?;
226 ///
227 /// // 基本写入
228 /// exiftool.write("photo.jpg")
229 /// .tag("Copyright", "© 2026 My Company")
230 /// .execute()?;
231 ///
232 /// // 高级写入
233 /// exiftool.write("photo.jpg")
234 /// .tag("Artist", "Photographer")
235 /// .tag("Copyright", "© 2026")
236 /// .delete("Comment")
237 /// .overwrite_original(true)
238 /// .execute()?;
239 /// # Ok(())
240 /// # }
241 /// ```
242 pub fn write<P: AsRef<Path>>(&self, path: P) -> WriteBuilder<'_> {
243 WriteBuilder::new(self, path)
244 }
245
246 /// 读取单个标签的值
247 ///
248 /// 这是 `query().tag().execute()` 的快捷方式。
249 ///
250 /// # 示例
251 ///
252 /// ```rust,no_run
253 /// use exiftool_rs_wrapper::ExifTool;
254 ///
255 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
256 /// let exiftool = ExifTool::new()?;
257 ///
258 /// let make: String = exiftool.read_tag("photo.jpg", "Make")?;
259 /// println!("相机制造商: {}", make);
260 ///
261 /// // 使用 TagId
262 /// use exiftool_rs_wrapper::TagId;
263 /// let model: String = exiftool.read_tag("photo.jpg", "Model")?;
264 /// # Ok(())
265 /// # }
266 /// ```
267 pub fn read_tag<T, P, S>(&self, path: P, tag: S) -> Result<T>
268 where
269 T: for<'de> serde::Deserialize<'de>,
270 P: AsRef<Path>,
271 S: AsRef<str>,
272 {
273 let metadata = self.query(path).tag(tag.as_ref()).execute()?;
274
275 let value = metadata
276 .get(tag.as_ref())
277 .ok_or_else(|| Error::TagNotFound(tag.as_ref().to_string()))?;
278
279 // 将 TagValue 转换为目标类型
280 let json = serde_json::to_value(value)?;
281 let result: T = serde_json::from_value(json)?;
282
283 Ok(result)
284 }
285
286 /// 获取 ExifTool 版本
287 ///
288 /// # 示例
289 ///
290 /// ```rust,no_run
291 /// use exiftool_rs_wrapper::ExifTool;
292 ///
293 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
294 /// let exiftool = ExifTool::new()?;
295 /// let version = exiftool.version()?;
296 /// println!("ExifTool version: {}", version);
297 /// # Ok(())
298 /// # }
299 /// ```
300 pub fn version(&self) -> Result<String> {
301 let mut inner = self.inner.lock()?;
302 inner.send_line("-ver")?;
303 inner.send_line("-execute")?;
304 inner.flush()?;
305
306 let response = inner.read_response()?;
307 Ok(response.text().trim().to_string())
308 }
309
310 /// 获取所有支持的标签列表
311 pub fn list_tags(&self) -> Result<Vec<String>> {
312 let mut inner = self.inner.lock()?;
313 inner.send_line("-list")?;
314 inner.send_line("-execute")?;
315 inner.flush()?;
316
317 let response = inner.read_response()?;
318 let tags: Vec<String> = response
319 .lines()
320 .iter()
321 .map(|line| line.trim().to_string())
322 .filter(|line| {
323 !line.is_empty() && !line.starts_with('-') && !line.contains("Command-line")
324 })
325 .collect();
326
327 Ok(tags)
328 }
329
330 /// 执行原始命令
331 ///
332 /// 这是高级 API,允许直接发送参数到 ExifTool。
333 ///
334 /// # 安全性
335 ///
336 /// 谨慎使用此功能,确保参数不包含恶意输入。
337 pub(crate) fn execute_raw(&self, args: &[impl AsRef<str>]) -> Result<Response> {
338 debug!("Executing raw command with {} args", args.len());
339
340 let args: Vec<String> = args.iter().map(|a| a.as_ref().to_string()).collect();
341
342 let mut inner = self.inner.lock()?;
343 inner.execute(&args)
344 }
345
346 /// 关闭 ExifTool 进程
347 ///
348 /// 优雅地关闭进程。通常不需要手动调用,
349 /// 因为 `Drop` 实现会自动处理。
350 pub fn close(&self) -> Result<()> {
351 let mut inner = self.inner.lock()?;
352 inner.close()
353 }
354
355 /// 删除备份文件
356 ///
357 /// 使用 `-delete_original` 选项删除 `_original` 备份文件。
358 ///
359 /// # 参数
360 ///
361 /// * `path` - 原始文件路径(ExifTool 会找到对应的备份文件)
362 /// * `force` - 是否强制删除(使用 `-delete_original!`)
363 ///
364 /// # 示例
365 ///
366 /// ```rust,no_run
367 /// use exiftool_rs_wrapper::ExifTool;
368 ///
369 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
370 /// let exiftool = ExifTool::new()?;
371 ///
372 /// // 删除 photo.jpg 的备份文件 photo.jpg_original
373 /// exiftool.delete_original("photo.jpg", false)?;
374 ///
375 /// // 强制删除备份文件
376 /// exiftool.delete_original("photo.jpg", true)?;
377 /// # Ok(())
378 /// # }
379 /// ```
380 pub fn delete_original<P: AsRef<Path>>(&self, path: P, force: bool) -> Result<()> {
381 let arg = if force {
382 "-delete_original!"
383 } else {
384 "-delete_original"
385 };
386 let args = vec![arg.to_string(), path.as_ref().to_string_lossy().to_string()];
387 self.execute_raw(&args)?;
388 Ok(())
389 }
390
391 /// 从备份恢复原始文件
392 ///
393 /// 使用 `-restore_original` 选项从 `_original` 备份文件恢复原始文件。
394 ///
395 /// # 参数
396 ///
397 /// * `path` - 文件路径
398 ///
399 /// # 示例
400 ///
401 /// ```rust,no_run
402 /// use exiftool_rs_wrapper::ExifTool;
403 ///
404 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
405 /// let exiftool = ExifTool::new()?;
406 ///
407 /// // 从 photo.jpg_original 恢复 photo.jpg
408 /// exiftool.restore_original("photo.jpg")?;
409 /// # Ok(())
410 /// # }
411 /// ```
412 pub fn restore_original<P: AsRef<Path>>(&self, path: P) -> Result<()> {
413 let args = vec![
414 "-restore_original".to_string(),
415 path.as_ref().to_string_lossy().to_string(),
416 ];
417 self.execute_raw(&args)?;
418 Ok(())
419 }
420}
421
422impl Default for ExifTool {
423 fn default() -> Self {
424 Self::new().expect("Failed to create default ExifTool instance")
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_exiftool_new() {
434 // 仅在 ExifTool 可用时运行
435 match ExifTool::new() {
436 Ok(_) => {
437 println!("✓ ExifTool is available");
438 }
439 Err(Error::ExifToolNotFound) => {
440 println!("⚠ ExifTool not found, skipping test");
441 }
442 Err(e) => panic!("Unexpected error: {:?}", e),
443 }
444 }
445
446 #[test]
447 fn test_version() {
448 match ExifTool::new() {
449 Ok(et) => {
450 let version = et.version().unwrap();
451 assert!(!version.is_empty());
452 println!("ExifTool version: {}", version);
453 }
454 Err(Error::ExifToolNotFound) => {
455 println!("⚠ ExifTool not found, skipping test");
456 }
457 Err(e) => panic!("Unexpected error: {:?}", e),
458 }
459 }
460}
461mod tags;