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: Option<u32>,
59    php_format: bool,
60    plot_format: bool,
61    args_format: bool,
62    // 其他选项
63    common_args: Vec<String>,
64    echo: Vec<(String, Option<String>)>,
65    efile: Option<String>,
66}
67
68impl<'et> QueryBuilder<'et> {
69    /// 创建新的查询构建器
70    pub(crate) fn new<P: AsRef<Path>>(exiftool: &'et ExifTool, path: P) -> Self {
71        Self {
72            exiftool,
73            path: path.as_ref().to_path_buf(),
74            args: Vec::new(),
75            include_unknown: false,
76            include_duplicates: false,
77            raw_values: false,
78            group_by_category: false,
79            no_composite: false,
80            extract_embedded: None,
81            extensions: Vec::new(),
82            ignore_dirs: Vec::new(),
83            recursive: false,
84            progress_interval: None,
85            progress_title: None,
86            specific_tags: Vec::new(),
87            excluded_tags: Vec::new(),
88            // 输出格式选项
89            decimal: false,
90            escape_format: None,
91            force_print: false,
92            group_names: None,
93            html_format: false,
94            hex: false,
95            long_format: false,
96            latin: false,
97            short_format: None,
98            tab_format: false,
99            table_format: false,
100            text_out: None,
101            tag_out: None,
102            tag_out_ext: Vec::new(),
103            list_item: None,
104            file_order: None,
105            quiet: false,
106            // 高级输出选项
107            html_dump: None,
108            php_format: false,
109            plot_format: false,
110            args_format: false,
111            // 其他选项
112            common_args: Vec::new(),
113            echo: Vec::new(),
114            efile: None,
115        }
116    }
117
118    /// 包含未知标签
119    pub fn include_unknown(mut self, yes: bool) -> Self {
120        self.include_unknown = yes;
121        self
122    }
123
124    /// 包含重复标签
125    pub fn include_duplicates(mut self, yes: bool) -> Self {
126        self.include_duplicates = yes;
127        self
128    }
129
130    /// 显示原始数值(而非格式化后的值)
131    pub fn raw_values(mut self, yes: bool) -> Self {
132        self.raw_values = yes;
133        self
134    }
135
136    /// 按类别分组(-g1 选项)
137    pub fn group_by_category(mut self, yes: bool) -> Self {
138        self.group_by_category = yes;
139        self
140    }
141
142    /// 禁用复合标签生成
143    ///
144    /// 使用 `-e` 选项禁用复合标签(Composite tags)的生成。
145    /// 复合标签是由 ExifTool 根据其他标签计算得出的派生标签。
146    ///
147    /// # 示例
148    ///
149    /// ```rust,no_run
150    /// use exiftool_rs_wrapper::ExifTool;
151    ///
152    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
153    /// let exiftool = ExifTool::new()?;
154    ///
155    /// // 只读取原始标签,不生成复合标签
156    /// let metadata = exiftool.query("photo.jpg")
157    ///     .no_composite(true)
158    ///     .execute()?;
159    /// # Ok(())
160    /// # }
161    /// ```
162    pub fn no_composite(mut self, yes: bool) -> Self {
163        self.no_composite = yes;
164        self
165    }
166
167    /// 提取嵌入文件信息
168    ///
169    /// 使用 `-ee` 选项从文件中提取嵌入的文件信息。
170    /// 例如从 RAW 文件中提取 JPEG 预览图的元数据。
171    ///
172    /// # 级别
173    ///
174    /// - `None` - 不提取嵌入文件(默认)
175    /// - `Some(1)` - `-ee` 提取直接嵌入的文件
176    /// - `Some(2)` - `-ee2` 提取所有层级的嵌入文件
177    /// - `Some(3+)` - `-ee3` 及以上更深入的提取
178    ///
179    /// # 示例
180    ///
181    /// ```rust,no_run
182    /// use exiftool_rs_wrapper::ExifTool;
183    ///
184    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
185    /// let exiftool = ExifTool::new()?;
186    ///
187    /// // 提取嵌入文件信息
188    /// let metadata = exiftool.query("photo.raw")
189    ///     .extract_embedded(Some(1))
190    ///     .execute()?;
191    /// # Ok(())
192    /// # }
193    /// ```
194    pub fn extract_embedded(mut self, level: Option<u8>) -> Self {
195        self.extract_embedded = level;
196        self
197    }
198
199    /// 设置文件扩展名过滤
200    ///
201    /// 使用 `-ext` 选项只处理指定扩展名的文件。
202    /// 可以使用多次来指定多个扩展名。
203    ///
204    /// # 示例
205    ///
206    /// ```rust,no_run
207    /// use exiftool_rs_wrapper::ExifTool;
208    ///
209    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
210    /// let exiftool = ExifTool::new()?;
211    ///
212    /// // 只处理 jpg 文件
213    /// let metadata = exiftool.query("/photos")
214    ///     .extension("jpg")
215    ///     .recursive(true)
216    ///     .execute()?;
217    ///
218    /// // 处理多个扩展名
219    /// let metadata = exiftool.query("/photos")
220    ///     .extension("jpg")
221    ///     .extension("png")
222    ///     .extension("raw")
223    ///     .recursive(true)
224    ///     .execute()?;
225    /// # Ok(())
226    /// # }
227    /// ```
228    pub fn extension(mut self, ext: impl Into<String>) -> Self {
229        self.extensions.push(ext.into());
230        self
231    }
232
233    /// 设置要忽略的目录
234    ///
235    /// 使用 `-i` 选项忽略指定的目录名称。
236    /// 在递归处理时,匹配的目录将被跳过。
237    ///
238    /// # 示例
239    ///
240    /// ```rust,no_run
241    /// use exiftool_rs_wrapper::ExifTool;
242    ///
243    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
244    /// let exiftool = ExifTool::new()?;
245    ///
246    /// // 忽略 .git 和 node_modules 目录
247    /// let metadata = exiftool.query("/project")
248    ///     .ignore(".git")
249    ///     .ignore("node_modules")
250    ///     .recursive(true)
251    ///     .execute()?;
252    /// # Ok(())
253    /// # }
254    /// ```
255    pub fn ignore(mut self, dir: impl Into<String>) -> Self {
256        self.ignore_dirs.push(dir.into());
257        self
258    }
259
260    /// 递归处理子目录
261    ///
262    /// 使用 `-r` 选项递归处理目录中的所有文件。
263    ///
264    /// # 示例
265    ///
266    /// ```rust,no_run
267    /// use exiftool_rs_wrapper::ExifTool;
268    ///
269    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
270    /// let exiftool = ExifTool::new()?;
271    ///
272    /// // 递归处理整个目录树
273    /// let metadata = exiftool.query("/photos")
274    ///     .recursive(true)
275    ///     .extension("jpg")
276    ///     .execute()?;
277    /// # Ok(())
278    /// # }
279    /// ```
280    pub fn recursive(mut self, yes: bool) -> Self {
281        self.recursive = yes;
282        self
283    }
284
285    /// 启用进度显示
286    ///
287    /// 使用 `-progress` 选项在处理文件时显示进度信息。
288    /// 可以指定间隔(每隔多少文件显示一次)和标题。
289    ///
290    /// # 参数
291    ///
292    /// - `interval` - 每隔多少文件显示一次进度(None 表示每个文件都显示)
293    /// - `title` - 进度信息的标题(可选)
294    ///
295    /// # 示例
296    ///
297    /// ```rust,no_run
298    /// use exiftool_rs_wrapper::ExifTool;
299    ///
300    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
301    /// let exiftool = ExifTool::new()?;
302    ///
303    /// // 每 10 个文件显示一次进度
304    /// let metadata = exiftool.query("/photos")
305    ///     .recursive(true)
306    ///     .progress(Some(10), Some("Processing"))
307    ///     .execute()?;
308    /// # Ok(())
309    /// # }
310    /// ```
311    pub fn progress(mut self, interval: Option<u32>, title: Option<impl Into<String>>) -> Self {
312        self.progress_interval = interval;
313        self.progress_title = title.map(|t| t.into());
314        self
315    }
316
317    /// 添加特定标签查询
318    pub fn tag(mut self, tag: impl Into<String>) -> Self {
319        self.specific_tags.push(tag.into());
320        self
321    }
322
323    /// 添加多个标签查询
324    pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
325        for tag in tags {
326            self.specific_tags.push(tag.as_ref().to_string());
327        }
328        self
329    }
330
331    /// 添加特定标签查询(使用 TagId)
332    pub fn tag_id(self, tag: TagId) -> Self {
333        self.tag(tag.name())
334    }
335
336    /// 排除特定标签
337    pub fn exclude(mut self, tag: impl Into<String>) -> Self {
338        self.excluded_tags.push(tag.into());
339        self
340    }
341
342    /// 排除多个标签
343    pub fn excludes(mut self, tags: &[impl AsRef<str>]) -> Self {
344        for tag in tags {
345            self.excluded_tags.push(tag.as_ref().to_string());
346        }
347        self
348    }
349
350    /// 排除特定标签(使用 TagId)
351    pub fn exclude_id(self, tag: TagId) -> Self {
352        self.exclude(tag.name())
353    }
354
355    /// 使用特定编码
356    pub fn charset(mut self, charset: impl Into<String>) -> Self {
357        self.args.push(format!("-charset {}", charset.into()));
358        self
359    }
360
361    /// 使用特定语言
362    pub fn lang(mut self, lang: impl Into<String>) -> Self {
363        self.args.push(format!("-lang {}", lang.into()));
364        self
365    }
366
367    /// 设置 GPS 坐标格式
368    ///
369    /// 使用 `-c` 选项设置 GPS 坐标的输出格式
370    ///
371    /// # 示例
372    ///
373    /// ```rust,no_run
374    /// use exiftool_rs_wrapper::ExifTool;
375    ///
376    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
377    /// let exiftool = ExifTool::new()?;
378    ///
379    /// // 使用小数度格式
380    /// let metadata = exiftool.query("photo.jpg")
381    ///     .coord_format("%.6f")
382    ///     .execute()?;
383    ///
384    /// // 使用度分秒格式
385    /// let metadata = exiftool.query("photo.jpg")
386    ///     .coord_format("%d deg %d' %.2f\"")
387    ///     .execute()?;
388    /// # Ok(())
389    /// # }
390    /// ```
391    pub fn coord_format(mut self, format: impl Into<String>) -> Self {
392        self.args.push(format!("-c {}", format.into()));
393        self
394    }
395
396    /// 设置日期/时间格式
397    ///
398    /// 使用 `-d` 选项设置日期/时间值的输出格式
399    ///
400    /// # 预设格式
401    ///
402    /// - `"%Y:%m:%d %H:%M:%S"` - 标准 EXIF 格式(默认)
403    /// - `"%Y-%m-%d"` - ISO 日期格式
404    /// - `"%c"` - 本地时间格式
405    /// - `"%F %T"` - ISO 8601 格式
406    ///
407    /// # 示例
408    ///
409    /// ```rust,no_run
410    /// use exiftool_rs_wrapper::ExifTool;
411    ///
412    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
413    /// let exiftool = ExifTool::new()?;
414    ///
415    /// // 使用 ISO 格式
416    /// let metadata = exiftool.query("photo.jpg")
417    ///     .date_format("%Y-%m-%d %H:%M:%S")
418    ///     .execute()?;
419    /// # Ok(())
420    /// # }
421    /// ```
422    pub fn date_format(mut self, format: impl Into<String>) -> Self {
423        self.args.push(format!("-d {}", format.into()));
424        self
425    }
426
427    /// 添加原始参数(高级用法)
428    pub fn arg(mut self, arg: impl Into<String>) -> Self {
429        self.args.push(arg.into());
430        self
431    }
432
433    /// 设置自定义打印格式
434    ///
435    /// 使用 `-p` 选项按指定格式打印输出。
436    /// 使用 `$TAGNAME` 语法引用标签值。
437    ///
438    /// # 格式语法
439    ///
440    /// - `$TAGNAME` - 插入标签值
441    /// - `$TAGNAME#` - 插入原始数值(无格式化)
442    /// - `${TAGNAME:FMT}` - 使用指定格式
443    /// - `$$` - 插入 `$` 字符
444    ///
445    /// # 示例
446    ///
447    /// ```rust,no_run
448    /// use exiftool_rs_wrapper::ExifTool;
449    ///
450    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
451    /// let exiftool = ExifTool::new()?;
452    ///
453    /// // 自定义输出格式
454    /// let output = exiftool.query("photo.jpg")
455    ///     .print_format("$FileName: $DateTimeOriginal ($Make $Model)")
456    ///     .execute_text()?;
457    ///
458    /// println!("{}", output);
459    /// # Ok(())
460    /// # }
461    /// ```
462    pub fn print_format(mut self, format: impl Into<String>) -> Self {
463        self.args.push(format!("-p {}", format.into()));
464        self
465    }
466
467    /// 按字母顺序排序输出
468    ///
469    /// 使用 `-sort` 选项对标签进行字母排序
470    pub fn sort(mut self, yes: bool) -> Self {
471        if yes {
472            self.args.push("-sort".to_string());
473        }
474        self
475    }
476
477    /// 设置列表项分隔符
478    ///
479    /// 使用 `-sep` 选项设置列表项的分隔符字符串
480    ///
481    /// # 示例
482    ///
483    /// ```rust,no_run
484    /// use exiftool_rs_wrapper::ExifTool;
485    ///
486    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
487    /// let exiftool = ExifTool::new()?;
488    ///
489    /// // 使用逗号分隔列表项
490    /// let metadata = exiftool.query("photo.jpg")
491    ///     .separator(", ")
492    ///     .execute()?;
493    /// # Ok(())
494    /// # }
495    /// ```
496    pub fn separator(mut self, sep: impl Into<String>) -> Self {
497        self.args.push(format!("-sep {}", sep.into()));
498        self
499    }
500
501    /// 启用快速模式
502    ///
503    /// 使用 `-fast` 选项提高元数据提取速度。
504    /// 这会跳过某些处理步骤,可能遗漏某些信息。
505    ///
506    /// # 级别
507    ///
508    /// - `None` - 不使用快速模式(默认)
509    /// - `Some(1)` - `-fast` 基础快速模式
510    /// - `Some(2)` - `-fast2` 更激进的快速模式
511    ///
512    /// # 示例
513    ///
514    /// ```rust,no_run
515    /// use exiftool_rs_wrapper::ExifTool;
516    ///
517    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
518    /// let exiftool = ExifTool::new()?;
519    ///
520    /// // 使用快速模式处理大量文件
521    /// let metadata = exiftool.query("photo.jpg")
522    ///     .fast(Some(1))
523    ///     .execute()?;
524    /// # Ok(())
525    /// # }
526    /// ```
527    pub fn fast(mut self, level: Option<u8>) -> Self {
528        match level {
529            Some(1) => self.args.push("-fast".to_string()),
530            Some(l) if l > 1 => self.args.push(format!("-fast{}", l)),
531            _ => {}
532        }
533        self
534    }
535
536    /// 强制扫描 XMP 数据
537    ///
538    /// 使用 `-scanForXMP` 选项暴力扫描文件中的 XMP 数据
539    pub fn scan_for_xmp(mut self, yes: bool) -> Self {
540        if yes {
541            self.args.push("-scanForXMP".to_string());
542        }
543        self
544    }
545
546    /// 设置 API 选项
547    ///
548    /// 使用 `-api` 选项设置 ExifTool API 选项。
549    /// 常见选项包括:`QuickTimeUTC`, `SystemTags`, `largefilesupport`
550    ///
551    /// # 示例
552    ///
553    /// ```rust,no_run
554    /// use exiftool_rs_wrapper::ExifTool;
555    ///
556    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
557    /// let exiftool = ExifTool::new()?;
558    ///
559    /// // 启用 QuickTimeUTC
560    /// let metadata = exiftool.query("video.mp4")
561    ///     .api_option("QuickTimeUTC", None::<&str>)
562    ///     .execute()?;
563    /// # Ok(())
564    /// # }
565    /// ```
566    pub fn api_option(mut self, opt: impl Into<String>, value: Option<impl Into<String>>) -> Self {
567        let arg = match value {
568            Some(v) => format!("-api {}={}", opt.into(), v.into()),
569            None => format!("-api {}", opt.into()),
570        };
571        self.args.push(arg);
572        self
573    }
574
575    /// 设置用户参数
576    ///
577    /// 使用 `-userParam` 选项设置用户参数,可在配置文件中使用
578    ///
579    /// # 示例
580    ///
581    /// ```rust,no_run
582    /// use exiftool_rs_wrapper::ExifTool;
583    ///
584    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
585    /// let exiftool = ExifTool::new()?;
586    ///
587    /// // 设置自定义参数
588    /// let metadata = exiftool.query("photo.jpg")
589    ///     .user_param("MyParam", Some("value"))
590    ///     .execute()?;
591    /// # Ok(())
592    /// # }
593    /// ```
594    pub fn user_param(
595        mut self,
596        param: impl Into<String>,
597        value: Option<impl Into<String>>,
598    ) -> Self {
599        let arg = match value {
600            Some(v) => format!("-userParam {}={}", param.into(), v.into()),
601            None => format!("-userParam {}", param.into()),
602        };
603        self.args.push(arg);
604        self
605    }
606
607    /// 设置密码
608    ///
609    /// 使用 `-password` 选项处理受密码保护的文件
610    ///
611    /// # 安全性警告
612    ///
613    /// 密码将以纯文本形式传递给 ExifTool 进程。
614    /// 在多用户系统中使用时请注意安全性。
615    ///
616    /// # 示例
617    ///
618    /// ```rust,no_run
619    /// use exiftool_rs_wrapper::ExifTool;
620    ///
621    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
622    /// let exiftool = ExifTool::new()?;
623    ///
624    /// // 读取受密码保护的 PDF
625    /// let metadata = exiftool.query("protected.pdf")
626    ///     .password("secret123")
627    ///     .execute()?;
628    /// # Ok(())
629    /// # }
630    /// ```
631    pub fn password(mut self, passwd: impl Into<String>) -> Self {
632        self.args.push(format!("-password {}", passwd.into()));
633        self
634    }
635
636    /// 十进制显示标签 ID
637    ///
638    /// 使用 `-D` 选项以十进制格式显示标签 ID 编号
639    pub fn decimal(mut self, yes: bool) -> Self {
640        self.decimal = yes;
641        self
642    }
643
644    /// 转义格式
645    ///
646    /// 使用 `-E`、`-ex` 或 `-ec` 选项转义标签值
647    pub fn escape(mut self, format: EscapeFormat) -> Self {
648        self.escape_format = Some(format);
649        self
650    }
651
652    /// 强制打印
653    ///
654    /// 使用 `-f` 选项强制打印所有指定标签
655    pub fn force_print(mut self, yes: bool) -> Self {
656        self.force_print = yes;
657        self
658    }
659
660    /// 打印组名
661    ///
662    /// 使用 `-G` 选项打印每个标签的组名
663    pub fn group_names(mut self, level: Option<u8>) -> Self {
664        self.group_names = level;
665        self
666    }
667
668    /// HTML 格式
669    ///
670    /// 使用 `-h` 选项以 HTML 格式输出
671    pub fn html_format(mut self, yes: bool) -> Self {
672        self.html_format = yes;
673        self
674    }
675
676    /// 十六进制显示
677    ///
678    /// 使用 `-H` 选项以十六进制显示标签 ID
679    pub fn hex(mut self, yes: bool) -> Self {
680        self.hex = yes;
681        self
682    }
683
684    /// 长格式输出
685    ///
686    /// 使用 `-l` 选项以长格式(2行)输出
687    pub fn long_format(mut self, yes: bool) -> Self {
688        self.long_format = yes;
689        self
690    }
691
692    /// Latin1 编码
693    ///
694    /// 使用 `-L` 选项使用 Windows Latin1 编码
695    pub fn latin(mut self, yes: bool) -> Self {
696        self.latin = yes;
697        self
698    }
699
700    /// 短格式输出
701    ///
702    /// 使用 `-s` 或 `-S` 选项以短格式输出
703    pub fn short_format(mut self, level: Option<u8>) -> Self {
704        self.short_format = level;
705        self
706    }
707
708    /// Tab 分隔格式
709    ///
710    /// 使用 `-t` 选项以 Tab 分隔格式输出
711    pub fn tab_format(mut self, yes: bool) -> Self {
712        self.tab_format = yes;
713        self
714    }
715
716    /// 表格格式
717    ///
718    /// 使用 `-T` 选项以表格格式输出
719    pub fn table_format(mut self, yes: bool) -> Self {
720        self.table_format = yes;
721        self
722    }
723
724    /// 文本输出到文件
725    ///
726    /// 使用 `-w` 选项将输出写入文件
727    pub fn text_out(mut self, ext: impl Into<String>) -> Self {
728        self.text_out = Some(ext.into());
729        self
730    }
731
732    /// 标签输出到文件
733    ///
734    /// 使用 `-W` 选项为每个标签创建输出文件
735    pub fn tag_out(mut self, format: impl Into<String>) -> Self {
736        self.tag_out = Some(format.into());
737        self
738    }
739
740    /// 标签输出扩展名过滤
741    ///
742    /// 使用 `-Wext` 选项指定 `-W` 输出的文件类型
743    pub fn tag_out_ext(mut self, ext: impl Into<String>) -> Self {
744        self.tag_out_ext.push(ext.into());
745        self
746    }
747
748    /// 提取列表项
749    ///
750    /// 使用 `-listItem` 选项提取列表中的特定项
751    pub fn list_item(mut self, index: u32) -> Self {
752        self.list_item = Some(index);
753        self
754    }
755
756    /// 文件处理顺序
757    ///
758    /// 使用 `-fileOrder` 选项设置文件处理顺序
759    pub fn file_order(mut self, tag: impl Into<String>, descending: bool) -> Self {
760        self.file_order = Some((tag.into(), descending));
761        self
762    }
763
764    /// 静默模式
765    ///
766    /// 使用 `-q` 选项减少输出信息
767    pub fn quiet(mut self, yes: bool) -> Self {
768        self.quiet = yes;
769        self
770    }
771
772    /// HTML二进制转储
773    ///
774    /// 使用 `-htmlDump` 选项生成HTML格式的二进制转储
775    /// 可以指定可选的偏移量
776    pub fn html_dump(mut self, offset: Option<u32>) -> Self {
777        self.html_dump = offset;
778        self
779    }
780
781    /// PHP数组格式输出
782    ///
783    /// 使用 `-php` 选项导出为PHP数组格式
784    pub fn php_format(mut self, yes: bool) -> Self {
785        self.php_format = yes;
786        self
787    }
788
789    /// SVG plot格式输出
790    ///
791    /// 使用 `-plot` 选项输出为SVG plot文件
792    pub fn plot_format(mut self, yes: bool) -> Self {
793        self.plot_format = yes;
794        self
795    }
796
797    /// 格式化为exiftool参数
798    ///
799    /// 使用 `-args` 选项将元数据格式化为exiftool参数格式
800    pub fn args_format(mut self, yes: bool) -> Self {
801        self.args_format = yes;
802        self
803    }
804
805    /// 设置公共参数
806    ///
807    /// 使用 `-common_args` 选项定义在多个命令之间共享的参数
808    pub fn common_args(mut self, args: &[impl AsRef<str>]) -> Self {
809        for arg in args {
810            self.common_args.push(arg.as_ref().to_string());
811        }
812        self
813    }
814
815    /// 输出文本到stdout或stderr
816    ///
817    /// 使用 `-echo` 选项在处理期间输出文本
818    /// - `text`: 要输出的文本
819    /// - `target`: 输出目标,None表示stdout,Some("stderr")表示stderr
820    pub fn echo(mut self, text: impl Into<String>, target: Option<impl Into<String>>) -> Self {
821        self.echo.push((text.into(), target.map(|t| t.into())));
822        self
823    }
824
825    /// 保存错误文件名到文件
826    ///
827    /// 使用 `-efile` 选项将处理失败的文件名保存到指定文件
828    pub fn efile(mut self, filename: impl Into<String>) -> Self {
829        self.efile = Some(filename.into());
830        self
831    }
832
833    /// 执行查询
834    pub fn execute(self) -> Result<Metadata> {
835        let args = self.build_args();
836
837        // 发送命令并获取响应
838        let response = self.exiftool.execute_raw(&args)?;
839
840        // 解析响应
841        self.parse_response(response)
842    }
843
844    /// 执行查询并返回 JSON
845    pub fn execute_json(self) -> Result<serde_json::Value> {
846        let args = self.build_args();
847        let response = self.exiftool.execute_raw(&args)?;
848        response.json()
849    }
850
851    /// 执行查询并反序列化为自定义类型
852    pub fn execute_as<T: serde::de::DeserializeOwned>(self) -> Result<T> {
853        let args = self.build_args();
854        let response = self.exiftool.execute_raw(&args)?;
855        response.json()
856    }
857
858    /// 执行查询并返回纯文本
859    ///
860    /// 当使用 `-p` (print_format) 选项时,
861    /// 使用此方法获取纯文本输出而非 JSON
862    ///
863    /// # 示例
864    ///
865    /// ```rust,no_run
866    /// use exiftool_rs_wrapper::ExifTool;
867    ///
868    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
869    /// let exiftool = ExifTool::new()?;
870    ///
871    /// let output = exiftool.query("photo.jpg")
872    ///     .print_format("$FileName: $DateTimeOriginal")
873    ///     .execute_text()?;
874    ///
875    /// println!("{}", output);
876    /// # Ok(())
877    /// # }
878    /// ```
879    pub fn execute_text(self) -> Result<String> {
880        let args = self.build_args();
881        let response = self.exiftool.execute_raw(&args)?;
882        Ok(response.text().trim().to_string())
883    }
884
885    /// 构建参数列表
886    fn build_args(&self) -> Vec<String> {
887        let mut args = vec!["-json".to_string()];
888
889        // 分组选项
890        if self.group_by_category {
891            args.push("-g1".to_string());
892        }
893
894        // 未知标签
895        if self.include_unknown {
896            args.push("-u".to_string());
897        }
898
899        // 重复标签
900        if self.include_duplicates {
901            args.push("-a".to_string());
902        }
903
904        // 原始数值
905        if self.raw_values {
906            args.push("-n".to_string());
907        }
908
909        // 十进制显示
910        if self.decimal {
911            args.push("-D".to_string());
912        }
913
914        // 转义格式
915        if let Some(format) = self.escape_format {
916            let flag = match format {
917                EscapeFormat::Html => "-E",
918                EscapeFormat::Xml => "-ex",
919                EscapeFormat::C => "-ec",
920            };
921            args.push(flag.to_string());
922        }
923
924        // 强制打印
925        if self.force_print {
926            args.push("-f".to_string());
927        }
928
929        // 组名
930        if let Some(level) = self.group_names {
931            if level == 1 {
932                args.push("-G".to_string());
933            } else {
934                args.push(format!("-G{}", level));
935            }
936        }
937
938        // HTML 格式
939        if self.html_format {
940            args.push("-h".to_string());
941        }
942
943        // 十六进制
944        if self.hex {
945            args.push("-H".to_string());
946        }
947
948        // 长格式
949        if self.long_format {
950            args.push("-l".to_string());
951        }
952
953        // Latin1 编码
954        if self.latin {
955            args.push("-L".to_string());
956        }
957
958        // 短格式
959        if let Some(level) = self.short_format {
960            if level == 0 {
961                args.push("-S".to_string());
962            } else {
963                args.push(format!("-s{}", level));
964            }
965        }
966
967        // Tab 格式
968        if self.tab_format {
969            args.push("-t".to_string());
970        }
971
972        // 表格格式
973        if self.table_format {
974            args.push("-T".to_string());
975        }
976
977        // 文本输出
978        if let Some(ref ext) = self.text_out {
979            args.push(format!("-w {}", ext));
980        }
981
982        // 标签输出
983        if let Some(ref format) = self.tag_out {
984            args.push(format!("-W {}", format));
985        }
986
987        // 标签输出扩展名
988        for ext in &self.tag_out_ext {
989            args.push(format!("-Wext {}", ext));
990        }
991
992        // 列表项
993        if let Some(index) = self.list_item {
994            args.push(format!("-listItem {}", index));
995        }
996
997        // 文件顺序
998        if let Some((ref tag, descending)) = self.file_order {
999            let order = if descending { "-" } else { "" };
1000            args.push(format!("-fileOrder {}{}", order, tag));
1001        }
1002
1003        // 静默模式
1004        if self.quiet {
1005            args.push("-q".to_string());
1006        }
1007
1008        // HTML二进制转储
1009        if let Some(offset) = self.html_dump {
1010            args.push(format!("-htmlDump{}", offset));
1011        }
1012
1013        // PHP数组格式
1014        if self.php_format {
1015            args.push("-php".to_string());
1016        }
1017
1018        // SVG plot格式
1019        if self.plot_format {
1020            args.push("-plot".to_string());
1021        }
1022
1023        // 格式化为exiftool参数
1024        if self.args_format {
1025            args.push("-args".to_string());
1026        }
1027
1028        // 公共参数
1029        for arg in &self.common_args {
1030            args.push(format!("-common_args {}", arg));
1031        }
1032
1033        // echo输出
1034        for (text, target) in &self.echo {
1035            let echo_arg = match target {
1036                Some(t) if t == "stderr" => format!("-echo2 {}", text),
1037                _ => format!("-echo {}", text),
1038            };
1039            args.push(echo_arg);
1040        }
1041
1042        // 错误文件
1043        if let Some(ref filename) = self.efile {
1044            args.push(format!("-efile {}", filename));
1045        }
1046
1047        // 禁用复合标签
1048        if self.no_composite {
1049            args.push("-e".to_string());
1050        }
1051
1052        // 提取嵌入文件
1053        if let Some(level) = self.extract_embedded {
1054            if level == 1 {
1055                args.push("-ee".to_string());
1056            } else {
1057                args.push(format!("-ee{}", level));
1058            }
1059        }
1060
1061        // 文件扩展名过滤
1062        for ext in &self.extensions {
1063            args.push(format!("-ext {}", ext));
1064        }
1065
1066        // 忽略目录
1067        for dir in &self.ignore_dirs {
1068            args.push(format!("-i {}", dir));
1069        }
1070
1071        // 递归处理
1072        if self.recursive {
1073            args.push("-r".to_string());
1074        }
1075
1076        // 进度显示
1077        if let Some(interval) = self.progress_interval {
1078            let progress_arg = if let Some(ref title) = self.progress_title {
1079                format!("-progress{}:{}", interval, title)
1080            } else {
1081                format!("-progress{}", interval)
1082            };
1083            args.push(progress_arg);
1084        }
1085
1086        // 添加自定义参数
1087        args.extend(self.args.clone());
1088
1089        // 特定标签
1090        for tag in &self.specific_tags {
1091            args.push(format!("-{}", tag));
1092        }
1093
1094        // 排除标签
1095        for tag in &self.excluded_tags {
1096            args.push(format!("-{}=", tag));
1097        }
1098
1099        // 文件路径
1100        args.push(self.path.to_string_lossy().to_string());
1101
1102        args
1103    }
1104
1105    /// 解析响应
1106    fn parse_response(&self, response: Response) -> Result<Metadata> {
1107        if response.is_error() {
1108            return Err(Error::process(
1109                response
1110                    .error_message()
1111                    .unwrap_or_else(|| "Unknown error".to_string()),
1112            ));
1113        }
1114
1115        let json_value: Vec<serde_json::Value> = response.json()?;
1116
1117        if json_value.is_empty() {
1118            return Ok(Metadata::new());
1119        }
1120
1121        // 取第一个(也是唯一一个)结果
1122        let metadata: Metadata = serde_json::from_value(json_value[0].clone())?;
1123
1124        Ok(metadata)
1125    }
1126}
1127
1128/// 批量查询构建器
1129pub struct BatchQueryBuilder<'et> {
1130    exiftool: &'et ExifTool,
1131    paths: Vec<PathBuf>,
1132    args: Vec<String>,
1133    include_unknown: bool,
1134    include_duplicates: bool,
1135    raw_values: bool,
1136    group_by_category: bool,
1137    specific_tags: Vec<String>,
1138}
1139
1140impl<'et> BatchQueryBuilder<'et> {
1141    /// 创建新的批量查询构建器
1142    pub(crate) fn new(exiftool: &'et ExifTool, paths: Vec<PathBuf>) -> Self {
1143        Self {
1144            exiftool,
1145            paths,
1146            args: Vec::new(),
1147            include_unknown: false,
1148            include_duplicates: false,
1149            raw_values: false,
1150            group_by_category: false,
1151            specific_tags: Vec::new(),
1152        }
1153    }
1154
1155    /// 包含未知标签
1156    pub fn include_unknown(mut self, yes: bool) -> Self {
1157        self.include_unknown = yes;
1158        self
1159    }
1160
1161    /// 包含重复标签
1162    pub fn include_duplicates(mut self, yes: bool) -> Self {
1163        self.include_duplicates = yes;
1164        self
1165    }
1166
1167    /// 显示原始数值
1168    pub fn raw_values(mut self, yes: bool) -> Self {
1169        self.raw_values = yes;
1170        self
1171    }
1172
1173    /// 按类别分组
1174    pub fn group_by_category(mut self, yes: bool) -> Self {
1175        self.group_by_category = yes;
1176        self
1177    }
1178
1179    /// 添加特定标签查询
1180    pub fn tag(mut self, tag: impl Into<String>) -> Self {
1181        self.specific_tags.push(tag.into());
1182        self
1183    }
1184
1185    /// 添加多个标签查询
1186    pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
1187        for tag in tags {
1188            self.specific_tags.push(tag.as_ref().to_string());
1189        }
1190        self
1191    }
1192
1193    /// 设置 GPS 坐标格式
1194    pub fn coord_format(mut self, format: impl Into<String>) -> Self {
1195        self.args.push(format!("-c {}", format.into()));
1196        self
1197    }
1198
1199    /// 设置日期/时间格式
1200    pub fn date_format(mut self, format: impl Into<String>) -> Self {
1201        self.args.push(format!("-d {}", format.into()));
1202        self
1203    }
1204
1205    /// 设置密码
1206    pub fn password(mut self, passwd: impl Into<String>) -> Self {
1207        self.args.push(format!("-password {}", passwd.into()));
1208        self
1209    }
1210
1211    /// 启用快速模式
1212    pub fn fast(mut self, level: Option<u8>) -> Self {
1213        match level {
1214            Some(1) => self.args.push("-fast".to_string()),
1215            Some(l) if l > 1 => self.args.push(format!("-fast{}", l)),
1216            _ => {}
1217        }
1218        self
1219    }
1220
1221    /// 强制扫描 XMP 数据
1222    pub fn scan_for_xmp(mut self, yes: bool) -> Self {
1223        if yes {
1224            self.args.push("-scanForXMP".to_string());
1225        }
1226        self
1227    }
1228
1229    /// 设置 API 选项
1230    pub fn api_option(mut self, opt: impl Into<String>, value: Option<impl Into<String>>) -> Self {
1231        let arg = match value {
1232            Some(v) => format!("-api {}={}", opt.into(), v.into()),
1233            None => format!("-api {}", opt.into()),
1234        };
1235        self.args.push(arg);
1236        self
1237    }
1238
1239    /// 设置用户参数
1240    pub fn user_param(
1241        mut self,
1242        param: impl Into<String>,
1243        value: Option<impl Into<String>>,
1244    ) -> Self {
1245        let arg = match value {
1246            Some(v) => format!("-userParam {}={}", param.into(), v.into()),
1247            None => format!("-userParam {}", param.into()),
1248        };
1249        self.args.push(arg);
1250        self
1251    }
1252
1253    /// 设置自定义打印格式
1254    pub fn print_format(mut self, format: impl Into<String>) -> Self {
1255        self.args.push(format!("-p {}", format.into()));
1256        self
1257    }
1258
1259    /// 按字母顺序排序输出
1260    pub fn sort(mut self, yes: bool) -> Self {
1261        if yes {
1262            self.args.push("-sort".to_string());
1263        }
1264        self
1265    }
1266
1267    /// 设置列表项分隔符
1268    pub fn separator(mut self, sep: impl Into<String>) -> Self {
1269        self.args.push(format!("-sep {}", sep.into()));
1270        self
1271    }
1272
1273    /// 执行批量查询
1274    pub fn execute(self) -> Result<Vec<(PathBuf, Metadata)>> {
1275        if self.paths.is_empty() {
1276            return Ok(Vec::new());
1277        }
1278
1279        let args = self.build_args();
1280        let response = self.exiftool.execute_raw(&args)?;
1281
1282        // 解析 JSON 数组响应
1283        let json_values: Vec<serde_json::Value> = response.json()?;
1284
1285        let mut results = Vec::with_capacity(json_values.len());
1286
1287        for (i, json) in json_values.into_iter().enumerate() {
1288            let path = self.paths.get(i).cloned().unwrap_or_default();
1289            let metadata: Metadata = serde_json::from_value(json)?;
1290            results.push((path, metadata));
1291        }
1292
1293        Ok(results)
1294    }
1295
1296    /// 构建参数列表
1297    fn build_args(&self) -> Vec<String> {
1298        let mut args = vec!["-json".to_string()];
1299
1300        if self.group_by_category {
1301            args.push("-g1".to_string());
1302        }
1303
1304        if self.include_unknown {
1305            args.push("-u".to_string());
1306        }
1307
1308        if self.include_duplicates {
1309            args.push("-a".to_string());
1310        }
1311
1312        if self.raw_values {
1313            args.push("-n".to_string());
1314        }
1315
1316        args.extend(self.args.clone());
1317
1318        for tag in &self.specific_tags {
1319            args.push(format!("-{}", tag));
1320        }
1321
1322        // 添加所有文件路径
1323        for path in &self.paths {
1324            args.push(path.to_string_lossy().to_string());
1325        }
1326
1327        args
1328    }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333    #[test]
1334    fn test_query_builder_args() {
1335        // 这个测试需要 ExifTool 实例,所以只做简单的构建测试
1336        // 实际测试需要在集成测试中进行
1337    }
1338}