Skip to main content

rs_plugin_common_interfaces/domain/
media.rs

1use std::str::FromStr;
2
3use crate::url::RsLink;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use strum_macros::EnumString;
7
8use crate::domain::backup::BackupFile;
9
10
11
12pub const DEFAULT_MIME: &str = "application/octet-stream";
13
14#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
15#[serde(rename_all = "camelCase")]
16pub struct FileEpisode {
17    pub id: String,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub season: Option<u32>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub episode: Option<u32>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub episode_to: Option<u32>,
24}
25
26impl FromStr for FileEpisode {
27    type Err = String;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        let splitted: Vec<&str> = s.split("|").collect();
31        if splitted.len() == 3 {
32            Ok(FileEpisode {
33                id: splitted[0].to_string(),
34                season: splitted[1].parse::<u32>().ok().and_then(|i| {
35                    if i == 0 {
36                        None
37                    } else {
38                        Some(i)
39                    }
40                }),
41                episode: splitted[2].parse::<u32>().ok().and_then(|i| {
42                    if i == 0 {
43                        None
44                    } else {
45                        Some(i)
46                    }
47                }),
48                episode_to: None,
49            })
50        } else if splitted.len() == 2 {
51            Ok(FileEpisode {
52                id: splitted[0].to_string(),
53                season: splitted[1].parse::<u32>().ok().and_then(|i| {
54                    if i == 0 {
55                        None
56                    } else {
57                        Some(i)
58                    }
59                }),
60                episode: None,
61                episode_to: None,
62            })
63        } else {
64            Ok(FileEpisode {
65                id: splitted[0].to_string(),
66                season: None,
67                episode: None,
68                episode_to: None,
69            })
70        }
71    }
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
75pub struct MediaItemReference {
76    pub id: String,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub conf: Option<u16>,
79}
80
81impl FromStr for MediaItemReference {
82    type Err = String;
83
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        let splitted: Vec<&str> = s.split('|').collect();
86        if splitted.len() == 2 {
87            Ok(MediaItemReference {
88                id: splitted[0].to_string(),
89                conf: splitted[1].parse::<u16>().ok().and_then(|e| {
90                    if e == 100 {
91                        None
92                    } else {
93                        Some(e)
94                    }
95                }),
96            })
97        } else {
98            Ok(MediaItemReference {
99                id: splitted[0].to_string(),
100                conf: None,
101            })
102        }
103    }
104}
105
106#[derive(
107    Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
108)]
109#[strum(serialize_all = "camelCase")]
110#[serde(rename_all = "camelCase")]
111pub enum FileType {
112    Directory,
113    Photo,
114    Video,
115    Archive,
116    Album,
117    Book,
118    #[default]
119    Other,
120}
121
122#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
123#[serde(rename_all = "camelCase")]
124pub struct Media {
125    pub id: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub source: Option<String>,
128    pub name: String,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub description: Option<String>,
131
132    #[serde(rename = "type")]
133    pub kind: FileType,
134    pub mimetype: String,
135    pub size: Option<u64>,
136
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub params: Option<Value>,
139
140    pub added: Option<i64>,
141    pub modified: Option<i64>,
142    pub created: Option<i64>,
143
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub rating: Option<f32>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub avg_rating: Option<f32>,
148
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub md5: Option<String>,
151
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub width: Option<usize>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub height: Option<usize>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub phash: Option<String>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub thumbhash: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub focal: Option<u64>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub iso: Option<u64>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub color_space: Option<String>,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub icc: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub mp: Option<u32>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub sspeed: Option<String>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub f_number: Option<f64>,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub orientation: Option<usize>,
176
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub duration: Option<usize>,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub acodecs: Option<Vec<String>>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub achan: Option<Vec<usize>>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub vcodecs: Option<Vec<String>>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub fps: Option<f64>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub bitrate: Option<u64>,
189
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub long: Option<f64>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub lat: Option<f64>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub model: Option<String>,
196
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub pages: Option<usize>,
199
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub progress: Option<usize>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub tags: Option<Vec<MediaItemReference>>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub series: Option<Vec<FileEpisode>>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub people: Option<Vec<MediaItemReference>>,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub faces: Option<Vec<FaceEmbedding>>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub backups: Option<Vec<BackupFile>>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub thumb: Option<String>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub thumbv: Option<usize>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub thumbsize: Option<u64>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub iv: Option<String>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub origin: Option<RsLink>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub movie: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub book: Option<String>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub lang: Option<String>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub uploader: Option<String>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub uploadkey: Option<String>,
232
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub original_hash: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub original_id: Option<String>,
237
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub face_recognition_error: Option<String>,
240}
241
242impl Media {
243    pub fn max_date(&self) -> i64 {
244        *[
245            self.created.unwrap_or(0),
246            self.added.unwrap_or(0),
247            self.modified.unwrap_or(0),
248        ]
249        .iter()
250        .max()
251        .unwrap_or(&0)
252    }
253
254    pub fn bytes_size(&self) -> Option<u64> {
255        if self.iv.is_none() {
256            self.size
257        } else {
258            //16 Bytes to store IV
259            //4 to store encrypted thumb size = T (can be 0)
260            //4 to store encrypted Info size = I (can be 0)
261            //32 to store thumb mimetype
262            //256 to store file mimetype
263            //T Bytes for the encrypted thumb
264            //I Bytes for the encrypted info
265            if let Some(file_size) = self.size {
266                Some(file_size + 16 + 4 + 4 + 32 + 256 + self.thumbsize.unwrap_or(0) + 0)
267            } else {
268                None
269            }
270        }
271    }
272}
273
274#[derive(Debug, Serialize, Deserialize, Clone, Default)]
275#[serde(rename_all = "camelCase")]
276pub struct RsGpsPosition {
277    pub lat: f64,
278    pub long: f64,
279}
280
281
282#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
283#[serde(rename_all = "camelCase")]
284pub struct FaceEmbedding {
285    pub id: String,
286    pub embedding: Vec<f32>,
287    pub media_ref: Option<String>,
288    pub bbox: Option<FaceBBox>,
289    pub confidence: Option<f32>,
290    pub pose: Option<(f32, f32, f32)>,
291    pub person_id: Option<String>,
292}
293
294#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
295#[serde(rename_all = "camelCase")]
296pub struct FaceBBox {
297    pub x1: f32,
298    pub y1: f32,
299    pub x2: f32,
300    pub y2: f32,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub video_s: Option<f32>, // Seconds in video where face was detected
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub video_percent: Option<u32>, // Percent (0-100) of video where face was detected
305}