1use std::path::{Path, PathBuf};
4
5use crate::ExifTool;
6use crate::error::{Error, Result};
7use crate::process::Response;
8use crate::types::{Metadata, TagId};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EscapeFormat {
13 Html,
15 Xml,
17 C,
19}
20
21pub 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 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 html_dump: Option<u32>,
59 php_format: bool,
60 plot_format: bool,
61 args_format: bool,
62 common_args: Vec<String>,
64 echo: Vec<(String, Option<String>)>,
65 efile: Option<String>,
66}
67
68impl<'et> QueryBuilder<'et> {
69 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 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 html_dump: None,
108 php_format: false,
109 plot_format: false,
110 args_format: false,
111 common_args: Vec::new(),
113 echo: Vec::new(),
114 efile: None,
115 }
116 }
117
118 pub fn include_unknown(mut self, yes: bool) -> Self {
120 self.include_unknown = yes;
121 self
122 }
123
124 pub fn include_duplicates(mut self, yes: bool) -> Self {
126 self.include_duplicates = yes;
127 self
128 }
129
130 pub fn raw_values(mut self, yes: bool) -> Self {
132 self.raw_values = yes;
133 self
134 }
135
136 pub fn group_by_category(mut self, yes: bool) -> Self {
138 self.group_by_category = yes;
139 self
140 }
141
142 pub fn no_composite(mut self, yes: bool) -> Self {
163 self.no_composite = yes;
164 self
165 }
166
167 pub fn extract_embedded(mut self, level: Option<u8>) -> Self {
195 self.extract_embedded = level;
196 self
197 }
198
199 pub fn extension(mut self, ext: impl Into<String>) -> Self {
229 self.extensions.push(ext.into());
230 self
231 }
232
233 pub fn ignore(mut self, dir: impl Into<String>) -> Self {
256 self.ignore_dirs.push(dir.into());
257 self
258 }
259
260 pub fn recursive(mut self, yes: bool) -> Self {
281 self.recursive = yes;
282 self
283 }
284
285 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 pub fn tag(mut self, tag: impl Into<String>) -> Self {
319 self.specific_tags.push(tag.into());
320 self
321 }
322
323 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 pub fn tag_id(self, tag: TagId) -> Self {
333 self.tag(tag.name())
334 }
335
336 pub fn exclude(mut self, tag: impl Into<String>) -> Self {
338 self.excluded_tags.push(tag.into());
339 self
340 }
341
342 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 pub fn exclude_id(self, tag: TagId) -> Self {
352 self.exclude(tag.name())
353 }
354
355 pub fn charset(mut self, charset: impl Into<String>) -> Self {
357 self.args.push(format!("-charset {}", charset.into()));
358 self
359 }
360
361 pub fn lang(mut self, lang: impl Into<String>) -> Self {
363 self.args.push(format!("-lang {}", lang.into()));
364 self
365 }
366
367 pub fn coord_format(mut self, format: impl Into<String>) -> Self {
392 self.args.push(format!("-c {}", format.into()));
393 self
394 }
395
396 pub fn date_format(mut self, format: impl Into<String>) -> Self {
423 self.args.push(format!("-d {}", format.into()));
424 self
425 }
426
427 pub fn arg(mut self, arg: impl Into<String>) -> Self {
429 self.args.push(arg.into());
430 self
431 }
432
433 pub fn print_format(mut self, format: impl Into<String>) -> Self {
463 self.args.push(format!("-p {}", format.into()));
464 self
465 }
466
467 pub fn sort(mut self, yes: bool) -> Self {
471 if yes {
472 self.args.push("-sort".to_string());
473 }
474 self
475 }
476
477 pub fn separator(mut self, sep: impl Into<String>) -> Self {
497 self.args.push(format!("-sep {}", sep.into()));
498 self
499 }
500
501 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 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 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 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 pub fn password(mut self, passwd: impl Into<String>) -> Self {
632 self.args.push(format!("-password {}", passwd.into()));
633 self
634 }
635
636 pub fn decimal(mut self, yes: bool) -> Self {
640 self.decimal = yes;
641 self
642 }
643
644 pub fn escape(mut self, format: EscapeFormat) -> Self {
648 self.escape_format = Some(format);
649 self
650 }
651
652 pub fn force_print(mut self, yes: bool) -> Self {
656 self.force_print = yes;
657 self
658 }
659
660 pub fn group_names(mut self, level: Option<u8>) -> Self {
664 self.group_names = level;
665 self
666 }
667
668 pub fn html_format(mut self, yes: bool) -> Self {
672 self.html_format = yes;
673 self
674 }
675
676 pub fn hex(mut self, yes: bool) -> Self {
680 self.hex = yes;
681 self
682 }
683
684 pub fn long_format(mut self, yes: bool) -> Self {
688 self.long_format = yes;
689 self
690 }
691
692 pub fn latin(mut self, yes: bool) -> Self {
696 self.latin = yes;
697 self
698 }
699
700 pub fn short_format(mut self, level: Option<u8>) -> Self {
704 self.short_format = level;
705 self
706 }
707
708 pub fn tab_format(mut self, yes: bool) -> Self {
712 self.tab_format = yes;
713 self
714 }
715
716 pub fn table_format(mut self, yes: bool) -> Self {
720 self.table_format = yes;
721 self
722 }
723
724 pub fn text_out(mut self, ext: impl Into<String>) -> Self {
728 self.text_out = Some(ext.into());
729 self
730 }
731
732 pub fn tag_out(mut self, format: impl Into<String>) -> Self {
736 self.tag_out = Some(format.into());
737 self
738 }
739
740 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 pub fn list_item(mut self, index: u32) -> Self {
752 self.list_item = Some(index);
753 self
754 }
755
756 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 pub fn quiet(mut self, yes: bool) -> Self {
768 self.quiet = yes;
769 self
770 }
771
772 pub fn html_dump(mut self, offset: Option<u32>) -> Self {
777 self.html_dump = offset;
778 self
779 }
780
781 pub fn php_format(mut self, yes: bool) -> Self {
785 self.php_format = yes;
786 self
787 }
788
789 pub fn plot_format(mut self, yes: bool) -> Self {
793 self.plot_format = yes;
794 self
795 }
796
797 pub fn args_format(mut self, yes: bool) -> Self {
801 self.args_format = yes;
802 self
803 }
804
805 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 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 pub fn efile(mut self, filename: impl Into<String>) -> Self {
829 self.efile = Some(filename.into());
830 self
831 }
832
833 pub fn execute(self) -> Result<Metadata> {
835 let args = self.build_args();
836
837 let response = self.exiftool.execute_raw(&args)?;
839
840 self.parse_response(response)
842 }
843
844 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 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 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 fn build_args(&self) -> Vec<String> {
887 let mut args = vec!["-json".to_string()];
888
889 if self.group_by_category {
891 args.push("-g1".to_string());
892 }
893
894 if self.include_unknown {
896 args.push("-u".to_string());
897 }
898
899 if self.include_duplicates {
901 args.push("-a".to_string());
902 }
903
904 if self.raw_values {
906 args.push("-n".to_string());
907 }
908
909 if self.decimal {
911 args.push("-D".to_string());
912 }
913
914 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 if self.force_print {
926 args.push("-f".to_string());
927 }
928
929 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 if self.html_format {
940 args.push("-h".to_string());
941 }
942
943 if self.hex {
945 args.push("-H".to_string());
946 }
947
948 if self.long_format {
950 args.push("-l".to_string());
951 }
952
953 if self.latin {
955 args.push("-L".to_string());
956 }
957
958 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 if self.tab_format {
969 args.push("-t".to_string());
970 }
971
972 if self.table_format {
974 args.push("-T".to_string());
975 }
976
977 if let Some(ref ext) = self.text_out {
979 args.push(format!("-w {}", ext));
980 }
981
982 if let Some(ref format) = self.tag_out {
984 args.push(format!("-W {}", format));
985 }
986
987 for ext in &self.tag_out_ext {
989 args.push(format!("-Wext {}", ext));
990 }
991
992 if let Some(index) = self.list_item {
994 args.push(format!("-listItem {}", index));
995 }
996
997 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 if self.quiet {
1005 args.push("-q".to_string());
1006 }
1007
1008 if let Some(offset) = self.html_dump {
1010 args.push(format!("-htmlDump{}", offset));
1011 }
1012
1013 if self.php_format {
1015 args.push("-php".to_string());
1016 }
1017
1018 if self.plot_format {
1020 args.push("-plot".to_string());
1021 }
1022
1023 if self.args_format {
1025 args.push("-args".to_string());
1026 }
1027
1028 for arg in &self.common_args {
1030 args.push(format!("-common_args {}", arg));
1031 }
1032
1033 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 if let Some(ref filename) = self.efile {
1044 args.push(format!("-efile {}", filename));
1045 }
1046
1047 if self.no_composite {
1049 args.push("-e".to_string());
1050 }
1051
1052 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 for ext in &self.extensions {
1063 args.push(format!("-ext {}", ext));
1064 }
1065
1066 for dir in &self.ignore_dirs {
1068 args.push(format!("-i {}", dir));
1069 }
1070
1071 if self.recursive {
1073 args.push("-r".to_string());
1074 }
1075
1076 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 args.extend(self.args.clone());
1088
1089 for tag in &self.specific_tags {
1091 args.push(format!("-{}", tag));
1092 }
1093
1094 for tag in &self.excluded_tags {
1096 args.push(format!("-{}=", tag));
1097 }
1098
1099 args.push(self.path.to_string_lossy().to_string());
1101
1102 args
1103 }
1104
1105 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 let metadata: Metadata = serde_json::from_value(json_value[0].clone())?;
1123
1124 Ok(metadata)
1125 }
1126}
1127
1128pub 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 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 pub fn include_unknown(mut self, yes: bool) -> Self {
1157 self.include_unknown = yes;
1158 self
1159 }
1160
1161 pub fn include_duplicates(mut self, yes: bool) -> Self {
1163 self.include_duplicates = yes;
1164 self
1165 }
1166
1167 pub fn raw_values(mut self, yes: bool) -> Self {
1169 self.raw_values = yes;
1170 self
1171 }
1172
1173 pub fn group_by_category(mut self, yes: bool) -> Self {
1175 self.group_by_category = yes;
1176 self
1177 }
1178
1179 pub fn tag(mut self, tag: impl Into<String>) -> Self {
1181 self.specific_tags.push(tag.into());
1182 self
1183 }
1184
1185 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 pub fn coord_format(mut self, format: impl Into<String>) -> Self {
1195 self.args.push(format!("-c {}", format.into()));
1196 self
1197 }
1198
1199 pub fn date_format(mut self, format: impl Into<String>) -> Self {
1201 self.args.push(format!("-d {}", format.into()));
1202 self
1203 }
1204
1205 pub fn password(mut self, passwd: impl Into<String>) -> Self {
1207 self.args.push(format!("-password {}", passwd.into()));
1208 self
1209 }
1210
1211 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 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 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 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 pub fn print_format(mut self, format: impl Into<String>) -> Self {
1255 self.args.push(format!("-p {}", format.into()));
1256 self
1257 }
1258
1259 pub fn sort(mut self, yes: bool) -> Self {
1261 if yes {
1262 self.args.push("-sort".to_string());
1263 }
1264 self
1265 }
1266
1267 pub fn separator(mut self, sep: impl Into<String>) -> Self {
1269 self.args.push(format!("-sep {}", sep.into()));
1270 self
1271 }
1272
1273 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 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 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 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 }
1338}