exiftool_rs_wrapper/
query.rs1use std::path::{Path, PathBuf};
4
5use crate::ExifTool;
6use crate::error::{Error, Result};
7use crate::process::Response;
8use crate::types::{Metadata, TagId};
9
10pub struct QueryBuilder<'et> {
12 exiftool: &'et ExifTool,
13 path: PathBuf,
14 args: Vec<String>,
15 include_unknown: bool,
16 include_duplicates: bool,
17 raw_values: bool,
18 group_by_category: bool,
19 specific_tags: Vec<String>,
20 excluded_tags: Vec<String>,
21}
22
23impl<'et> QueryBuilder<'et> {
24 pub(crate) fn new<P: AsRef<Path>>(exiftool: &'et ExifTool, path: P) -> Self {
26 Self {
27 exiftool,
28 path: path.as_ref().to_path_buf(),
29 args: Vec::new(),
30 include_unknown: false,
31 include_duplicates: false,
32 raw_values: false,
33 group_by_category: false,
34 specific_tags: Vec::new(),
35 excluded_tags: Vec::new(),
36 }
37 }
38
39 pub fn include_unknown(mut self, yes: bool) -> Self {
41 self.include_unknown = yes;
42 self
43 }
44
45 pub fn include_duplicates(mut self, yes: bool) -> Self {
47 self.include_duplicates = yes;
48 self
49 }
50
51 pub fn raw_values(mut self, yes: bool) -> Self {
53 self.raw_values = yes;
54 self
55 }
56
57 pub fn group_by_category(mut self, yes: bool) -> Self {
59 self.group_by_category = yes;
60 self
61 }
62
63 pub fn tag(mut self, tag: impl Into<String>) -> Self {
65 self.specific_tags.push(tag.into());
66 self
67 }
68
69 pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
71 for tag in tags {
72 self.specific_tags.push(tag.as_ref().to_string());
73 }
74 self
75 }
76
77 pub fn tag_id(self, tag: TagId) -> Self {
79 self.tag(tag.name())
80 }
81
82 pub fn exclude(mut self, tag: impl Into<String>) -> Self {
84 self.excluded_tags.push(tag.into());
85 self
86 }
87
88 pub fn excludes(mut self, tags: &[impl AsRef<str>]) -> Self {
90 for tag in tags {
91 self.excluded_tags.push(tag.as_ref().to_string());
92 }
93 self
94 }
95
96 pub fn exclude_id(self, tag: TagId) -> Self {
98 self.exclude(tag.name())
99 }
100
101 pub fn charset(mut self, charset: impl Into<String>) -> Self {
103 self.args.push(format!("-charset {}", charset.into()));
104 self
105 }
106
107 pub fn lang(mut self, lang: impl Into<String>) -> Self {
109 self.args.push(format!("-lang {}", lang.into()));
110 self
111 }
112
113 pub fn arg(mut self, arg: impl Into<String>) -> Self {
115 self.args.push(arg.into());
116 self
117 }
118
119 pub fn execute(self) -> Result<Metadata> {
121 let args = self.build_args();
122
123 let response = self.exiftool.execute_raw(&args)?;
125
126 self.parse_response(response)
128 }
129
130 pub fn execute_json(self) -> Result<serde_json::Value> {
132 let args = self.build_args();
133 let response = self.exiftool.execute_raw(&args)?;
134 response.json()
135 }
136
137 pub fn execute_as<T: serde::de::DeserializeOwned>(self) -> Result<T> {
139 let args = self.build_args();
140 let response = self.exiftool.execute_raw(&args)?;
141 response.json()
142 }
143
144 fn build_args(&self) -> Vec<String> {
146 let mut args = vec!["-json".to_string()];
147
148 if self.group_by_category {
150 args.push("-g1".to_string());
151 }
152
153 if self.include_unknown {
155 args.push("-u".to_string());
156 }
157
158 if self.include_duplicates {
160 args.push("-a".to_string());
161 }
162
163 if self.raw_values {
165 args.push("-n".to_string());
166 }
167
168 args.extend(self.args.clone());
170
171 for tag in &self.specific_tags {
173 args.push(format!("-{}", tag));
174 }
175
176 for tag in &self.excluded_tags {
178 args.push(format!("-{}=", tag));
179 }
180
181 args.push(self.path.to_string_lossy().to_string());
183
184 args
185 }
186
187 fn parse_response(&self, response: Response) -> Result<Metadata> {
189 if response.is_error() {
190 return Err(Error::process(
191 response
192 .error_message()
193 .unwrap_or_else(|| "Unknown error".to_string()),
194 ));
195 }
196
197 let json_value: Vec<serde_json::Value> = response.json()?;
198
199 if json_value.is_empty() {
200 return Ok(Metadata::new());
201 }
202
203 let metadata: Metadata = serde_json::from_value(json_value[0].clone())?;
205
206 Ok(metadata)
207 }
208}
209
210pub struct BatchQueryBuilder<'et> {
212 exiftool: &'et ExifTool,
213 paths: Vec<PathBuf>,
214 args: Vec<String>,
215 include_unknown: bool,
216 include_duplicates: bool,
217 raw_values: bool,
218 group_by_category: bool,
219 specific_tags: Vec<String>,
220}
221
222impl<'et> BatchQueryBuilder<'et> {
223 pub(crate) fn new(exiftool: &'et ExifTool, paths: Vec<PathBuf>) -> Self {
225 Self {
226 exiftool,
227 paths,
228 args: Vec::new(),
229 include_unknown: false,
230 include_duplicates: false,
231 raw_values: false,
232 group_by_category: false,
233 specific_tags: Vec::new(),
234 }
235 }
236
237 pub fn include_unknown(mut self, yes: bool) -> Self {
239 self.include_unknown = yes;
240 self
241 }
242
243 pub fn include_duplicates(mut self, yes: bool) -> Self {
245 self.include_duplicates = yes;
246 self
247 }
248
249 pub fn raw_values(mut self, yes: bool) -> Self {
251 self.raw_values = yes;
252 self
253 }
254
255 pub fn group_by_category(mut self, yes: bool) -> Self {
257 self.group_by_category = yes;
258 self
259 }
260
261 pub fn tag(mut self, tag: impl Into<String>) -> Self {
263 self.specific_tags.push(tag.into());
264 self
265 }
266
267 pub fn tags(mut self, tags: &[impl AsRef<str>]) -> Self {
269 for tag in tags {
270 self.specific_tags.push(tag.as_ref().to_string());
271 }
272 self
273 }
274
275 pub fn execute(self) -> Result<Vec<(PathBuf, Metadata)>> {
277 if self.paths.is_empty() {
278 return Ok(Vec::new());
279 }
280
281 let args = self.build_args();
282 let response = self.exiftool.execute_raw(&args)?;
283
284 let json_values: Vec<serde_json::Value> = response.json()?;
286
287 let mut results = Vec::with_capacity(json_values.len());
288
289 for (i, json) in json_values.into_iter().enumerate() {
290 let path = self.paths.get(i).cloned().unwrap_or_default();
291 let metadata: Metadata = serde_json::from_value(json)?;
292 results.push((path, metadata));
293 }
294
295 Ok(results)
296 }
297
298 fn build_args(&self) -> Vec<String> {
300 let mut args = vec!["-json".to_string()];
301
302 if self.group_by_category {
303 args.push("-g1".to_string());
304 }
305
306 if self.include_unknown {
307 args.push("-u".to_string());
308 }
309
310 if self.include_duplicates {
311 args.push("-a".to_string());
312 }
313
314 if self.raw_values {
315 args.push("-n".to_string());
316 }
317
318 args.extend(self.args.clone());
319
320 for tag in &self.specific_tags {
321 args.push(format!("-{}", tag));
322 }
323
324 for path in &self.paths {
326 args.push(path.to_string_lossy().to_string());
327 }
328
329 args
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 #[test]
336 fn test_query_builder_args() {
337 }
340}