exiftool_rs_wrapper/
binary.rs1use crate::ExifTool;
4use crate::error::{Error, Result};
5use std::path::{Path, PathBuf};
6
7pub struct BinaryWriteBuilder<'et> {
9 exiftool: &'et ExifTool,
10 path: PathBuf,
11 binary_tags: Vec<(BinaryTag, Vec<u8>)>,
12 overwrite_original: bool,
13 backup: bool,
14}
15
16impl<'et> BinaryWriteBuilder<'et> {
17 pub(crate) fn new<P: AsRef<Path>>(exiftool: &'et ExifTool, path: P) -> Self {
19 Self {
20 exiftool,
21 path: path.as_ref().to_path_buf(),
22 binary_tags: Vec::new(),
23 overwrite_original: false,
24 backup: true,
25 }
26 }
27
28 pub fn thumbnail(mut self, data: Vec<u8>) -> Self {
30 self.binary_tags.push((BinaryTag::Thumbnail, data));
31 self
32 }
33
34 pub fn preview(mut self, data: Vec<u8>) -> Self {
36 self.binary_tags.push((BinaryTag::Preview, data));
37 self
38 }
39
40 pub fn jpeg_preview(mut self, data: Vec<u8>) -> Self {
42 self.binary_tags.push((BinaryTag::JpegPreview, data));
43 self
44 }
45
46 pub fn overwrite_original(mut self, yes: bool) -> Self {
48 self.overwrite_original = yes;
49 self
50 }
51
52 pub fn backup(mut self, yes: bool) -> Self {
54 self.backup = yes;
55 self
56 }
57
58 pub fn execute(self) -> Result<BinaryWriteResult> {
60 use std::io::Write;
61 use tempfile::NamedTempFile;
62
63 let mut temp_files = Vec::with_capacity(self.binary_tags.len());
64 let mut args = Vec::new();
65
66 if self.overwrite_original {
68 args.push("-overwrite_original".to_string());
69 }
70
71 if !self.backup {
72 args.push("-overwrite_original_in_place".to_string());
73 }
74
75 for (tag, data) in &self.binary_tags {
77 let mut temp_file = NamedTempFile::new()?;
78 temp_file.write_all(data)?;
79 let temp_path = temp_file.path().to_path_buf();
80 temp_files.push(temp_file);
81
82 args.push(format!("-{}={}", tag.tag_name(), temp_path.display()));
83 }
84
85 args.push(self.path.to_string_lossy().to_string());
87
88 let response = self.exiftool.execute_raw(&args)?;
90
91 drop(temp_files);
93
94 if response.is_error() {
95 return Err(Error::process(
96 response
97 .error_message()
98 .unwrap_or_else(|| "Unknown binary write error".to_string()),
99 ));
100 }
101
102 Ok(BinaryWriteResult {
103 path: self.path,
104 written_tags: self.binary_tags.into_iter().map(|(tag, _)| tag).collect(),
105 })
106 }
107}
108
109#[derive(Debug, Clone)]
111pub struct BinaryWriteResult {
112 pub path: PathBuf,
114
115 pub written_tags: Vec<BinaryTag>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum BinaryTag {
122 Thumbnail,
124
125 Preview,
127
128 JpegPreview,
130
131 Other(&'static str),
133}
134
135impl BinaryTag {
136 pub fn tag_name(&self) -> &str {
138 match self {
139 Self::Thumbnail => "ThumbnailImage",
140 Self::Preview => "PreviewImage",
141 Self::JpegPreview => "JpgFromRaw",
142 Self::Other(name) => name,
143 }
144 }
145}
146
147impl From<&'static str> for BinaryTag {
148 fn from(name: &'static str) -> Self {
149 match name {
150 "ThumbnailImage" => Self::Thumbnail,
151 "PreviewImage" => Self::Preview,
152 "JpgFromRaw" => Self::JpegPreview,
153 _ => Self::Other(name),
154 }
155 }
156}
157
158pub trait BinaryOperations {
160 fn read_binary<P: AsRef<Path>>(&self, path: P, tag: BinaryTag) -> Result<Vec<u8>>;
162
163 fn write_binary<P: AsRef<Path>>(&self, path: P) -> BinaryWriteBuilder<'_>;
165
166 fn extract_thumbnail<P: AsRef<Path>, Q: AsRef<Path>>(&self, source: P, dest: Q) -> Result<()>;
168
169 fn extract_preview<P: AsRef<Path>, Q: AsRef<Path>>(&self, source: P, dest: Q) -> Result<()>;
171}
172
173impl BinaryOperations for ExifTool {
174 fn read_binary<P: AsRef<Path>>(&self, path: P, tag: BinaryTag) -> Result<Vec<u8>> {
175 let args = vec![
176 "-b".to_string(),
177 format!("-{}", tag.tag_name()),
178 path.as_ref().to_string_lossy().to_string(),
179 ];
180
181 let response = self.execute_raw(&args)?;
182
183 let data = response.text().trim().as_bytes().to_vec();
185
186 if let Ok(decoded) = base64_decode(response.text().trim()) {
188 Ok(decoded)
189 } else {
190 Ok(data)
191 }
192 }
193
194 fn write_binary<P: AsRef<Path>>(&self, path: P) -> BinaryWriteBuilder<'_> {
195 BinaryWriteBuilder::new(self, path)
196 }
197
198 fn extract_thumbnail<P: AsRef<Path>, Q: AsRef<Path>>(&self, source: P, dest: Q) -> Result<()> {
199 use std::fs::File;
200 use std::io::Write;
201
202 let data = self.read_binary(source, BinaryTag::Thumbnail)?;
203
204 if data.is_empty() {
205 return Err(Error::TagNotFound("ThumbnailImage".to_string()));
206 }
207
208 let mut file = File::create(dest)?;
209 file.write_all(&data)?;
210
211 Ok(())
212 }
213
214 fn extract_preview<P: AsRef<Path>, Q: AsRef<Path>>(&self, source: P, dest: Q) -> Result<()> {
215 use std::fs::File;
216 use std::io::Write;
217
218 let data = self.read_binary(source, BinaryTag::Preview)?;
219
220 if data.is_empty() {
221 return Err(Error::TagNotFound("PreviewImage".to_string()));
222 }
223
224 let mut file = File::create(dest)?;
225 file.write_all(&data)?;
226
227 Ok(())
228 }
229}
230
231fn base64_decode(input: &str) -> Result<Vec<u8>> {
233 use base64::{Engine, engine::general_purpose::STANDARD};
234
235 STANDARD
236 .decode(input)
237 .map_err(|e| Error::parse(format!("Base64 decode error: {}", e)))
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_binary_tag() {
246 assert_eq!(BinaryTag::Thumbnail.tag_name(), "ThumbnailImage");
247 assert_eq!(BinaryTag::Preview.tag_name(), "PreviewImage");
248
249 let tag: BinaryTag = "ThumbnailImage".into();
250 assert_eq!(tag, BinaryTag::Thumbnail);
251 }
252
253 #[test]
254 fn test_base64_decode() {
255 let encoded = "SGVsbG8gV29ybGQh";
257 let decoded = base64_decode(encoded).unwrap();
258 assert_eq!(decoded, b"Hello World!");
259 }
260}