1use crate::ExifTool;
6use crate::error::Result;
7use crate::types::TagId;
8use crate::write::WriteBuilder;
9use std::path::Path;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DateShiftDirection {
14 Add,
16 Subtract,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum TimeUnit {
23 Years,
25 Months,
27 Days,
29 Hours,
31 Minutes,
33 Seconds,
35}
36
37impl TimeUnit {
38 }
41
42#[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 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 pub fn years(mut self, value: i32) -> Self {
68 self.years = value;
69 self
70 }
71
72 pub fn months(mut self, value: i32) -> Self {
74 self.months = value;
75 self
76 }
77
78 pub fn days(mut self, value: i32) -> Self {
80 self.days = value;
81 self
82 }
83
84 pub fn hours(mut self, value: i32) -> Self {
86 self.hours = value;
87 self
88 }
89
90 pub fn minutes(mut self, value: i32) -> Self {
92 self.minutes = value;
93 self
94 }
95
96 pub fn seconds(mut self, value: i32) -> Self {
98 self.seconds = value;
99 self
100 }
101
102 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
134pub trait AdvancedWriteOperations {
136 fn shift_datetime<P: AsRef<Path>>(&self, path: P, offset: DateTimeOffset) -> Result<()>;
154
155 fn shift_specific_datetime<P: AsRef<Path>>(
157 &self,
158 path: P,
159 tag: TagId,
160 offset: DateTimeOffset,
161 ) -> Result<()>;
162
163 fn numeric_operation<P: AsRef<Path>>(
165 &self,
166 path: P,
167 tag: TagId,
168 operation: NumericOperation,
169 ) -> Result<()>;
170
171 fn append_string<P: AsRef<Path>>(&self, path: P, tag: TagId, suffix: &str) -> Result<()>;
173
174 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum NumericOperation {
183 Add(i64),
185 Subtract(i64),
187 Multiply(i64),
189 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 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}