Skip to main content

exiftool_rs_wrapper/
advanced.rs

1//! 高级写入功能模块
2//!
3//! 支持日期偏移、条件写入、批量写入等高级功能
4
5use crate::ExifTool;
6use crate::error::Result;
7use crate::types::TagId;
8use crate::write::WriteBuilder;
9use std::path::Path;
10
11/// 日期偏移方向
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DateShiftDirection {
14    /// 增加
15    Add,
16    /// 减少
17    Subtract,
18}
19
20/// 时间单位
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TimeUnit {
23    /// 年
24    Years,
25    /// 月
26    Months,
27    /// 日
28    Days,
29    /// 时
30    Hours,
31    /// 分
32    Minutes,
33    /// 秒
34    Seconds,
35}
36
37impl TimeUnit {
38    /// 获取单位缩写
39    #[allow(dead_code)]
40    fn abbreviation(&self) -> &'static str {
41        match self {
42            Self::Years => "y",
43            Self::Months => "m",
44            Self::Days => "d",
45            Self::Hours => "H",
46            Self::Minutes => "M",
47            Self::Seconds => "S",
48        }
49    }
50}
51
52/// 日期时间偏移量
53#[derive(Debug, Clone)]
54pub struct DateTimeOffset {
55    years: i32,
56    months: i32,
57    days: i32,
58    hours: i32,
59    minutes: i32,
60    seconds: i32,
61}
62
63impl DateTimeOffset {
64    /// 创建新的偏移量
65    pub fn new() -> Self {
66        Self {
67            years: 0,
68            months: 0,
69            days: 0,
70            hours: 0,
71            minutes: 0,
72            seconds: 0,
73        }
74    }
75
76    /// 设置年
77    pub fn years(mut self, value: i32) -> Self {
78        self.years = value;
79        self
80    }
81
82    /// 设置月
83    pub fn months(mut self, value: i32) -> Self {
84        self.months = value;
85        self
86    }
87
88    /// 设置日
89    pub fn days(mut self, value: i32) -> Self {
90        self.days = value;
91        self
92    }
93
94    /// 设置时
95    pub fn hours(mut self, value: i32) -> Self {
96        self.hours = value;
97        self
98    }
99
100    /// 设置分
101    pub fn minutes(mut self, value: i32) -> Self {
102        self.minutes = value;
103        self
104    }
105
106    /// 设置秒
107    pub fn seconds(mut self, value: i32) -> Self {
108        self.seconds = value;
109        self
110    }
111
112    /// 格式化为 ExifTool 格式
113    /// 格式: "+y:m:d H:M:S" 或 "-y:m:d H:M:S"
114    pub fn format(&self) -> String {
115        let sign = if self.is_negative() { "-" } else { "+" };
116        format!(
117            "{}{}:{:02}:{:02} {:02}:{:02}:{:02}",
118            sign,
119            self.years.abs(),
120            self.months.abs(),
121            self.days.abs(),
122            self.hours.abs(),
123            self.minutes.abs(),
124            self.seconds.abs()
125        )
126    }
127
128    fn is_negative(&self) -> bool {
129        self.years < 0
130            || self.months < 0
131            || self.days < 0
132            || self.hours < 0
133            || self.minutes < 0
134            || self.seconds < 0
135    }
136}
137
138impl Default for DateTimeOffset {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/// 高级写入操作 trait
145pub trait AdvancedWriteOperations {
146    /// 偏移日期时间标签
147    ///
148    /// # 示例
149    ///
150    /// ```rust,no_run
151    /// use exiftool_rs_wrapper::ExifTool;
152    /// use exiftool_rs_wrapper::advanced::AdvancedWriteOperations;
153    /// use exiftool_rs_wrapper::advanced::DateTimeOffset;
154    ///
155    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
156    /// let exiftool = ExifTool::new()?;
157    ///
158    /// // 将所有日期时间增加 1 天 2 小时
159    /// exiftool.shift_datetime("photo.jpg", DateTimeOffset::new().days(1).hours(2))?;
160    /// # Ok(())
161    /// # }
162    /// ```
163    fn shift_datetime<P: AsRef<Path>>(&self, path: P, offset: DateTimeOffset) -> Result<()>;
164
165    /// 仅偏移特定日期时间标签
166    fn shift_specific_datetime<P: AsRef<Path>>(
167        &self,
168        path: P,
169        tag: TagId,
170        offset: DateTimeOffset,
171    ) -> Result<()>;
172
173    /// 数值运算
174    fn numeric_operation<P: AsRef<Path>>(
175        &self,
176        path: P,
177        tag: TagId,
178        operation: NumericOperation,
179    ) -> Result<()>;
180
181    /// 字符串追加
182    fn append_string<P: AsRef<Path>>(&self, path: P, tag: TagId, suffix: &str) -> Result<()>;
183
184    /// 条件写入
185    fn write_if<P: AsRef<Path>, F>(&self, path: P, condition: &str, builder_fn: F) -> Result<()>
186    where
187        F: FnOnce(WriteBuilder<'_>) -> WriteBuilder<'_>;
188}
189
190/// 数值运算类型
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum NumericOperation {
193    /// 加法
194    Add(i64),
195    /// 减法
196    Subtract(i64),
197    /// 乘法
198    Multiply(i64),
199    /// 除法
200    Divide(i64),
201}
202
203impl NumericOperation {
204    fn operator(&self) -> &'static str {
205        match self {
206            Self::Add(_) => "+=",
207            Self::Subtract(_) => "-=",
208            Self::Multiply(_) => "*=",
209            Self::Divide(_) => "/=",
210        }
211    }
212
213    fn value(&self) -> i64 {
214        match self {
215            Self::Add(v) => *v,
216            Self::Subtract(v) => *v,
217            Self::Multiply(v) => *v,
218            Self::Divide(v) => *v,
219        }
220    }
221}
222
223impl AdvancedWriteOperations for ExifTool {
224    fn shift_datetime<P: AsRef<Path>>(&self, path: P, offset: DateTimeOffset) -> Result<()> {
225        let offset_str = offset.format();
226
227        self.write(path)
228            .arg(format!("-AllDates{}", offset_str))
229            .overwrite_original(true)
230            .execute()?;
231
232        Ok(())
233    }
234
235    fn shift_specific_datetime<P: AsRef<Path>>(
236        &self,
237        path: P,
238        tag: TagId,
239        offset: DateTimeOffset,
240    ) -> Result<()> {
241        let offset_str = offset.format();
242
243        self.write(path)
244            .arg(format!("-{}{}", tag.name(), offset_str))
245            .overwrite_original(true)
246            .execute()?;
247
248        Ok(())
249    }
250
251    fn numeric_operation<P: AsRef<Path>>(
252        &self,
253        path: P,
254        tag: TagId,
255        operation: NumericOperation,
256    ) -> Result<()> {
257        let op_str = format!(
258            "-{}{}{}",
259            tag.name(),
260            operation.operator(),
261            operation.value()
262        );
263
264        self.write(path)
265            .arg(op_str)
266            .overwrite_original(true)
267            .execute()?;
268
269        Ok(())
270    }
271
272    fn append_string<P: AsRef<Path>>(&self, path: P, tag: TagId, suffix: &str) -> Result<()> {
273        self.write(path)
274            .arg(format!("-{}+={}", tag.name(), suffix))
275            .overwrite_original(true)
276            .execute()?;
277
278        Ok(())
279    }
280
281    fn write_if<P: AsRef<Path>, F>(&self, path: P, _condition: &str, builder_fn: F) -> Result<()>
282    where
283        F: FnOnce(WriteBuilder<'_>) -> WriteBuilder<'_>,
284    {
285        // 使用 builder_fn 构建 WriteBuilder,它已经可以通过 .condition() 设置条件
286        let builder = self.write(path);
287        let builder = builder_fn(builder);
288        builder.execute().map(|_| ())
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_datetime_offset_format() {
298        let offset = DateTimeOffset::new().days(1).hours(2).minutes(30);
299
300        assert_eq!(offset.format(), "+0:00:01 02:30:00");
301    }
302
303    #[test]
304    fn test_datetime_offset_negative() {
305        let offset = DateTimeOffset::new().days(-1).hours(-2);
306
307        assert_eq!(offset.format(), "-0:00:01 02:00:00");
308    }
309
310    #[test]
311    fn test_numeric_operation() {
312        let op = NumericOperation::Add(5);
313        assert_eq!(op.operator(), "+=");
314        assert_eq!(op.value(), 5);
315
316        let op = NumericOperation::Multiply(2);
317        assert_eq!(op.operator(), "*=");
318        assert_eq!(op.value(), 2);
319    }
320}