Skip to main content

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;