Skip to main content

exiftool_rs_wrapper/
query.rs

1//! 查询构建器 - Builder 模式实现
2
3use std::path::{Path, PathBuf};
4
5use crate::ExifTool;
6use crate::error::{Error, Result};
7use crate::process::Response;
8use crate::types::{Metadata, TagId};
9
10/// 转义格式
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EscapeFormat {
13    /// HTML 转义 (-E)
14    Html,
15    /// XML 转义 (-ex)
16    Xml,
17    /// C 语言转义 (-ec)
18    C,
19}
20
21/// 查询构建器
22pub struct QueryBuilder<'et> {
23    exiftool: &'et ExifTool,
24    path: PathBuf,
25    args: Vec<String>,
26    include_unknown: bool,
27    include_duplicates: bool,
28    raw_values: bool,
29    group_by_category: bool,
30    no_composite: bool,
31    extract_embedded: Option<u8>,
32    extensions: Vec<String>,
33    ignore_dirs: Vec<String>,
34    recursive: bool,
35    progress_interval: Option<u32>,
36    progress_title: Option<String>,
37    specific_tags: Vec<String>,
38    excluded_tags: Vec<String>,
39    // 输出格式选项
40    decimal: bool,
41    escape_format: Option<EscapeFormat>,
42    force_print: bool,
43    group_names: Option<u8>,
44    html_format: bool,
45    hex: bool,
46    long_format: bool,
47    latin: bool,
48    short_format: Option<u8>,
49    tab_format: bool,
50    table_format: bool,
51    text_out: Option<String>,
52    tag_out: Option<String>,
53    tag_out_ext: Vec<String>,
54    list_item: Option<u32>,
55    file_order: Option<(String, bool)>,
56    quiet: bool,
57    // 高级输出选项
58    html_dump_enabled: bool,
59    html_dump_offset: Option<u32>,
60    php_format: bool,
61    plot_format: bool,
62    args_format: bool,
63    // 其他选项
64    common_args: Vec<String>,
65    echo: Vec<(String, Option<String>)>,
66    efile: Option<String>,
67}
68
69impl<'et> QueryBuilder<'et> {
70    /// 创建新的查询构建器
71    pub(crate) fn new<P: AsRef<Path>>(exiftool: &'et ExifTool, path: P) -> Self {
72        Self {
73            exiftool,
74            path: path.as_ref().to_path_buf(),
75            args: Vec::new(),
76            include_unknown: false,
77            include_duplicates: false,
78            raw_values: false,
79            group_by_category: false,
80            no_composite: false,
81            extract_embedded: None,
82            extensions: Vec::new(),
83            ignore_dirs: Vec::new(),
84            recursive: false,
85            progress_interval: None,
86            progress_title: None,
87            specific_tags: Vec::new(),
88            excluded_tags: Vec::new(),
89            // 输出格式选项
90            decimal: false,
91            escape_format: None,
92            force_print: false,
93            group_names: None,
94            html_format: false,
95            hex: false,
96            long_format: false,
97            latin: false,
98            short_format: None,
99            tab_format: false,
100            table_format: false,
101            text_out: None,
102            tag_out: None,
103            tag_out_ext: Vec::new(),
104            list_item: None,
105            file_order: None,
106            quiet: false,
107            // 高级输出选项
108            html_dump_enabled: false,
109            html_dump_offset: None,
110            php_format: false,
111            plot_format: false,
112            args_format: false,
113            // 其他选项
114            common_args: Vec::new(),
115            echo: Vec::new(),
116            efile: None,
117        }
118    }
119
120    /// 包含未知标签
121    pub fn include_unknown(mut self, yes: bool) -> Self {
122        self.include_unknown = yes;
123        self
124    }
125
126    /// 包含重复标签
127    pub fn include_duplicates(mut self, yes: bool) -> Self {
128        self.include_duplicates = yes;
129        self
130    }
131
132    /// 显示原始数值(而非格式化后的值)
133    pub fn raw_values(mut self, yes: bool) -> Self {
134        self.raw_values = yes;
135        self
136    }
137
138    /// 按类别分组(-g1 选项)
139    pub fn group_by_category(mut self, yes: bool) -> Self {
140        self.group_by_category = yes;
141        self
142    }
143
144    /// 禁用复合标签生成
145    ///
146    /// 使用 `-e` 选项禁用复合标签(Composite tags)的生成。
147    /// 复合标签是由 ExifTool 根据其他标签计算得出的派生标签。
148    ///
149    /// # 示例
150    ///
151    /// ```rust,no_run
152    /// use exiftool_rs_wrapper::ExifTool;
153    ///
154    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
155    /// let exiftool = ExifTool::new()?;
156    ///
157    /// // 只读取原始标签,不生成复合标签
158    /// let metadata = exiftool.query("photo.jpg")
159    ///     .no_composite(true)
160    ///     .execute()?;
161    /// # Ok(())
162    /// # }
163    /// ```
164    pub fn no_composite(mut self, yes: bool) -> Self {
165        self.no_composite = yes;
166        self
167    }
168
169    /// 提取嵌入文件信息
170    ///
171    /// 使用 `-ee` 选项从文件中提取嵌入的文件信息。
172    /// 例如从 RAW 文件中提取 JPEG 预览图的元数据。
173    ///
174    /// # 级别
175    ///
176    /// - `None` - 不提取嵌入文件(默认)
177    /// - `Some(1)` - `-ee` 提取直接嵌入的文件
178    /// - `Some(2)` - `-ee2` 提取所有层级的嵌入文件
179    /// - `Some(3+)` - `-ee3` 及以上更深入的提取
180    ///
181    /// # 示例
182    ///
183    /// ```rust,no_run
184    /// use exiftool_rs_wrapper::ExifTool;
185    ///
186    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
187    /// let exiftool = ExifTool::new()?;
188    ///
189    /// // 提取嵌入文件信息
190    /// let metadata = exiftool.query("photo.raw")
191    ///     .extract_embedded(Some(1))
192    ///     .execute()?;
193    /// # Ok(())
194    /// # }
195    /// ```
196    pub fn extract_embedded(mut self, level: Option<u8>) -> Self {
197        self.extract_embedded = level;
198        self
199    }
200
201    /// 设置文件扩展名过滤
202    ///
203    /// 使用 `-ext` 选项只处理指定扩展名的文件。
204    /// 可以使用多次来指定多个扩展名。
205    ///
206    /// # 示例
207    ///
208    /// ```rust,no_run
209    /// use exiftool_rs_wrapper::ExifTool;
210    ///
211    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
212    /// let exiftool = ExifTool::new()?;
213    ///
214    /// // 只处理 jpg 文件
215    /// let metadata = exiftool.query("/photos")
216    ///     .extension("jpg")
217    ///     .recursive(true)
218    ///     .execute()?;
219    ///
220    /// // 处理多个扩展名
221    /// let metadata = exiftool.query("/photos")
222    ///     .extension("jpg")
223    ///     .extension("png")
224    ///     .extension("raw")
225    ///     .recursive(true)
226    ///     .execute()?;
227    /// # Ok(())
228    /// # }
229    /// ```
230    pub fn extension(mut self, ext: impl Into<String>) -> Self {
231        self.extensions.push(ext.into());
232        self
233    }
234
235    /// 设置要忽略的目录
236    ///
237    /// 使用 `-i` 选项忽略指定的目录名称。
238    /// 在递归处理时,匹配的目录将被跳过。
239    ///
240    /// # 示例
241    ///
242    /// ```rust,no_run
243    /// use exiftool_rs_wrapper::ExifTool;
244    ///
245    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
246    /// let exiftool = ExifTool::new()?;
247    ///
248    /// // 忽略 .git 和 node_modules 目录
249    /// let metadata = exiftool.query("/project")
250    ///     .ignore(".git")
251    ///     .ignore("node_modules")
252    ///     .recursive(true)
253    ///     .execute()?;
254    /// # Ok(())
255    /// # }
256    /// ```
257    pub fn ignore(mut self, dir: impl Into<String>) -> Self {
258        self.ignore_dirs.push(dir.into());
259        self
260    }
261
262    /// 递归处理子目录
263    ///
264    /// 使用 `-r` 选项递归处理目录中的所有文件。
265    ///
266    /// # 示例
267    ///
268    /// ```rust,no_run
269    /// use exiftool_rs_wrapper::ExifTool;
270    ///
271    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
272    /// let exiftool = ExifTool::new()?;
273    ///
274    /// // 递归处理整个目录树
275    /// let metadata = exiftool.query("/photos")
276    ///     .recursive(true)
277    ///     .extension("jpg")
278    ///     .execute()?;
279    /// # Ok(())
280    /// # }
281    /// ```
282    pub fn recursive(mut self, yes: bool) -> Self {
283        self.recursive = yes;
284        self
285    }
286
287    /// 启用进度显示
288    ///
289    /// 使用 `-progress` 选项在处理文件时显示进度信息。
290    /// 可以指定间隔(每隔多少文件显示一次)和标题。
291    ///
292    /// # 参数
293    ///
294    /// - `interval` - 每隔多少文件显示一次进度(None 表示每个文件都显示)
295    /// - `title` - 进度信息的标题(可选)
296    ///
297    /// # 示例
298    ///
299    /// ```rust,no_run
300    /// use exiftool_rs_wrapper::ExifTool;
301    ///
302    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
303    /// let exiftool = ExifTool::new()?;
304    ///
305    /// // 每 10 个文件显示一次进度
306    /// let metadata = exiftool.query("/photos")
307    ///     .recursive(true)
308    ///     .progress(Some(10), Some("Processing"))
309    ///     .execute()?;
310    /// # Ok(())
311    /// # }
312    /// ```
313    pub fn progress(mut self, interval: Option<u32>, title: Option<impl Into<String>>) -> Self {
314        self.progress_interval = interval;
315        self.progress_title = title.map(|t| t.into());
316        self
317    }
318
319    /// 添加特定标签查询
320    pub fn tag(mut self, tag: impl Into<String>) -> Self {
321        self.specific_tags.push(tag.into());
322        self
323    }
324
325    /// 添加多个标签查询
326    pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
327        for tag in tags {
328            self.specific_tags.push(tag.as_ref().to_string());
329        }
330        self
331    }
332
333    /// 添加特定标签查询(使用 TagId)
334    pub fn tag_id(self, tag: TagId) -> Self {
335        self.tag(tag.name())
336    }
337
338    /// 排除特定标签
339    pub fn exclude(mut self, tag: impl Into<String>) -> Self {
340        self.excluded_tags.push(tag.into());
341        self
342    }
343
344    /// 排除多个标签
345    pub fn excludes(mut self, tags: &[impl AsRef<str>]) -> Self {
346        for tag in tags {
347            self.excluded_tags.push(tag.as_ref().to_string());
348        }
349        self
350    }
351
352    /// 排除特定标签(使用 TagId)
353    pub fn exclude_id(self, tag: TagId) -> Self {
354        self.exclude(tag.name())
355    }
356
357    /// 使用特定编码
358    pub fn charset(mut self, charset: impl Into<String>) -> Self {
359        self.args.push("-charset".to_string());
360        self.args.push(charset.into());
361        self
362    }
363
364    /// 使用特定语言
365    pub fn lang(mut self, lang: impl Into<String>) -> Self {
366        self.args.push("-lang".to_string());
367        self.args.push(lang.into());
368        self
369    }
370
371    /// 设置 GPS 坐标格式
372    ///
373    /// 使用 `-c` 选项设置 GPS 坐标的输出格式
374    ///
375    /// # 示例
376    ///
377    /// ```rust,no_run
378    /// use exiftool_rs_wrapper::ExifTool;
379    ///
380    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
381    /// let exiftool = ExifTool::new()?;
382    ///
383    /// // 使用小数度格式
384    /// let metadata = exiftool.query("photo.jpg")
385    ///     .coord_format("%.6f")
386    ///     .execute()?;
387    ///
388    /// // 使用度分秒格式
389    /// let metadata = exiftool.query("photo.jpg")
390    ///     .coord_format("%d deg %d' %.2f\"")
391    ///     .execute()?;
392    /// # Ok(())
393    /// # }
394    /// ```
395    pub fn coord_format(mut self, format: impl Into<String>) -> Self {
396        self.args.push("-c".to_string());
397        self.args.push(format.into());
398        self
399    }
400
401    /// 设置日期/时间格式
402    ///
403    /// 使用 `-d` 选项设置日期/时间值的输出格式
404    ///
405    /// # 预设格式
406    ///
407    /// - `"%Y:%m:%d %H:%M:%S"` - 标准 EXIF 格式(默认)
408    /// - `"%Y-%m-%d"` - ISO 日期格式
409    /// - `"%c"` - 本地时间格式
410    /// - `"%F %T"` - ISO 8601 格式
411    ///
412    /// # 示例
413    ///
414    /// ```rust,no_run
415    /// use exiftool_rs_wrapper::ExifTool;
416    ///
417    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
418    /// let exiftool = ExifTool::new()?;
419    ///
420    /// // 使用 ISO 格式
421    /// let metadata = exiftool.query("photo.jpg")
422    ///     .date_format("%Y-%m-%d %H:%M:%S")
423    ///     .execute()?;
424    /// # Ok(())
425    /// # }
426    /// ```
427    pub fn date_format(mut self, format: impl Into<String>) -> Self {
428        self.args.push("-d".to_string());
429        self.args.push(format.into());
430        self
431    }
432
433    /// 添加原始参数(高级用法)
434    pub fn arg(mut self, arg: impl Into<String>) -> Self {
435        self.args.push(arg.into());
436        self
437    }
438
439    /// 从文件读取参数
440    ///
441    /// 对应 ExifTool 的 `-@` 选项,从指定文件中读取命令行参数,
442    /// 每行一个参数。
443    ///
444    /// # 示例
445    ///
446    /// ```rust,no_run
447    /// use exiftool_rs_wrapper::ExifTool;
448    ///
449    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
450    /// let exiftool = ExifTool::new()?;
451    ///
452    /// let metadata = exiftool.query("photo.jpg")
453    ///     .args_file("args.txt")
454    ///     .execute()?;
455    /// # Ok(())
456    /// # }
457    /// ```
458    pub fn args_file(mut self, path: impl Into<String>) -> Self {
459        self.args.push("-@".to_string());
460        self.args.push(path.into());
461        self
462    }
463
464    /// 设置 CSV 分隔符
465    ///
466    /// 对应 ExifTool 的 `-csvDelim` 选项,设置 CSV 输出中使用的分隔符字符。
467    ///
468    /// # 示例
469    ///
470    /// ```rust,no_run
471    /// use exiftool_rs_wrapper::ExifTool;
472    ///
473    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
474    /// let exiftool = ExifTool::new()?;
475    ///
476    /// let output = exiftool.query("photo.jpg")
477    ///     .csv_delimiter("\t")
478    ///     .execute_text()?;
479    /// # Ok(())
480    /// # }
481    /// ```
482    pub fn csv_delimiter(mut self, delim: impl Into<String>) -> Self {
483        self.args.push("-csvDelim".to_string());
484        self.args.push(delim.into());
485        self
486    }
487
488    /// 加载替代文件的标签信息
489    ///
490    /// 对应 ExifTool 的 `-fileNUM` 选项,从替代文件中加载标签。
491    /// `num` 为文件编号(1-9),`path` 为替代文件路径。
492    ///
493    /// # 示例
494    ///
495    /// ```rust,no_run
496    /// use exiftool_rs_wrapper::ExifTool;
497    ///
498    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
499    /// let exiftool = ExifTool::new()?;
500    ///
501    /// let metadata = exiftool.query("photo.jpg")
502    ///     .alternate_file(1, "other.jpg")
503    ///     .execute()?;
504    /// # Ok(())
505    /// # }
506    /// ```
507    pub fn alternate_file(mut self, num: u8, path: impl Into<String>) -> Self {
508        self.args.push(format!("-file{}", num));
509        self.args.push(path.into());
510        self
511    }
512
513    /// 递归处理子目录(包含隐藏目录)
514    ///
515    /// 对应 ExifTool 的 `-r.` 选项,递归处理时包含以 `.` 开头的隐藏目录。
516    ///
517    /// # 示例
518    ///
519    /// ```rust,no_run
520    /// use exiftool_rs_wrapper::ExifTool;
521    ///
522    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
523    /// let exiftool = ExifTool::new()?;
524    ///
525    /// let metadata = exiftool.query("/photos")
526    ///     .recursive_hidden()
527    ///     .execute()?;
528    /// # Ok(())
529    /// # }
530    /// ```
531    pub fn recursive_hidden(mut self) -> Self {
532        self.args.push("-r.".to_string());
533        self
534    }
535
536    /// 设置源文件格式
537    ///
538    /// 对应 ExifTool 的 `-srcfile` 选项,指定处理时使用的源文件格式字符串。
539    /// 支持使用 `%d`、`%f`、`%e` 等占位符来匹配不同的源文件。
540    ///
541    /// # 示例
542    ///
543    /// ```rust,no_run
544    /// use exiftool_rs_wrapper::ExifTool;
545    ///
546    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
547    /// let exiftool = ExifTool::new()?;
548    ///
549    /// // 从 XMP sidecar 文件读取标签
550    /// let metadata = exiftool.query("photo.jpg")
551    ///     .source_file("%d%f.xmp")
552    ///     .execute()?;
553    /// # Ok(())
554    /// # }
555    /// ```
556    pub fn source_file(mut self, fmt: impl Into<String>) -> Self {
557        self.args.push("-srcfile".to_string());
558        self.args.push(fmt.into());
559        self
560    }
561
562    /// 提取未知二进制标签
563    ///
564    /// 对应 ExifTool 的 `-U` 选项,提取未知的二进制标签值。
565    /// 比 `-u` 更激进,会尝试解码未知的二进制数据。
566    ///
567    /// # 示例
568    ///
569    /// ```rust,no_run
570    /// use exiftool_rs_wrapper::ExifTool;
571    ///
572    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
573    /// let exiftool = ExifTool::new()?;
574    ///
575    /// let metadata = exiftool.query("photo.jpg")
576    ///     .unknown_binary()
577    ///     .execute()?;
578    /// # Ok(())
579    /// # }
580    /// ```
581    pub fn unknown_binary(mut self) -> Self {
582        self.args.push("-U".to_string());
583        self
584    }
585
586    /// 加载 ExifTool 插件模块
587    ///
588    /// 对应 ExifTool 的 `-use` 选项,加载指定的 ExifTool 插件模块。
589    ///
590    /// # 示例
591    ///
592    /// ```rust,no_run
593    /// use exiftool_rs_wrapper::ExifTool;
594    ///
595    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
596    /// let exiftool = ExifTool::new()?;
597    ///
598    /// let metadata = exiftool.query("photo.jpg")
599    ///     .use_module("MWG")
600    ///     .execute()?;
601    /// # Ok(())
602    /// # }
603    /// ```
604    pub fn use_module(mut self, module: impl Into<String>) -> Self {
605        self.args.push("-use".to_string());
606        self.args.push(module.into());
607        self
608    }
609
610    /// 设置自定义打印格式
611    ///
612    /// 使用 `-p` 选项按指定格式打印输出。
613    /// 使用 `$TAGNAME` 语法引用标签值。
614    ///
615    /// # 格式语法
616    ///
617    /// - `$TAGNAME` - 插入标签值
618    /// - `$TAGNAME#` - 插入原始数值(无格式化)
619    /// - `${TAGNAME:FMT}` - 使用指定格式
620    /// - `$$` - 插入 `$` 字符
621    ///
622    /// # 示例
623    ///
624    /// ```rust,no_run
625    /// use exiftool_rs_wrapper::ExifTool;
626    ///
627    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
628    /// let exiftool = ExifTool::new()?;
629    ///
630    /// // 自定义输出格式
631    /// let output = exiftool.query("photo.jpg")
632    ///     .print_format("$FileName: $DateTimeOriginal ($Make $Model)")
633    ///     .execute_text()?;
634    ///
635    /// println!("{}", output);
636    /// # Ok(())
637    /// # }
638    /// ```
639    pub fn print_format(mut self, format: impl Into<String>) -> Self {
640        self.args.push("-p".to_string());
641        self.args.push(format.into());
642        self
643    }
644
645    /// 禁用打印转换
646    ///
647    /// 使用 `-n` 选项禁用所有打印转换,显示原始数值。
648    /// 与 `raw_values()` 功能相同,但提供更直观的命名。
649    ///
650    /// # 示例
651    ///
652    /// ```rust,no_run
653    /// use exiftool_rs_wrapper::ExifTool;
654    ///
655    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
656    /// let exiftool = ExifTool::new()?;
657    ///
658    /// // 禁用打印转换,获取原始数值
659    /// let metadata = exiftool.query("photo.jpg")
660    ///     .no_print_conv()
661    ///     .execute()?;
662    /// # Ok(())
663    /// # }
664    /// ```
665    pub fn no_print_conv(mut self) -> Self {
666        self.raw_values = true;
667        self
668    }
669
670    /// 二进制输出
671    ///
672    /// 使用 `-b` 选项以二进制格式输出标签值。
673    /// 通常用于提取缩略图、预览图等二进制数据。
674    ///
675    /// # 示例
676    ///
677    /// ```rust,no_run
678    /// use exiftool_rs_wrapper::ExifTool;
679    ///
680    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
681    /// let exiftool = ExifTool::new()?;
682    ///
683    /// // 以二进制格式输出
684    /// let output = exiftool.query("photo.jpg")
685    ///     .binary()
686    ///     .tag("ThumbnailImage")
687    ///     .execute_text()?;
688    /// # Ok(())
689    /// # }
690    /// ```
691    pub fn binary(mut self) -> Self {
692        self.args.push("-b".to_string());
693        self
694    }
695
696    /// 按组分类输出
697    ///
698    /// 使用 `-g` 选项按组分类显示标签。
699    /// 可选参数指定分组级别(0-7),默认为 0。
700    ///
701    /// # 示例
702    ///
703    /// ```rust,no_run
704    /// use exiftool_rs_wrapper::ExifTool;
705    ///
706    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
707    /// let exiftool = ExifTool::new()?;
708    ///
709    /// // 按默认分组级别显示
710    /// let metadata = exiftool.query("photo.jpg")
711    ///     .group_headings(None)
712    ///     .execute()?;
713    ///
714    /// // 按指定分组级别显示
715    /// let metadata = exiftool.query("photo.jpg")
716    ///     .group_headings(Some(1))
717    ///     .execute()?;
718    /// # Ok(())
719    /// # }
720    /// ```
721    pub fn group_headings(mut self, num: Option<u8>) -> Self {
722        match num {
723            Some(n) => self.args.push(format!("-g{}", n)),
724            None => self.args.push("-g".to_string()),
725        }
726        self
727    }
728
729    /// 按多个组级别分类输出
730    ///
731    /// 使用 `-gNUM:NUM:...` 选项按多个组级别分类显示标签。
732    /// 例如 `group_headings_multi(&[0, 1])` 生成 `-g0:1`。
733    ///
734    /// # 示例
735    ///
736    /// ```rust,no_run
737    /// use exiftool_rs_wrapper::ExifTool;
738    ///
739    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
740    /// let exiftool = ExifTool::new()?;
741    ///
742    /// // 按多组级别分类显示(-g0:1:2)
743    /// let metadata = exiftool.query("photo.jpg")
744    ///     .group_headings_multi(&[0, 1, 2])
745    ///     .execute()?;
746    /// # Ok(())
747    /// # }
748    /// ```
749    pub fn group_headings_multi(mut self, groups: &[u8]) -> Self {
750        if groups.is_empty() {
751            self.args.push("-g".to_string());
752        } else {
753            let nums: Vec<String> = groups.iter().map(|n| n.to_string()).collect();
754            self.args.push(format!("-g{}", nums.join(":")));
755        }
756        self
757    }
758
759    /// 短输出格式
760    ///
761    /// 使用 `-s` 选项以短格式输出标签名。
762    /// 可选参数指定短格式级别:
763    /// - `None` 或 `Some(1)` - `-s` 使用标签名而非描述
764    /// - `Some(2)` - `-s2` 更短的输出
765    /// - `Some(3)` - `-s3` 最短输出
766    ///
767    /// # 示例
768    ///
769    /// ```rust,no_run
770    /// use exiftool_rs_wrapper::ExifTool;
771    ///
772    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
773    /// let exiftool = ExifTool::new()?;
774    ///
775    /// let metadata = exiftool.query("photo.jpg")
776    ///     .short(Some(2))
777    ///     .execute()?;
778    /// # Ok(())
779    /// # }
780    /// ```
781    pub fn short(mut self, level: Option<u8>) -> Self {
782        match level {
783            Some(n) if n > 1 => self.args.push(format!("-s{}", n)),
784            _ => self.args.push("-s".to_string()),
785        }
786        self
787    }
788
789    /// 显式指定短格式级别
790    ///
791    /// 使用 `-sNUM` 选项显式指定短格式级别,支持 `-s1`、`-s2`、`-s3`。
792    ///
793    /// # 参数
794    ///
795    /// - `level` - 短格式级别(1、2 或 3)
796    ///
797    /// # 示例
798    ///
799    /// ```rust,no_run
800    /// use exiftool_rs_wrapper::ExifTool;
801    ///
802    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
803    /// let exiftool = ExifTool::new()?;
804    ///
805    /// // 使用 -s1 显式指定级别 1
806    /// let metadata = exiftool.query("photo.jpg")
807    ///     .short_format_level(1)
808    ///     .execute()?;
809    ///
810    /// // 使用 -s3 最短格式
811    /// let metadata = exiftool.query("photo.jpg")
812    ///     .short_format_level(3)
813    ///     .execute()?;
814    /// # Ok(())
815    /// # }
816    /// ```
817    pub fn short_format_level(mut self, level: u8) -> Self {
818        if (1..=3).contains(&level) {
819            self.args.push(format!("-s{}", level));
820        }
821        self
822    }
823
824    /// 极短输出格式
825    ///
826    /// 使用 `-S` 选项以极短格式输出(仅标签名和值)
827    ///
828    /// # 示例
829    ///
830    /// ```rust,no_run
831    /// use exiftool_rs_wrapper::ExifTool;
832    ///
833    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
834    /// let exiftool = ExifTool::new()?;
835    ///
836    /// let output = exiftool.query("photo.jpg")
837    ///     .very_short()
838    ///     .execute_text()?;
839    /// # Ok(())
840    /// # }
841    /// ```
842    pub fn very_short(mut self) -> Self {
843        self.args.push("-S".to_string());
844        self
845    }
846
847    /// 允许重复标签
848    ///
849    /// 使用 `-a` 选项允许输出中包含重复的标签。
850    /// 与 `include_duplicates(true)` 功能相同。
851    ///
852    /// # 示例
853    ///
854    /// ```rust,no_run
855    /// use exiftool_rs_wrapper::ExifTool;
856    ///
857    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
858    /// let exiftool = ExifTool::new()?;
859    ///
860    /// let metadata = exiftool.query("photo.jpg")
861    ///     .allow_duplicates()
862    ///     .execute()?;
863    /// # Ok(())
864    /// # }
865    /// ```
866    pub fn allow_duplicates(mut self) -> Self {
867        self.include_duplicates = true;
868        self
869    }
870
871    /// 提取未知标签
872    ///
873    /// 使用 `-u` 选项提取未知标签。
874    /// 与 `include_unknown(true)` 功能相同。
875    ///
876    /// # 示例
877    ///
878    /// ```rust,no_run
879    /// use exiftool_rs_wrapper::ExifTool;
880    ///
881    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
882    /// let exiftool = ExifTool::new()?;
883    ///
884    /// let metadata = exiftool.query("photo.jpg")
885    ///     .unknown()
886    ///     .execute()?;
887    /// # Ok(())
888    /// # }
889    /// ```
890    pub fn unknown(mut self) -> Self {
891        self.include_unknown = true;
892        self
893    }
894
895    /// XML 格式输出
896    ///
897    /// 使用 `-X` 选项以 XML/RDF 格式输出。
898    /// 通常与 `execute_text()` 配合使用获取 XML 文本。
899    ///
900    /// # 示例
901    ///
902    /// ```rust,no_run
903    /// use exiftool_rs_wrapper::ExifTool;
904    ///
905    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
906    /// let exiftool = ExifTool::new()?;
907    ///
908    /// let xml = exiftool.query("photo.jpg")
909    ///     .xml_format()
910    ///     .execute_text()?;
911    /// # Ok(())
912    /// # }
913    /// ```
914    pub fn xml_format(mut self) -> Self {
915        self.args.push("-X".to_string());
916        self
917    }
918
919    /// 忽略次要错误
920    ///
921    /// 使用 `-m` 选项忽略次要错误和警告,继续处理。
922    ///
923    /// # 示例
924    ///
925    /// ```rust,no_run
926    /// use exiftool_rs_wrapper::ExifTool;
927    ///
928    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
929    /// let exiftool = ExifTool::new()?;
930    ///
931    /// let metadata = exiftool.query("photo.jpg")
932    ///     .ignore_minor_errors()
933    ///     .execute()?;
934    /// # Ok(())
935    /// # }
936    /// ```
937    pub fn ignore_minor_errors(mut self) -> Self {
938        self.args.push("-m".to_string());
939        self
940    }
941
942    /// 按字母顺序排序输出
943    ///
944    /// 使用 `-sort` 选项对标签进行字母排序
945    pub fn sort(mut self, yes: bool) -> Self {
946        if yes {
947            self.args.push("-sort".to_string());
948        }
949        self
950    }
951
952    /// 设置列表项分隔符
953    ///
954    /// 使用 `-sep` 选项设置列表项的分隔符字符串
955    ///
956    /// # 示例
957    ///
958    /// ```rust,no_run
959    /// use exiftool_rs_wrapper::ExifTool;
960    ///
961    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
962    /// let exiftool = ExifTool::new()?;
963    ///
964    /// // 使用逗号分隔列表项
965    /// let metadata = exiftool.query("photo.jpg")
966    ///     .separator(", ")
967    ///     .execute()?;
968    /// # Ok(())
969    /// # }
970    /// ```
971    pub fn separator(mut self, sep: impl Into<String>) -> Self {
972        self.args.push("-sep".to_string());
973        self.args.push(sep.into());
974        self
975    }
976
977    /// 启用快速模式
978    ///
979    /// 使用 `-fast` 选项提高元数据提取速度。
980    /// 这会跳过某些处理步骤,可能遗漏某些信息。
981    ///
982    /// # 级别
983    ///
984    /// - `None` - 不使用快速模式(默认)
985    /// - `Some(1)` - `-fast` 基础快速模式
986    /// - `Some(2)` - `-fast2` 更激进的快速模式
987    ///
988    /// # 示例
989    ///
990    /// ```rust,no_run
991    /// use exiftool_rs_wrapper::ExifTool;
992    ///
993    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
994    /// let exiftool = ExifTool::new()?;
995    ///
996    /// // 使用快速模式处理大量文件
997    /// let metadata = exiftool.query("photo.jpg")
998    ///     .fast(Some(1))
999    ///     .execute()?;
1000    /// # Ok(())
1001    /// # }
1002    /// ```
1003    pub fn fast(mut self, level: Option<u8>) -> Self {
1004        match level {
1005            Some(1) => self.args.push("-fast".to_string()),
1006            Some(l) if l > 1 => self.args.push(format!("-fast{}", l)),
1007            _ => {}
1008        }
1009        self
1010    }
1011
1012    /// 强制扫描 XMP 数据
1013    ///
1014    /// 使用 `-scanForXMP` 选项暴力扫描文件中的 XMP 数据
1015    pub fn scan_for_xmp(mut self, yes: bool) -> Self {
1016        if yes {
1017            self.args.push("-scanForXMP".to_string());
1018        }
1019        self
1020    }
1021
1022    /// 设置 API 选项
1023    ///
1024    /// 使用 `-api` 选项设置 ExifTool API 选项。
1025    /// 常见选项包括:`QuickTimeUTC`, `SystemTags`, `largefilesupport`
1026    ///
1027    /// # 示例
1028    ///
1029    /// ```rust,no_run
1030    /// use exiftool_rs_wrapper::ExifTool;
1031    ///
1032    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1033    /// let exiftool = ExifTool::new()?;
1034    ///
1035    /// // 启用 QuickTimeUTC
1036    /// let metadata = exiftool.query("video.mp4")
1037    ///     .api_option("QuickTimeUTC", None::<&str>)
1038    ///     .execute()?;
1039    /// # Ok(())
1040    /// # }
1041    /// ```
1042    pub fn api_option(mut self, opt: impl Into<String>, value: Option<impl Into<String>>) -> Self {
1043        let option = opt.into();
1044        self.args.push("-api".to_string());
1045        match value {
1046            Some(v) => self.args.push(format!("{}={}", option, v.into())),
1047            None => self.args.push(option),
1048        }
1049        self
1050    }
1051
1052    /// 设置 API 选项(使用 `^=` 赋值语义)
1053    ///
1054    /// 使用 `-api OPT^=VAL` 设置 API 选项。
1055    /// 与 `=` 赋值的区别:省略值时 `=` 设为 undef,`^=` 设为空字符串。
1056    ///
1057    /// # 示例
1058    ///
1059    /// ```rust,no_run
1060    /// use exiftool_rs_wrapper::ExifTool;
1061    ///
1062    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1063    /// let exiftool = ExifTool::new()?;
1064    ///
1065    /// // 设置 API 选项为空字符串(-api OPT^=)
1066    /// let metadata = exiftool.query("photo.jpg")
1067    ///     .api_option_empty("LargeFileSupport", None::<&str>)
1068    ///     .execute()?;
1069    /// # Ok(())
1070    /// # }
1071    /// ```
1072    pub fn api_option_empty(
1073        mut self,
1074        opt: impl Into<String>,
1075        value: Option<impl Into<String>>,
1076    ) -> Self {
1077        let option = opt.into();
1078        self.args.push("-api".to_string());
1079        match value {
1080            Some(v) => self.args.push(format!("{}^={}", option, v.into())),
1081            None => self.args.push(format!("{}^=", option)),
1082        }
1083        self
1084    }
1085
1086    /// 设置用户参数
1087    ///
1088    /// 使用 `-userParam` 选项设置用户参数,可在配置文件中使用
1089    ///
1090    /// # 示例
1091    ///
1092    /// ```rust,no_run
1093    /// use exiftool_rs_wrapper::ExifTool;
1094    ///
1095    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1096    /// let exiftool = ExifTool::new()?;
1097    ///
1098    /// // 设置自定义参数
1099    /// let metadata = exiftool.query("photo.jpg")
1100    ///     .user_param("MyParam", Some("value"))
1101    ///     .execute()?;
1102    /// # Ok(())
1103    /// # }
1104    /// ```
1105    pub fn user_param(
1106        mut self,
1107        param: impl Into<String>,
1108        value: Option<impl Into<String>>,
1109    ) -> Self {
1110        let param = param.into();
1111        self.args.push("-userParam".to_string());
1112        match value {
1113            Some(v) => self.args.push(format!("{}={}", param, v.into())),
1114            None => self.args.push(param),
1115        }
1116        self
1117    }
1118
1119    /// 设置用户参数(使用 `^=` 赋值语义)
1120    ///
1121    /// 使用 `-userParam PARAM^=VAL` 设置用户参数。
1122    /// 与 `=` 赋值的区别:省略值时 `=` 设为 undef,`^=` 设为空字符串。
1123    ///
1124    /// # 示例
1125    ///
1126    /// ```rust,no_run
1127    /// use exiftool_rs_wrapper::ExifTool;
1128    ///
1129    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1130    /// let exiftool = ExifTool::new()?;
1131    ///
1132    /// // 设置用户参数为空字符串
1133    /// let metadata = exiftool.query("photo.jpg")
1134    ///     .user_param_empty("MyParam", None::<&str>)
1135    ///     .execute()?;
1136    /// # Ok(())
1137    /// # }
1138    /// ```
1139    pub fn user_param_empty(
1140        mut self,
1141        param: impl Into<String>,
1142        value: Option<impl Into<String>>,
1143    ) -> Self {
1144        let param = param.into();
1145        self.args.push("-userParam".to_string());
1146        match value {
1147            Some(v) => self.args.push(format!("{}^={}", param, v.into())),
1148            None => self.args.push(format!("{}^=", param)),
1149        }
1150        self
1151    }
1152
1153    /// 设置密码
1154    ///
1155    /// 使用 `-password` 选项处理受密码保护的文件
1156    ///
1157    /// # 安全性警告
1158    ///
1159    /// 密码将以纯文本形式传递给 ExifTool 进程。
1160    /// 在多用户系统中使用时请注意安全性。
1161    ///
1162    /// # 示例
1163    ///
1164    /// ```rust,no_run
1165    /// use exiftool_rs_wrapper::ExifTool;
1166    ///
1167    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1168    /// let exiftool = ExifTool::new()?;
1169    ///
1170    /// // 读取受密码保护的 PDF
1171    /// let metadata = exiftool.query("protected.pdf")
1172    ///     .password("secret123")
1173    ///     .execute()?;
1174    /// # Ok(())
1175    /// # }
1176    /// ```
1177    pub fn password(mut self, passwd: impl Into<String>) -> Self {
1178        self.args.push("-password".to_string());
1179        self.args.push(passwd.into());
1180        self
1181    }
1182
1183    /// 十进制显示标签 ID
1184    ///
1185    /// 使用 `-D` 选项以十进制格式显示标签 ID 编号
1186    pub fn decimal(mut self, yes: bool) -> Self {
1187        self.decimal = yes;
1188        self
1189    }
1190
1191    /// 转义格式
1192    ///
1193    /// 使用 `-E`、`-ex` 或 `-ec` 选项转义标签值
1194    pub fn escape(mut self, format: EscapeFormat) -> Self {
1195        self.escape_format = Some(format);
1196        self
1197    }
1198
1199    /// HTML 转义输出
1200    ///
1201    /// 使用 `-E` 选项对标签值进行 HTML 实体转义。
1202    /// 这是 `escape(EscapeFormat::Html)` 的快捷方法。
1203    ///
1204    /// # 示例
1205    ///
1206    /// ```rust,no_run
1207    /// use exiftool_rs_wrapper::ExifTool;
1208    ///
1209    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1210    /// let exiftool = ExifTool::new()?;
1211    ///
1212    /// let metadata = exiftool.query("photo.jpg")
1213    ///     .escape_html()
1214    ///     .execute()?;
1215    /// # Ok(())
1216    /// # }
1217    /// ```
1218    pub fn escape_html(self) -> Self {
1219        self.escape(EscapeFormat::Html)
1220    }
1221
1222    /// XML 转义输出
1223    ///
1224    /// 使用 `-ex` 选项对标签值进行 XML 转义。
1225    /// 这是 `escape(EscapeFormat::Xml)` 的快捷方法。
1226    ///
1227    /// # 示例
1228    ///
1229    /// ```rust,no_run
1230    /// use exiftool_rs_wrapper::ExifTool;
1231    ///
1232    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1233    /// let exiftool = ExifTool::new()?;
1234    ///
1235    /// let metadata = exiftool.query("photo.jpg")
1236    ///     .escape_xml()
1237    ///     .execute()?;
1238    /// # Ok(())
1239    /// # }
1240    /// ```
1241    pub fn escape_xml(self) -> Self {
1242        self.escape(EscapeFormat::Xml)
1243    }
1244
1245    /// C 语言转义输出
1246    ///
1247    /// 使用 `-ec` 选项对标签值进行 C 语言风格转义。
1248    /// 这是 `escape(EscapeFormat::C)` 的快捷方法。
1249    ///
1250    /// # 示例
1251    ///
1252    /// ```rust,no_run
1253    /// use exiftool_rs_wrapper::ExifTool;
1254    ///
1255    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1256    /// let exiftool = ExifTool::new()?;
1257    ///
1258    /// let metadata = exiftool.query("photo.jpg")
1259    ///     .escape_c()
1260    ///     .execute()?;
1261    /// # Ok(())
1262    /// # }
1263    /// ```
1264    pub fn escape_c(self) -> Self {
1265        self.escape(EscapeFormat::C)
1266    }
1267
1268    /// 强制打印
1269    ///
1270    /// 使用 `-f` 选项强制打印所有指定标签
1271    pub fn force_print(mut self, yes: bool) -> Self {
1272        self.force_print = yes;
1273        self
1274    }
1275
1276    /// 打印组名
1277    ///
1278    /// 使用 `-G` 选项打印每个标签的组名。
1279    /// 单个级别参数,生成 `-G` 或 `-GN`。
1280    pub fn group_names(mut self, level: Option<u8>) -> Self {
1281        self.group_names = level;
1282        self
1283    }
1284
1285    /// 打印多个组级别的组名
1286    ///
1287    /// 使用 `-GNUM:NUM:...` 选项打印多组级别的组名。
1288    /// 例如 `group_names_multi(&[0, 1])` 生成 `-G0:1`。
1289    ///
1290    /// # 示例
1291    ///
1292    /// ```rust,no_run
1293    /// use exiftool_rs_wrapper::ExifTool;
1294    ///
1295    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1296    /// let exiftool = ExifTool::new()?;
1297    ///
1298    /// // 按多组级别打印组名(-G0:1:2)
1299    /// let metadata = exiftool.query("photo.jpg")
1300    ///     .group_names_multi(&[0, 1, 2])
1301    ///     .execute()?;
1302    /// # Ok(())
1303    /// # }
1304    /// ```
1305    pub fn group_names_multi(mut self, groups: &[u8]) -> Self {
1306        if groups.is_empty() {
1307            self.args.push("-G".to_string());
1308        } else {
1309            let nums: Vec<String> = groups.iter().map(|n| n.to_string()).collect();
1310            self.args.push(format!("-G{}", nums.join(":")));
1311        }
1312        self
1313    }
1314
1315    /// HTML 格式
1316    ///
1317    /// 使用 `-h` 选项以 HTML 格式输出
1318    pub fn html_format(mut self, yes: bool) -> Self {
1319        self.html_format = yes;
1320        self
1321    }
1322
1323    /// 十六进制显示
1324    ///
1325    /// 使用 `-H` 选项以十六进制显示标签 ID
1326    pub fn hex(mut self, yes: bool) -> Self {
1327        self.hex = yes;
1328        self
1329    }
1330
1331    /// 长格式输出
1332    ///
1333    /// 使用 `-l` 选项以长格式(2行)输出
1334    pub fn long_format(mut self, yes: bool) -> Self {
1335        self.long_format = yes;
1336        self
1337    }
1338
1339    /// Latin1 编码
1340    ///
1341    /// 使用 `-L` 选项使用 Windows Latin1 编码
1342    pub fn latin(mut self, yes: bool) -> Self {
1343        self.latin = yes;
1344        self
1345    }
1346
1347    /// 短格式输出
1348    ///
1349    /// 使用 `-s` 或 `-S` 选项以短格式输出
1350    pub fn short_format(mut self, level: Option<u8>) -> Self {
1351        self.short_format = level;
1352        self
1353    }
1354
1355    /// Tab 分隔格式
1356    ///
1357    /// 使用 `-t` 选项以 Tab 分隔格式输出
1358    pub fn tab_format(mut self, yes: bool) -> Self {
1359        self.tab_format = yes;
1360        self
1361    }
1362
1363    /// 表格格式
1364    ///
1365    /// 使用 `-T` 选项以表格格式输出
1366    pub fn table_format(mut self, yes: bool) -> Self {
1367        self.table_format = yes;
1368        self
1369    }
1370
1371    /// 文本输出到文件
1372    ///
1373    /// 使用 `-w` 选项将输出写入文件
1374    pub fn text_out(mut self, ext: impl Into<String>) -> Self {
1375        self.text_out = Some(ext.into());
1376        self
1377    }
1378
1379    /// 标签输出到文件
1380    ///
1381    /// 使用 `-W` 选项为每个标签创建输出文件
1382    pub fn tag_out(mut self, format: impl Into<String>) -> Self {
1383        self.tag_out = Some(format.into());
1384        self
1385    }
1386
1387    /// 标签输出扩展名过滤
1388    ///
1389    /// 使用 `-Wext` 选项指定 `-W` 输出的文件类型
1390    pub fn tag_out_ext(mut self, ext: impl Into<String>) -> Self {
1391        self.tag_out_ext.push(ext.into());
1392        self
1393    }
1394
1395    /// 提取列表项
1396    ///
1397    /// 使用 `-listItem` 选项提取列表中的特定项
1398    pub fn list_item(mut self, index: u32) -> Self {
1399        self.list_item = Some(index);
1400        self
1401    }
1402
1403    /// 列出目录而非文件
1404    ///
1405    /// 使用 `-list_dir` 选项让 ExifTool 列出目录名而非处理文件。
1406    /// 与 `-r` 递归选项配合使用时,输出目录树结构。
1407    ///
1408    /// # 示例
1409    ///
1410    /// ```rust,no_run
1411    /// use exiftool_rs_wrapper::ExifTool;
1412    ///
1413    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1414    /// let exiftool = ExifTool::new()?;
1415    ///
1416    /// // 列出目录而非文件
1417    /// let output = exiftool.query("/photos")
1418    ///     .list_dir()
1419    ///     .recursive(true)
1420    ///     .execute_text()?;
1421    /// # Ok(())
1422    /// # }
1423    /// ```
1424    pub fn list_dir(mut self) -> Self {
1425        self.args.push("-list_dir".to_string());
1426        self
1427    }
1428
1429    /// 文件处理顺序
1430    ///
1431    /// 使用 `-fileOrder` 选项设置文件处理顺序
1432    pub fn file_order(mut self, tag: impl Into<String>, descending: bool) -> Self {
1433        self.file_order = Some((tag.into(), descending));
1434        self
1435    }
1436
1437    /// 静默模式
1438    ///
1439    /// 使用 `-q` 选项减少输出信息
1440    pub fn quiet(mut self, yes: bool) -> Self {
1441        self.quiet = yes;
1442        self
1443    }
1444
1445    /// HTML 二进制转储
1446    ///
1447    /// 使用 `-htmlDump` 选项生成 HTML 格式的二进制转储。
1448    /// 可以指定可选的偏移量。
1449    ///
1450    /// # 参数
1451    ///
1452    /// - `offset` - 可选的起始偏移量。`None` 表示无偏移(纯 `-htmlDump`),
1453    ///   `Some(n)` 表示带偏移量(`-htmlDumpN`)
1454    pub fn html_dump(mut self, offset: Option<u32>) -> Self {
1455        self.html_dump_enabled = true;
1456        self.html_dump_offset = offset;
1457        self
1458    }
1459
1460    /// HTML 二进制转储(带指定偏移量)
1461    ///
1462    /// 使用 `-htmlDumpOFFSET` 选项生成带偏移量的 HTML 二进制转储。
1463    /// 这是 `html_dump(Some(offset))` 的便捷方法。
1464    pub fn html_dump_offset(mut self, offset: u32) -> Self {
1465        self.html_dump_enabled = true;
1466        self.html_dump_offset = Some(offset);
1467        self
1468    }
1469
1470    /// PHP数组格式输出
1471    ///
1472    /// 使用 `-php` 选项导出为PHP数组格式
1473    pub fn php_format(mut self, yes: bool) -> Self {
1474        self.php_format = yes;
1475        self
1476    }
1477
1478    /// SVG plot格式输出
1479    ///
1480    /// 使用 `-plot` 选项输出为SVG plot文件
1481    pub fn plot_format(mut self, yes: bool) -> Self {
1482        self.plot_format = yes;
1483        self
1484    }
1485
1486    /// 格式化为exiftool参数
1487    ///
1488    /// 使用 `-args` 选项将元数据格式化为exiftool参数格式
1489    pub fn args_format(mut self, yes: bool) -> Self {
1490        self.args_format = yes;
1491        self
1492    }
1493
1494    /// 设置公共参数
1495    ///
1496    /// 使用 `-common_args` 选项定义在多个命令之间共享的参数
1497    pub fn common_args(mut self, args: &[impl AsRef<str>]) -> Self {
1498        for arg in args {
1499            self.common_args.push(arg.as_ref().to_string());
1500        }
1501        self
1502    }
1503
1504    /// 输出文本到stdout或stderr
1505    ///
1506    /// 使用 `-echo` 选项在处理期间输出文本
1507    /// - `text`: 要输出的文本
1508    /// - `target`: 输出目标,None表示stdout,Some("stderr")表示stderr
1509    pub fn echo(mut self, text: impl Into<String>, target: Option<impl Into<String>>) -> Self {
1510        self.echo.push((text.into(), target.map(|t| t.into())));
1511        self
1512    }
1513
1514    /// 指定级别的echo输出
1515    ///
1516    /// 使用 `-echoNUM` 选项在处理期间输出文本到指定级别的输出流。
1517    /// - 级别 1: stdout (等价于 `-echo`)
1518    /// - 级别 2: stderr (等价于 `-echo2`)
1519    /// - 级别 3: 额外输出级别3
1520    /// - 级别 4: 额外输出级别4
1521    ///
1522    /// # 参数
1523    ///
1524    /// - `level` - echo级别 (1-4)
1525    /// - `text` - 要输出的文本
1526    ///
1527    /// # 示例
1528    ///
1529    /// ```rust,no_run
1530    /// use exiftool_rs_wrapper::ExifTool;
1531    ///
1532    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1533    /// let exiftool = ExifTool::new()?;
1534    ///
1535    /// // 使用 -echo3 输出到级别3
1536    /// let metadata = exiftool.query("photo.jpg")
1537    ///     .echo_level(3, "Processing...")
1538    ///     .execute()?;
1539    /// # Ok(())
1540    /// # }
1541    /// ```
1542    pub fn echo_level(mut self, level: u8, text: impl Into<String>) -> Self {
1543        if (1..=4).contains(&level) {
1544            self.args.push(format!("-echo{}", level));
1545            self.args.push(text.into());
1546        }
1547        self
1548    }
1549
1550    /// 保存错误文件名到文件
1551    ///
1552    /// 使用 `-efile` 选项将处理失败的文件名保存到指定文件
1553    pub fn efile(mut self, filename: impl Into<String>) -> Self {
1554        self.efile = Some(filename.into());
1555        self
1556    }
1557
1558    /// 保存错误文件名到文件(带级别和强制标志)
1559    ///
1560    /// 使用 `-efileNUM` 或 `-efile!` 或 `-efileNUM!` 变体。
1561    ///
1562    /// # 参数
1563    ///
1564    /// - `filename` - 输出文件路径
1565    /// - `num` - 可选级别(2、3 等),`None` 表示默认级别
1566    /// - `force` - 是否使用 `!` 后缀(强制覆盖)
1567    pub fn efile_variant(
1568        mut self,
1569        filename: impl Into<String>,
1570        num: Option<u8>,
1571        force: bool,
1572    ) -> Self {
1573        let num_str = num.map_or(String::new(), |n| n.to_string());
1574        let force_str = if force { "!" } else { "" };
1575        self.args.push(format!("-efile{}{}", num_str, force_str));
1576        self.args.push(filename.into());
1577        self
1578    }
1579
1580    /// 详细模式
1581    ///
1582    /// 使用 `-v` 或 `-vNUM` 选项设置详细输出级别(0-5)。
1583    ///
1584    /// # 参数
1585    ///
1586    /// - `level` - 详细级别,`None` 表示 `-v`(默认级别 1),
1587    ///   `Some(n)` 表示 `-vN`
1588    pub fn verbose(mut self, level: Option<u8>) -> Self {
1589        match level {
1590            Some(n) => self.args.push(format!("-v{}", n)),
1591            None => self.args.push("-v".to_string()),
1592        }
1593        self
1594    }
1595
1596    /// 追加扩展名过滤
1597    ///
1598    /// 使用 `-ext+` 选项追加文件扩展名过滤(而非替换已有列表)。
1599    /// 与 `extension()` 不同,此方法会在已有扩展名列表基础上追加。
1600    pub fn extension_add(mut self, ext: impl Into<String>) -> Self {
1601        self.args.push("-ext+".to_string());
1602        self.args.push(ext.into());
1603        self
1604    }
1605
1606    /// 次级文件处理顺序
1607    ///
1608    /// 使用 `-fileOrderNUM` 选项设置次级排序。
1609    /// `num` 为排序优先级(如 2 表示 `-fileOrder2`)。
1610    pub fn file_order_secondary(
1611        mut self,
1612        num: u8,
1613        tag: impl Into<String>,
1614        descending: bool,
1615    ) -> Self {
1616        let order = if descending { "-" } else { "" };
1617        self.args.push(format!("-fileOrder{}", num));
1618        self.args.push(format!("{}{}", order, tag.into()));
1619        self
1620    }
1621
1622    /// 条件过滤(带编号)
1623    ///
1624    /// 使用 `-ifNUM` 选项设置条件过滤。
1625    /// `-if2` 在第一个条件失败时仍然继续检查。
1626    /// `-if3` 及更高编号用于更细粒度的控制。
1627    ///
1628    /// # 参数
1629    ///
1630    /// - `num` - 条件编号(如 2 表示 `-if2`)
1631    /// - `expr` - 条件表达式
1632    pub fn condition_num(mut self, num: u8, expr: impl Into<String>) -> Self {
1633        self.args.push(format!("-if{}", num));
1634        self.args.push(expr.into());
1635        self
1636    }
1637
1638    /// 标签输出到文件(追加模式)
1639    ///
1640    /// 使用 `-W+` 选项为每个标签创建输出文件,追加到已有文件。
1641    pub fn tag_out_append(mut self, format: impl Into<String>) -> Self {
1642        self.args.push("-W+".to_string());
1643        self.args.push(format.into());
1644        self
1645    }
1646
1647    /// 标签输出到文件(仅创建新文件)
1648    ///
1649    /// 使用 `-W!` 选项为每个标签创建输出文件,但不覆盖已有文件。
1650    pub fn tag_out_create(mut self, format: impl Into<String>) -> Self {
1651        self.args.push("-W!".to_string());
1652        self.args.push(format.into());
1653        self
1654    }
1655
1656    /// 文本输出到文件(追加模式)
1657    ///
1658    /// 使用 `-w+` 选项将输出追加到已有文件。
1659    pub fn text_out_append(mut self, ext: impl Into<String>) -> Self {
1660        self.args.push("-w+".to_string());
1661        self.args.push(ext.into());
1662        self
1663    }
1664
1665    /// 文本输出到文件(仅创建新文件)
1666    ///
1667    /// 使用 `-w!` 选项将输出写入新文件,但不覆盖已有文件。
1668    pub fn text_out_create(mut self, ext: impl Into<String>) -> Self {
1669        self.args.push("-w!".to_string());
1670        self.args.push(ext.into());
1671        self
1672    }
1673
1674    /// 自定义打印格式(不追加换行符)
1675    ///
1676    /// 使用 `-p-` 选项按指定格式打印输出,但不在每行末尾追加换行符。
1677    /// 与 `print_format()` 的区别在于输出不会自动追加换行。
1678    pub fn print_format_no_newline(mut self, format: impl Into<String>) -> Self {
1679        self.args.push("-p-".to_string());
1680        self.args.push(format.into());
1681        self
1682    }
1683
1684    /// 导入 JSON 文件中的标签
1685    ///
1686    /// 使用 `-j=JSONFILE` 选项从 JSON 文件中导入标签数据。
1687    pub fn json_import(mut self, path: impl Into<String>) -> Self {
1688        self.args.push(format!("-j={}", path.into()));
1689        self
1690    }
1691
1692    /// 追加导入 JSON 文件中的标签
1693    ///
1694    /// 使用 `-j+=JSONFILE` 选项从 JSON 文件中追加导入标签数据。
1695    pub fn json_append(mut self, path: impl Into<String>) -> Self {
1696        self.args.push(format!("-j+={}", path.into()));
1697        self
1698    }
1699
1700    /// 导入 CSV 文件中的标签
1701    ///
1702    /// 使用 `-csv=CSVFILE` 选项从 CSV 文件中导入标签数据。
1703    pub fn csv_import(mut self, path: impl Into<String>) -> Self {
1704        self.args.push(format!("-csv={}", path.into()));
1705        self
1706    }
1707
1708    /// 追加导入 CSV 文件中的标签
1709    ///
1710    /// 使用 `-csv+=CSVFILE` 选项从 CSV 文件中追加导入标签数据。
1711    pub fn csv_append(mut self, path: impl Into<String>) -> Self {
1712        self.args.push(format!("-csv+={}", path.into()));
1713        self
1714    }
1715
1716    /// 修复 MakerNotes 基准偏移
1717    ///
1718    /// 使用 `-F` 选项修复 MakerNotes 的基准偏移。
1719    /// 不带参数时自动修复,带偏移量时使用指定偏移。
1720    ///
1721    /// # 参数
1722    ///
1723    /// - `offset` - 可选的偏移量修正值。`None` 表示自动修复(`-F`),
1724    ///   `Some(n)` 表示指定偏移(`-Fn`)
1725    pub fn fix_base(mut self, offset: Option<i64>) -> Self {
1726        match offset {
1727            Some(n) => self.args.push(format!("-F{}", n)),
1728            None => self.args.push("-F".to_string()),
1729        }
1730        self
1731    }
1732
1733    /// 修复 MakerNotes 基准偏移(带指定偏移量)
1734    ///
1735    /// 使用 `-FOFFSET` 选项修复 MakerNotes 偏移。
1736    /// 这是 `fix_base(Some(offset))` 的便捷方法。
1737    pub fn fix_base_offset(mut self, offset: i64) -> Self {
1738        self.args.push(format!("-F{}", offset));
1739        self
1740    }
1741
1742    /// 执行查询
1743    pub fn execute(self) -> Result<Metadata> {
1744        let args = self.build_args(true);
1745
1746        // 发送命令并获取响应
1747        let response = self.exiftool.execute_raw(&args)?;
1748
1749        // 解析响应
1750        self.parse_response(response)
1751    }
1752
1753    /// 执行查询并返回 JSON
1754    pub fn execute_json(self) -> Result<serde_json::Value> {
1755        let args = self.build_args(true);
1756        let response = self.exiftool.execute_raw(&args)?;
1757        response.json()
1758    }
1759
1760    /// 执行查询并反序列化为自定义类型
1761    pub fn execute_as<T: serde::de::DeserializeOwned>(self) -> Result<T> {
1762        let args = self.build_args(true);
1763        let response = self.exiftool.execute_raw(&args)?;
1764        response.json()
1765    }
1766
1767    /// 执行查询并返回纯文本
1768    ///
1769    /// 当使用 `-p` (print_format) 选项时,
1770    /// 使用此方法获取纯文本输出而非 JSON
1771    ///
1772    /// # 示例
1773    ///
1774    /// ```rust,no_run
1775    /// use exiftool_rs_wrapper::ExifTool;
1776    ///
1777    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1778    /// let exiftool = ExifTool::new()?;
1779    ///
1780    /// let output = exiftool.query("photo.jpg")
1781    ///     .print_format("$FileName: $DateTimeOriginal")
1782    ///     .execute_text()?;
1783    ///
1784    /// println!("{}", output);
1785    /// # Ok(())
1786    /// # }
1787    /// ```
1788    pub fn execute_text(self) -> Result<String> {
1789        let args = self.build_args(false);
1790        let response = self.exiftool.execute_raw(&args)?;
1791        Ok(response.text().trim().to_string())
1792    }
1793
1794    /// 构建参数列表
1795    fn build_args(&self, json_output: bool) -> Vec<String> {
1796        let mut args = Vec::new();
1797
1798        if json_output {
1799            args.push("-json".to_string());
1800        }
1801
1802        // 分组选项
1803        if self.group_by_category {
1804            args.push("-g1".to_string());
1805        }
1806
1807        // 未知标签
1808        if self.include_unknown {
1809            args.push("-u".to_string());
1810        }
1811
1812        // 重复标签
1813        if self.include_duplicates {
1814            args.push("-a".to_string());
1815        }
1816
1817        // 原始数值
1818        if self.raw_values {
1819            args.push("-n".to_string());
1820        }
1821
1822        // 十进制显示
1823        if self.decimal {
1824            args.push("-D".to_string());
1825        }
1826
1827        // 转义格式
1828        if let Some(format) = self.escape_format {
1829            let flag = match format {
1830                EscapeFormat::Html => "-E",
1831                EscapeFormat::Xml => "-ex",
1832                EscapeFormat::C => "-ec",
1833            };
1834            args.push(flag.to_string());
1835        }
1836
1837        // 强制打印
1838        if self.force_print {
1839            args.push("-f".to_string());
1840        }
1841
1842        // 组名(-G0, -G1, -G2 等)
1843        if let Some(level) = self.group_names {
1844            args.push(format!("-G{}", level));
1845        }
1846
1847        // HTML 格式
1848        if self.html_format {
1849            args.push("-h".to_string());
1850        }
1851
1852        // 十六进制
1853        if self.hex {
1854            args.push("-H".to_string());
1855        }
1856
1857        // 长格式
1858        if self.long_format {
1859            args.push("-l".to_string());
1860        }
1861
1862        // Latin1 编码
1863        if self.latin {
1864            args.push("-L".to_string());
1865        }
1866
1867        // 短格式
1868        if let Some(level) = self.short_format {
1869            if level == 0 {
1870                args.push("-S".to_string());
1871            } else {
1872                args.push(format!("-s{}", level));
1873            }
1874        }
1875
1876        // Tab 格式
1877        if self.tab_format {
1878            args.push("-t".to_string());
1879        }
1880
1881        // 表格格式
1882        if self.table_format {
1883            args.push("-T".to_string());
1884        }
1885
1886        // 文本输出
1887        if let Some(ref ext) = self.text_out {
1888            args.push("-w".to_string());
1889            args.push(ext.clone());
1890        }
1891
1892        // 标签输出
1893        if let Some(ref format) = self.tag_out {
1894            args.push("-W".to_string());
1895            args.push(format.clone());
1896        }
1897
1898        // 标签输出扩展名
1899        for ext in &self.tag_out_ext {
1900            args.push("-Wext".to_string());
1901            args.push(ext.clone());
1902        }
1903
1904        // 列表项
1905        if let Some(index) = self.list_item {
1906            args.push("-listItem".to_string());
1907            args.push(index.to_string());
1908        }
1909
1910        // 文件顺序
1911        if let Some((ref tag, descending)) = self.file_order {
1912            let order = if descending { "-" } else { "" };
1913            args.push("-fileOrder".to_string());
1914            args.push(format!("{}{}", order, tag));
1915        }
1916
1917        // 静默模式
1918        if self.quiet {
1919            args.push("-q".to_string());
1920        }
1921
1922        // HTML 二进制转储
1923        if self.html_dump_enabled {
1924            match self.html_dump_offset {
1925                Some(offset) => args.push(format!("-htmlDump{}", offset)),
1926                None => args.push("-htmlDump".to_string()),
1927            }
1928        }
1929
1930        // PHP数组格式
1931        if self.php_format {
1932            args.push("-php".to_string());
1933        }
1934
1935        // SVG plot格式
1936        if self.plot_format {
1937            args.push("-plot".to_string());
1938        }
1939
1940        // 格式化为exiftool参数
1941        if self.args_format {
1942            args.push("-args".to_string());
1943        }
1944
1945        // 公共参数
1946        for arg in &self.common_args {
1947            args.push("-common_args".to_string());
1948            args.push(arg.clone());
1949        }
1950
1951        // echo输出
1952        for (text, target) in &self.echo {
1953            let option = match target {
1954                Some(t) if t == "stderr" => "-echo2",
1955                _ => "-echo",
1956            };
1957            args.push(option.to_string());
1958            args.push(text.clone());
1959        }
1960
1961        // 错误文件
1962        if let Some(ref filename) = self.efile {
1963            args.push("-efile".to_string());
1964            args.push(filename.clone());
1965        }
1966
1967        // 禁用复合标签
1968        if self.no_composite {
1969            args.push("-e".to_string());
1970        }
1971
1972        // 提取嵌入文件
1973        if let Some(level) = self.extract_embedded {
1974            if level == 1 {
1975                args.push("-ee".to_string());
1976            } else {
1977                args.push(format!("-ee{}", level));
1978            }
1979        }
1980
1981        // 文件扩展名过滤
1982        for ext in &self.extensions {
1983            args.push("-ext".to_string());
1984            args.push(ext.clone());
1985        }
1986
1987        // 忽略目录
1988        for dir in &self.ignore_dirs {
1989            args.push("-i".to_string());
1990            args.push(dir.clone());
1991        }
1992
1993        // 递归处理
1994        if self.recursive {
1995            args.push("-r".to_string());
1996        }
1997
1998        // 进度显示
1999        if let Some(interval) = self.progress_interval {
2000            let progress_arg = if let Some(ref title) = self.progress_title {
2001                format!("-progress{}:{}", interval, title)
2002            } else {
2003                format!("-progress{}", interval)
2004            };
2005            args.push(progress_arg);
2006        }
2007
2008        // 添加自定义参数
2009        args.extend(self.args.clone());
2010
2011        // 特定标签
2012        for tag in &self.specific_tags {
2013            args.push(format!("-{}", tag));
2014        }
2015
2016        // 排除标签
2017        for tag in &self.excluded_tags {
2018            args.push(format!("--{}", tag));
2019        }
2020
2021        // 文件路径
2022        args.push(self.path.to_string_lossy().to_string());
2023
2024        args
2025    }
2026
2027    /// 解析响应
2028    fn parse_response(&self, response: Response) -> Result<Metadata> {
2029        parse_query_response(response)
2030    }
2031}
2032
2033/// 异步查询执行方法
2034///
2035/// 在 `async` feature 开启时,为 `QueryBuilder` 提供异步执行方法。
2036/// Builder 的链式调用仍然是同步的(仅收集参数),
2037/// 只有最终的 `async_execute` 才通过 `spawn_blocking` 异步执行。
2038#[cfg(feature = "async")]
2039impl QueryBuilder<'_> {
2040    /// 异步执行查询
2041    ///
2042    /// 内部先收集参数(纯数据),然后在阻塞线程池中执行 ExifTool 命令。
2043    /// 适用于 `AsyncExifTool::query_builder()` 返回的构建器。
2044    ///
2045    /// # 示例
2046    ///
2047    /// ```rust,no_run
2048    /// use exiftool_rs_wrapper::AsyncExifTool;
2049    ///
2050    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2051    /// let async_et = AsyncExifTool::new()?;
2052    ///
2053    /// let metadata = async_et.query_builder("photo.jpg")
2054    ///     .binary()
2055    ///     .group_headings(None)
2056    ///     .tag("Make")
2057    ///     .async_execute()
2058    ///     .await?;
2059    /// # Ok(())
2060    /// # }
2061    /// ```
2062    pub async fn async_execute(self) -> Result<Metadata> {
2063        // 先收集参数(纯数据,无引用)
2064        let args = self.build_args(true);
2065        // clone ExifTool(内部是 Arc,开销极小)
2066        let exiftool = self.exiftool.clone();
2067        tokio::task::spawn_blocking(move || {
2068            let response = exiftool.execute_raw(&args)?;
2069            parse_query_response(response)
2070        })
2071        .await
2072        .map_err(|e| Error::process(format!("异步查询任务执行失败: {}", e)))?
2073    }
2074
2075    /// 异步执行查询并返回 JSON
2076    ///
2077    /// 与 `execute_json` 相同,但在阻塞线程池中异步执行。
2078    pub async fn async_execute_json(self) -> Result<serde_json::Value> {
2079        let args = self.build_args(true);
2080        let exiftool = self.exiftool.clone();
2081        tokio::task::spawn_blocking(move || {
2082            let response = exiftool.execute_raw(&args)?;
2083            response.json()
2084        })
2085        .await
2086        .map_err(|e| Error::process(format!("异步查询任务执行失败: {}", e)))?
2087    }
2088
2089    /// 异步执行查询并反序列化为自定义类型
2090    ///
2091    /// 与 `execute_as` 相同,但在阻塞线程池中异步执行。
2092    pub async fn async_execute_as<T: serde::de::DeserializeOwned + Send + 'static>(
2093        self,
2094    ) -> Result<T> {
2095        let args = self.build_args(true);
2096        let exiftool = self.exiftool.clone();
2097        tokio::task::spawn_blocking(move || {
2098            let response = exiftool.execute_raw(&args)?;
2099            response.json()
2100        })
2101        .await
2102        .map_err(|e| Error::process(format!("异步查询任务执行失败: {}", e)))?
2103    }
2104
2105    /// 异步执行查询并返回纯文本
2106    ///
2107    /// 与 `execute_text` 相同,但在阻塞线程池中异步执行。
2108    pub async fn async_execute_text(self) -> Result<String> {
2109        let args = self.build_args(false);
2110        let exiftool = self.exiftool.clone();
2111        tokio::task::spawn_blocking(move || {
2112            let response = exiftool.execute_raw(&args)?;
2113            Ok(response.text().trim().to_string())
2114        })
2115        .await
2116        .map_err(|e| Error::process(format!("异步查询任务执行失败: {}", e)))?
2117    }
2118}
2119
2120/// 解析查询响应为 Metadata
2121///
2122/// 从 ExifTool 的 JSON 响应中解析出 Metadata 对象。
2123/// 此函数被同步 `execute` 和异步 `async_execute` 共用。
2124fn parse_query_response(response: Response) -> Result<Metadata> {
2125    if response.is_error() {
2126        return Err(Error::process(
2127            response
2128                .error_message()
2129                .unwrap_or_else(|| "Unknown error".to_string()),
2130        ));
2131    }
2132
2133    let json_value: Vec<serde_json::Value> = response.json()?;
2134
2135    if json_value.is_empty() {
2136        return Ok(Metadata::new());
2137    }
2138
2139    // 取第一个(也是唯一一个)结果
2140    let metadata: Metadata = serde_json::from_value(json_value[0].clone())?;
2141
2142    Ok(metadata)
2143}
2144
2145/// 批量查询构建器
2146pub struct BatchQueryBuilder<'et> {
2147    exiftool: &'et ExifTool,
2148    paths: Vec<PathBuf>,
2149    args: Vec<String>,
2150    include_unknown: bool,
2151    include_duplicates: bool,
2152    raw_values: bool,
2153    group_by_category: bool,
2154    specific_tags: Vec<String>,
2155}
2156
2157impl<'et> BatchQueryBuilder<'et> {
2158    /// 创建新的批量查询构建器
2159    pub(crate) fn new(exiftool: &'et ExifTool, paths: Vec<PathBuf>) -> Self {
2160        Self {
2161            exiftool,
2162            paths,
2163            args: Vec::new(),
2164            include_unknown: false,
2165            include_duplicates: false,
2166            raw_values: false,
2167            group_by_category: false,
2168            specific_tags: Vec::new(),
2169        }
2170    }
2171
2172    /// 包含未知标签
2173    pub fn include_unknown(mut self, yes: bool) -> Self {
2174        self.include_unknown = yes;
2175        self
2176    }
2177
2178    /// 包含重复标签
2179    pub fn include_duplicates(mut self, yes: bool) -> Self {
2180        self.include_duplicates = yes;
2181        self
2182    }
2183
2184    /// 显示原始数值
2185    pub fn raw_values(mut self, yes: bool) -> Self {
2186        self.raw_values = yes;
2187        self
2188    }
2189
2190    /// 按类别分组
2191    pub fn group_by_category(mut self, yes: bool) -> Self {
2192        self.group_by_category = yes;
2193        self
2194    }
2195
2196    /// 添加特定标签查询
2197    pub fn tag(mut self, tag: impl Into<String>) -> Self {
2198        self.specific_tags.push(tag.into());
2199        self
2200    }
2201
2202    /// 添加多个标签查询
2203    pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
2204        for tag in tags {
2205            self.specific_tags.push(tag.as_ref().to_string());
2206        }
2207        self
2208    }
2209
2210    /// 设置 GPS 坐标格式
2211    pub fn coord_format(mut self, format: impl Into<String>) -> Self {
2212        self.args.push("-c".to_string());
2213        self.args.push(format.into());
2214        self
2215    }
2216
2217    /// 设置日期/时间格式
2218    pub fn date_format(mut self, format: impl Into<String>) -> Self {
2219        self.args.push("-d".to_string());
2220        self.args.push(format.into());
2221        self
2222    }
2223
2224    /// 设置密码
2225    pub fn password(mut self, passwd: impl Into<String>) -> Self {
2226        self.args.push("-password".to_string());
2227        self.args.push(passwd.into());
2228        self
2229    }
2230
2231    /// 启用快速模式
2232    pub fn fast(mut self, level: Option<u8>) -> Self {
2233        match level {
2234            Some(1) => self.args.push("-fast".to_string()),
2235            Some(l) if l > 1 => self.args.push(format!("-fast{}", l)),
2236            _ => {}
2237        }
2238        self
2239    }
2240
2241    /// 强制扫描 XMP 数据
2242    pub fn scan_for_xmp(mut self, yes: bool) -> Self {
2243        if yes {
2244            self.args.push("-scanForXMP".to_string());
2245        }
2246        self
2247    }
2248
2249    /// 设置 API 选项
2250    pub fn api_option(mut self, opt: impl Into<String>, value: Option<impl Into<String>>) -> Self {
2251        let option = opt.into();
2252        self.args.push("-api".to_string());
2253        match value {
2254            Some(v) => self.args.push(format!("{}={}", option, v.into())),
2255            None => self.args.push(option),
2256        }
2257        self
2258    }
2259
2260    /// 设置用户参数
2261    pub fn user_param(
2262        mut self,
2263        param: impl Into<String>,
2264        value: Option<impl Into<String>>,
2265    ) -> Self {
2266        let param = param.into();
2267        self.args.push("-userParam".to_string());
2268        match value {
2269            Some(v) => self.args.push(format!("{}={}", param, v.into())),
2270            None => self.args.push(param),
2271        }
2272        self
2273    }
2274
2275    /// 设置自定义打印格式
2276    pub fn print_format(mut self, format: impl Into<String>) -> Self {
2277        self.args.push("-p".to_string());
2278        self.args.push(format.into());
2279        self
2280    }
2281
2282    /// 按字母顺序排序输出
2283    pub fn sort(mut self, yes: bool) -> Self {
2284        if yes {
2285            self.args.push("-sort".to_string());
2286        }
2287        self
2288    }
2289
2290    /// 设置列表项分隔符
2291    pub fn separator(mut self, sep: impl Into<String>) -> Self {
2292        self.args.push("-sep".to_string());
2293        self.args.push(sep.into());
2294        self
2295    }
2296
2297    /// 执行批量查询
2298    pub fn execute(self) -> Result<Vec<(PathBuf, Metadata)>> {
2299        if self.paths.is_empty() {
2300            return Ok(Vec::new());
2301        }
2302
2303        let args = self.build_args();
2304        let response = self.exiftool.execute_raw(&args)?;
2305
2306        // 解析 JSON 数组响应
2307        let json_values: Vec<serde_json::Value> = response.json()?;
2308
2309        let mut results = Vec::with_capacity(json_values.len());
2310
2311        for (i, json) in json_values.into_iter().enumerate() {
2312            let path = self.paths.get(i).cloned().unwrap_or_default();
2313            let metadata: Metadata = serde_json::from_value(json)?;
2314            results.push((path, metadata));
2315        }
2316
2317        Ok(results)
2318    }
2319
2320    /// 构建参数列表
2321    fn build_args(&self) -> Vec<String> {
2322        let mut args = vec!["-json".to_string()];
2323
2324        if self.group_by_category {
2325            args.push("-g1".to_string());
2326        }
2327
2328        if self.include_unknown {
2329            args.push("-u".to_string());
2330        }
2331
2332        if self.include_duplicates {
2333            args.push("-a".to_string());
2334        }
2335
2336        if self.raw_values {
2337            args.push("-n".to_string());
2338        }
2339
2340        args.extend(self.args.clone());
2341
2342        for tag in &self.specific_tags {
2343            args.push(format!("-{}", tag));
2344        }
2345
2346        // 添加所有文件路径
2347        for path in &self.paths {
2348            args.push(path.to_string_lossy().to_string());
2349        }
2350
2351        args
2352    }
2353}
2354
2355#[cfg(test)]
2356mod tests {
2357    use crate::Error;
2358
2359    #[test]
2360    fn test_response_warning_not_error() {
2361        let response = crate::Response::new(vec!["Warning: test".to_string()]);
2362        assert!(!response.is_error());
2363    }
2364
2365    #[test]
2366    fn test_query_builder_args() {
2367        let exiftool = match crate::ExifTool::new() {
2368            Ok(et) => et,
2369            Err(Error::ExifToolNotFound) => return,
2370            Err(e) => panic!("Unexpected error: {:?}", e),
2371        };
2372
2373        let args = exiftool
2374            .query("photo.jpg")
2375            .charset("utf8")
2376            .lang("zh")
2377            .coord_format("%.6f")
2378            .date_format("%Y-%m-%d")
2379            .print_format("$FileName")
2380            .separator(",")
2381            .api_option("QuickTimeUTC", Some("1"))
2382            .user_param("k", Some("v"))
2383            .password("p")
2384            .build_args(true);
2385
2386        assert!(args.windows(2).any(|w| w == ["-charset", "utf8"]));
2387        assert!(args.windows(2).any(|w| w == ["-lang", "zh"]));
2388        assert!(args.windows(2).any(|w| w == ["-c", "%.6f"]));
2389        assert!(args.windows(2).any(|w| w == ["-d", "%Y-%m-%d"]));
2390        assert!(args.windows(2).any(|w| w == ["-p", "$FileName"]));
2391        assert!(args.windows(2).any(|w| w == ["-sep", ","]));
2392        assert!(args.windows(2).any(|w| w == ["-api", "QuickTimeUTC=1"]));
2393        assert!(args.windows(2).any(|w| w == ["-userParam", "k=v"]));
2394        assert!(args.windows(2).any(|w| w == ["-password", "p"]));
2395    }
2396
2397    /// 测试 args_file 方法:验证 -@ 参数文件选项构建正确
2398    #[test]
2399    fn test_args_file() {
2400        let exiftool = match crate::ExifTool::new() {
2401            Ok(et) => et,
2402            Err(Error::ExifToolNotFound) => return,
2403            Err(e) => panic!("创建 ExifTool 实例时发生意外错误: {:?}", e),
2404        };
2405
2406        // 创建临时参数文件,写入 -FileName
2407        let tmp_dir = std::env::temp_dir();
2408        let args_path = tmp_dir.join("exiftool_test_args.txt");
2409        std::fs::write(&args_path, "-FileName\n").expect("写入临时参数文件失败");
2410
2411        let args = exiftool
2412            .query("photo.jpg")
2413            .args_file(args_path.to_string_lossy().as_ref())
2414            .build_args(false);
2415
2416        // 验证 args 包含 ["-@", "<路径>"]
2417        assert!(
2418            args.windows(2)
2419                .any(|w| w[0] == "-@" && w[1] == args_path.to_string_lossy().as_ref()),
2420            "参数列表应包含 -@ 和参数文件路径,实际: {:?}",
2421            args
2422        );
2423
2424        // 清理临时文件
2425        let _ = std::fs::remove_file(&args_path);
2426    }
2427
2428    /// 测试 csv_delimiter 方法:验证 -csvDelim 参数构建正确
2429    #[test]
2430    fn test_csv_delimiter() {
2431        let exiftool = match crate::ExifTool::new() {
2432            Ok(et) => et,
2433            Err(Error::ExifToolNotFound) => return,
2434            Err(e) => panic!("创建 ExifTool 实例时发生意外错误: {:?}", e),
2435        };
2436
2437        let args = exiftool
2438            .query("photo.jpg")
2439            .csv_delimiter(",")
2440            .build_args(false);
2441
2442        assert!(
2443            args.windows(2).any(|w| w == ["-csvDelim", ","]),
2444            "参数列表应包含 [\"-csvDelim\", \",\"],实际: {:?}",
2445            args
2446        );
2447    }
2448
2449    /// 测试 unknown_binary 方法:验证 -U 参数构建正确
2450    #[test]
2451    fn test_unknown_binary() {
2452        let exiftool = match crate::ExifTool::new() {
2453            Ok(et) => et,
2454            Err(Error::ExifToolNotFound) => return,
2455            Err(e) => panic!("创建 ExifTool 实例时发生意外错误: {:?}", e),
2456        };
2457
2458        let args = exiftool
2459            .query("photo.jpg")
2460            .unknown_binary()
2461            .build_args(false);
2462
2463        assert!(
2464            args.iter().any(|a| a == "-U"),
2465            "参数列表应包含 \"-U\",实际: {:?}",
2466            args
2467        );
2468    }
2469
2470    /// 测试 recursive_hidden 方法:验证 -r. 参数构建正确
2471    #[test]
2472    fn test_recursive_hidden() {
2473        let exiftool = match crate::ExifTool::new() {
2474            Ok(et) => et,
2475            Err(Error::ExifToolNotFound) => return,
2476            Err(e) => panic!("创建 ExifTool 实例时发生意外错误: {:?}", e),
2477        };
2478
2479        let args = exiftool
2480            .query("/photos")
2481            .recursive_hidden()
2482            .build_args(false);
2483
2484        assert!(
2485            args.iter().any(|a| a == "-r."),
2486            "参数列表应包含 \"-r.\",实际: {:?}",
2487            args
2488        );
2489    }
2490
2491    /// 测试 source_file 方法:验证 -srcfile 参数构建正确
2492    #[test]
2493    fn test_source_file() {
2494        let exiftool = match crate::ExifTool::new() {
2495            Ok(et) => et,
2496            Err(Error::ExifToolNotFound) => return,
2497            Err(e) => panic!("创建 ExifTool 实例时发生意外错误: {:?}", e),
2498        };
2499
2500        let args = exiftool
2501            .query("photo.jpg")
2502            .source_file("%d%f.xmp")
2503            .build_args(false);
2504
2505        assert!(
2506            args.windows(2).any(|w| w == ["-srcfile", "%d%f.xmp"]),
2507            "参数列表应包含 [\"-srcfile\", \"%d%f.xmp\"],实际: {:?}",
2508            args
2509        );
2510    }
2511}