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