Skip to main content

exiftool_rs_wrapper/
binary.rs

1//! 二进制数据处理模块
2
3use crate::ExifTool;
4use crate::error::{Error, Result};
5use std::path::{Path, PathBuf};
6
7/// 二进制数据写入构建器
8pub 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    /// 创建新的二进制写入构建器
18    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    /// 设置缩略图
29    pub fn thumbnail(mut self, data: Vec<u8>) -> Self {
30        self.binary_tags.push((BinaryTag::Thumbnail, data));
31        self
32    }
33
34    /// 设置预览图
35    pub fn preview(mut self, data: Vec<u8>) -> Self {
36        self.binary_tags.push((BinaryTag::Preview, data));
37        self
38    }
39
40    /// 设置 JPEG 预览
41    pub fn jpeg_preview(mut self, data: Vec<u8>) -> Self {
42        self.binary_tags.push((BinaryTag::JpegPreview, data));
43        self
44    }
45
46    /// 覆盖原始文件
47    pub fn overwrite_original(mut self, yes: bool) -> Self {
48        self.overwrite_original = yes;
49        self
50    }
51
52    /// 创建备份
53    pub fn backup(mut self, yes: bool) -> Self {
54        self.backup = yes;
55        self
56    }
57
58    /// 执行写入
59    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        // 基础选项
67        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        // 为每个二进制标签创建临时文件
76        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        // 添加目标文件
86        args.push(self.path.to_string_lossy().to_string());
87
88        // 执行命令
89        let response = self.exiftool.execute_raw(&args)?;
90
91        // 临时文件会在 temp_files 被 drop 时自动删除
92        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/// 二进制写入结果
110#[derive(Debug, Clone)]
111pub struct BinaryWriteResult {
112    /// 被修改的文件路径
113    pub path: PathBuf,
114
115    /// 写入的二进制标签
116    pub written_tags: Vec<BinaryTag>,
117}
118
119/// 二进制标签类型
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum BinaryTag {
122    /// 缩略图
123    Thumbnail,
124
125    /// 预览图
126    Preview,
127
128    /// JPEG 预览
129    JpegPreview,
130
131    /// 其他自定义标签
132    Other(&'static str),
133}
134
135impl BinaryTag {
136    /// 获取标签名称
137    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
158/// 扩展 ExifTool 以支持二进制操作
159pub trait BinaryOperations {
160    /// 读取二进制数据
161    fn read_binary<P: AsRef<Path>>(&self, path: P, tag: BinaryTag) -> Result<Vec<u8>>;
162
163    /// 写入二进制数据
164    fn write_binary<P: AsRef<Path>>(&self, path: P) -> BinaryWriteBuilder<'_>;
165
166    /// 提取缩略图到文件
167    fn extract_thumbnail<P: AsRef<Path>, Q: AsRef<Path>>(&self, source: P, dest: Q) -> Result<()>;
168
169    /// 提取预览图到文件
170    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        // 二进制数据在响应中直接返回
184        let data = response.text().trim().as_bytes().to_vec();
185
186        // 如果响应看起来是 Base64 编码的,尝试解码
187        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
231/// Base64 解码(简单实现)
232fn 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        // 测试简单 Base64 解码
256        let encoded = "SGVsbG8gV29ybGQh";
257        let decoded = base64_decode(encoded).unwrap();
258        assert_eq!(decoded, b"Hello World!");
259    }
260}