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
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 tags: Option<Vec<MediaItemReference>>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub series: Option<Vec<FileEpisode>>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub people: Option<Vec<MediaItemReference>>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub faces: Option<Vec<FaceEmbedding>>,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub backups: Option<Vec<BackupFile>>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub thumb: Option<String>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub thumbv: Option<usize>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub thumbsize: Option<u64>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub iv: Option<String>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub origin: Option<RsLink>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub movie: Option<String>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub book: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub lang: Option<String>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub uploader: Option<String>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub uploadkey: Option<String>,
230
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub original_hash: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub original_id: Option<String>,
235
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub face_recognition_error: Option<String>,
238}
239
240impl Media {
241    pub fn max_date(&self) -> i64 {
242        *[
243            self.created.unwrap_or(0),
244            self.added.unwrap_or(0),
245            self.modified.unwrap_or(0),
246        ]
247        .iter()
248        .max()
249        .unwrap_or(&0)
250    }
251
252    pub fn bytes_size(&self) -> Option<u64> {
253        if self.iv.is_none() {
254            self.size
255        } else {
256            //16 Bytes to store IV
257            //4 to store encrypted thumb size = T (can be 0)
258            //4 to store encrypted Info size = I (can be 0)
259            //32 to store thumb mimetype
260            //256 to store file mimetype
261            //T Bytes for the encrypted thumb
262            //I Bytes for the encrypted info
263            if let Some(file_size) = self.size {
264                Some(file_size + 16 + 4 + 4 + 32 + 256 + self.thumbsize.unwrap_or(0) + 0)
265            } else {
266                None
267            }
268        }
269    }
270}
271
272#[derive(Debug, Serialize, Deserialize, Clone, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct RsGpsPosition {
275    pub lat: f64,
276    pub long: f64,
277}
278
279#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
280#[serde(rename_all = "camelCase")]
281pub struct FaceEmbedding {
282    pub id: String,
283    pub embedding: Vec<f32>,
284    pub media_ref: Option<String>,
285    pub bbox: Option<FaceBBox>,
286    pub confidence: Option<f32>,
287    pub pose: Option<(f32, f32, f32)>,
288    pub person_id: Option<String>,
289}
290
291#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub struct FaceBBox {
294    pub x1: f32,
295    pub y1: f32,
296    pub x2: f32,
297    pub y2: f32,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub video_s: Option<f32>, // Seconds in video where face was detected
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub video_percent: Option<u32>, // Percent (0-100) of video where face was detected
302}