1use std::str::FromStr;
2
3use crate::{RsRequest, url::RsLink};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use strum_macros::EnumString;
7
8use crate::domain::backup::BackupFile;
9
10pub const DEFAULT_MIME: &str = "application/octet-stream";
11
12#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
13#[serde(rename_all = "camelCase")]
14pub struct FileEpisode {
15 pub id: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub season: Option<u32>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub episode: Option<u32>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub episode_to: Option<u32>,
22}
23
24impl FromStr for FileEpisode {
25 type Err = String;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 let splitted: Vec<&str> = s.split("|").collect();
29 if splitted.len() == 3 {
30 Ok(FileEpisode {
31 id: splitted[0].to_string(),
32 season: splitted[1].parse::<u32>().ok().and_then(|i| {
33 if i == 0 {
34 None
35 } else {
36 Some(i)
37 }
38 }),
39 episode: splitted[2].parse::<u32>().ok().and_then(|i| {
40 if i == 0 {
41 None
42 } else {
43 Some(i)
44 }
45 }),
46 episode_to: None,
47 })
48 } else if splitted.len() == 2 {
49 Ok(FileEpisode {
50 id: splitted[0].to_string(),
51 season: splitted[1].parse::<u32>().ok().and_then(|i| {
52 if i == 0 {
53 None
54 } else {
55 Some(i)
56 }
57 }),
58 episode: None,
59 episode_to: None,
60 })
61 } else {
62 Ok(FileEpisode {
63 id: splitted[0].to_string(),
64 season: None,
65 episode: None,
66 episode_to: None,
67 })
68 }
69 }
70}
71
72#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
73pub struct MediaItemReference {
74 pub id: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub conf: Option<u16>,
77}
78
79impl FromStr for MediaItemReference {
80 type Err = String;
81
82 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 let splitted: Vec<&str> = s.split('|').collect();
84 if splitted.len() == 2 {
85 Ok(MediaItemReference {
86 id: splitted[0].to_string(),
87 conf: splitted[1].parse::<u16>().ok().and_then(|e| {
88 if e == 100 {
89 None
90 } else {
91 Some(e)
92 }
93 }),
94 })
95 } else {
96 Ok(MediaItemReference {
97 id: splitted[0].to_string(),
98 conf: None,
99 })
100 }
101 }
102}
103
104#[derive(
105 Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
106)]
107#[strum(serialize_all = "camelCase")]
108#[serde(rename_all = "camelCase")]
109pub enum FileType {
110 Directory,
111 Photo,
112 Video,
113 Archive,
114 Album,
115 Book,
116 #[default]
117 Other,
118}
119
120#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
121#[serde(rename_all = "camelCase")]
122pub struct Media {
123 pub id: String,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub source: Option<String>,
126 pub name: String,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub description: Option<String>,
129
130 #[serde(rename = "type")]
131 pub kind: FileType,
132 pub mimetype: String,
133 pub size: Option<u64>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub params: Option<Value>,
137
138 pub added: Option<i64>,
139 pub modified: Option<i64>,
140 pub created: Option<i64>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub rating: Option<f32>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub avg_rating: Option<f32>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub md5: Option<String>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub width: Option<usize>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub height: Option<usize>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub phash: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub thumbhash: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub focal: Option<u64>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub iso: Option<u64>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub color_space: Option<String>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub icc: Option<String>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub mp: Option<u32>,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub sspeed: Option<String>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub f_number: Option<f64>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub orientation: Option<usize>,
174
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub duration: Option<usize>,
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub acodecs: Option<Vec<String>>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub achan: Option<Vec<usize>>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub vcodecs: Option<Vec<String>>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub fps: Option<f64>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub bitrate: Option<u64>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub long: Option<f64>,
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub lat: Option<f64>,
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub model: Option<String>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub pages: Option<usize>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub progress: Option<usize>,
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub faces: Option<Vec<FaceEmbedding>>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub backups: Option<Vec<BackupFile>>,
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub thumb: Option<String>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub thumbv: Option<usize>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub thumbsize: Option<u64>,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub iv: Option<String>,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub origin: Option<RsLink>,
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub lang: Option<String>,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub uploader: Option<String>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub uploadkey: Option<String>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub original_hash: Option<String>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub original_id: Option<String>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub face_recognition_error: Option<String>,
228}
229
230impl Media {
231 pub fn max_date(&self) -> i64 {
232 *[
233 self.created.unwrap_or(0),
234 self.added.unwrap_or(0),
235 self.modified.unwrap_or(0),
236 ]
237 .iter()
238 .max()
239 .unwrap_or(&0)
240 }
241
242 pub fn bytes_size(&self) -> Option<u64> {
243 if self.iv.is_none() {
244 self.size
245 } else {
246 if let Some(file_size) = self.size {
254 Some(file_size + 16 + 4 + 4 + 32 + 256 + self.thumbsize.unwrap_or(0) + 0)
255 } else {
256 None
257 }
258 }
259 }
260}
261
262#[derive(Debug, Serialize, Deserialize, Clone, Default)]
263#[serde(rename_all = "camelCase")]
264pub struct RsGpsPosition {
265 pub lat: f64,
266 pub long: f64,
267}
268
269#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
270#[serde(rename_all = "camelCase")]
271pub struct FaceEmbedding {
272 pub id: String,
273 pub embedding: Vec<f32>,
274 pub media_ref: Option<String>,
275 pub bbox: Option<FaceBBox>,
276 pub confidence: Option<f32>,
277 pub pose: Option<(f32, f32, f32)>,
278 pub person_id: Option<String>,
279}
280
281#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
282#[serde(rename_all = "camelCase")]
283pub struct FaceBBox {
284 pub x1: f32,
285 pub y1: f32,
286 pub x2: f32,
287 pub y2: f32,
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub video_s: Option<f32>, #[serde(skip_serializing_if = "Option::is_none")]
291 pub video_percent: Option<u32>, }
293
294
295
296
297
298
299
300#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
301#[serde(rename_all = "camelCase")]
302pub struct MediaForUpdate {
303 pub name: Option<String>,
304 pub description: Option<String>,
305 pub mimetype: Option<String>,
306 pub kind: Option<FileType>,
307 pub size: Option<u64>,
308
309 pub md5: Option<String>,
310
311 pub modified: Option<i64>,
312 pub created: Option<i64>,
313
314 pub width: Option<u32>,
315 pub height: Option<u32>,
316 pub orientation: Option<u8>,
317 pub color_space: Option<String>,
318 pub icc: Option<String>,
319 pub mp: Option<u32>,
320 pub vcodecs: Option<Vec<String>>,
321 pub acodecs: Option<Vec<String>>,
322 pub fps: Option<f64>,
323 pub bitrate: Option<u64>,
324 pub focal: Option<u64>,
325 pub iso: Option<u64>,
326 pub model: Option<String>,
327 pub sspeed: Option<String>,
328 pub f_number: Option<f64>,
329
330 pub pages: Option<usize>,
331
332 pub duration: Option<u64>,
333
334 pub progress: Option<usize>,
335
336 pub add_tags: Option<Vec<MediaItemReference>>,
337 pub remove_tags: Option<Vec<String>>,
338 pub tags_lookup: Option<Vec<String>>,
339
340 pub add_series: Option<Vec<FileEpisode>>,
341 pub remove_series: Option<Vec<FileEpisode>>,
342 pub series_lookup: Option<Vec<String>>,
343 pub season: Option<u32>,
344 pub episode: Option<u32>,
345
346 pub add_people: Option<Vec<MediaItemReference>>,
347 pub remove_people: Option<Vec<String>>,
348 pub people_lookup: Option<Vec<String>>,
349
350 pub long: Option<f64>,
351 pub lat: Option<f64>,
352 pub gps: Option<String>,
353
354 pub origin: Option<RsLink>,
355 pub origin_url: Option<String>,
356 #[serde(default)]
357 pub ignore_origin_duplicate: bool,
358
359 pub movie: Option<String>,
360 pub book: Option<String>,
361
362 pub lang: Option<String>,
363
364 pub rating: Option<u16>,
365
366 pub thumbsize: Option<usize>,
367 pub iv: Option<String>,
368
369 pub uploader: Option<String>,
370 pub uploadkey: Option<String>,
371 pub upload_id: Option<String>,
372
373 pub original_hash: Option<String>,
374 pub original_id: Option<String>,
375}
376
377
378impl MediaForUpdate {
379 pub fn merge_from(&mut self, mut patch: Self) {
386 fn overwrite_if_some<T>(dst: &mut Option<T>, src: &mut Option<T>) {
388 if src.is_some() {
389 *dst = src.take();
390 }
391 }
392
393 fn append_vec<T>(dst: &mut Option<Vec<T>>, src: &mut Option<Vec<T>>) {
396 match (dst.as_mut(), src.take()) {
397 (Some(d), Some(mut s)) => d.append(&mut s),
398 (None, Some(s)) => *dst = Some(s),
399 _ => {}
400 }
401 }
402
403 overwrite_if_some(&mut self.name, &mut patch.name);
405 overwrite_if_some(&mut self.description, &mut patch.description);
406 overwrite_if_some(&mut self.mimetype, &mut patch.mimetype);
407 overwrite_if_some(&mut self.kind, &mut patch.kind);
408 overwrite_if_some(&mut self.size, &mut patch.size);
409
410 overwrite_if_some(&mut self.md5, &mut patch.md5);
411
412 overwrite_if_some(&mut self.modified, &mut patch.modified);
413 overwrite_if_some(&mut self.created, &mut patch.created);
414
415 overwrite_if_some(&mut self.width, &mut patch.width);
416 overwrite_if_some(&mut self.height, &mut patch.height);
417 overwrite_if_some(&mut self.orientation, &mut patch.orientation);
418 overwrite_if_some(&mut self.color_space, &mut patch.color_space);
419 overwrite_if_some(&mut self.icc, &mut patch.icc);
420 overwrite_if_some(&mut self.mp, &mut patch.mp);
421 overwrite_if_some(&mut self.vcodecs, &mut patch.vcodecs);
422 overwrite_if_some(&mut self.acodecs, &mut patch.acodecs);
423 overwrite_if_some(&mut self.fps, &mut patch.fps);
424 overwrite_if_some(&mut self.bitrate, &mut patch.bitrate);
425 overwrite_if_some(&mut self.focal, &mut patch.focal);
426 overwrite_if_some(&mut self.iso, &mut patch.iso);
427 overwrite_if_some(&mut self.model, &mut patch.model);
428 overwrite_if_some(&mut self.sspeed, &mut patch.sspeed);
429 overwrite_if_some(&mut self.f_number, &mut patch.f_number);
430
431 overwrite_if_some(&mut self.pages, &mut patch.pages);
432 overwrite_if_some(&mut self.duration, &mut patch.duration);
433 overwrite_if_some(&mut self.progress, &mut patch.progress);
434
435 overwrite_if_some(&mut self.season, &mut patch.season);
436 overwrite_if_some(&mut self.episode, &mut patch.episode);
437
438 overwrite_if_some(&mut self.long, &mut patch.long);
439 overwrite_if_some(&mut self.lat, &mut patch.lat);
440 overwrite_if_some(&mut self.gps, &mut patch.gps);
441
442 overwrite_if_some(&mut self.origin, &mut patch.origin);
443 overwrite_if_some(&mut self.origin_url, &mut patch.origin_url);
444
445 overwrite_if_some(&mut self.movie, &mut patch.movie);
446 overwrite_if_some(&mut self.book, &mut patch.book);
447
448 overwrite_if_some(&mut self.lang, &mut patch.lang);
449 overwrite_if_some(&mut self.rating, &mut patch.rating);
450
451 overwrite_if_some(&mut self.thumbsize, &mut patch.thumbsize);
452 overwrite_if_some(&mut self.iv, &mut patch.iv);
453
454 overwrite_if_some(&mut self.uploader, &mut patch.uploader);
455 overwrite_if_some(&mut self.uploadkey, &mut patch.uploadkey);
456 overwrite_if_some(&mut self.upload_id, &mut patch.upload_id);
457
458 overwrite_if_some(&mut self.original_hash, &mut patch.original_hash);
459 overwrite_if_some(&mut self.original_id, &mut patch.original_id);
460
461 append_vec(&mut self.add_tags, &mut patch.add_tags);
463 append_vec(&mut self.remove_tags, &mut patch.remove_tags);
464 append_vec(&mut self.tags_lookup, &mut patch.tags_lookup);
465
466 append_vec(&mut self.add_series, &mut patch.add_series);
467 append_vec(&mut self.remove_series, &mut patch.remove_series);
468 append_vec(&mut self.series_lookup, &mut patch.series_lookup);
469
470 append_vec(&mut self.add_people, &mut patch.add_people);
471 append_vec(&mut self.remove_people, &mut patch.remove_people);
472 append_vec(&mut self.people_lookup, &mut patch.people_lookup);
473
474 self.ignore_origin_duplicate |= patch.ignore_origin_duplicate;
476 }
477
478 pub fn merged(mut self, patch: Self) -> Self {
480 self.merge_from(patch);
481 self
482 }
483}
484
485
486impl From<Media> for MediaForUpdate {
487 fn from(value: Media) -> Self {
488 MediaForUpdate {
489 description: value.description,
490 add_people: None,
491 add_tags: None,
492 long: value.long,
493 lat: value.lat,
494 created: value.created,
495 origin: value.origin,
496 add_series: None,
497 pages: value.pages,
498 original_hash: value.original_hash.or(value.md5),
499 original_id: Some(value.original_id.unwrap_or(value.id)),
500 book: None,
501 ..Default::default()
502 }
503 }
504}
505
506impl From<RsRequest> for MediaForUpdate {
507 fn from(value: RsRequest) -> Self {
508 let (add_series, season, episode) = if let Some(albums) = &value.albums {
510 if let Some(serie_id) = albums.first() {
511 (
513 Some(vec![FileEpisode {
514 id: serie_id.clone(),
515 season: value.season,
516 episode: value.episode,
517 episode_to: None,
518 }]),
519 None,
520 None,
521 )
522 } else {
523 (None, value.season, value.episode)
524 }
525 } else {
526 (None, value.season, value.episode)
528 };
529
530 MediaForUpdate {
531 name: value.filename_or_extract_from_url(),
532 description: value.description,
533 ignore_origin_duplicate: value.ignore_origin_duplicate,
534 size: value.size,
535 people_lookup: value.people_lookup,
537 tags_lookup: value.tags_lookup,
538 series_lookup: value.albums_lookup,
539 add_series,
540 movie: value.movie,
541 season,
542 episode,
543 ..Default::default()
544 }
545 }
546}
547