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    #[default]
118    Other,
119}
120
121#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
122#[serde(rename_all = "camelCase")]
123pub struct Media {
124    pub id: String,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub source: Option<String>,
127    pub name: String,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub description: Option<String>,
130
131    #[serde(rename = "type")]
132    pub kind: FileType,
133    pub mimetype: String,
134    pub size: Option<u64>,
135
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub params: Option<Value>,
138
139    pub added: Option<i64>,
140    pub modified: Option<i64>,
141    pub created: Option<i64>,
142
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub rating: Option<f32>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub avg_rating: Option<f32>,
147
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub md5: Option<String>,
150
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub width: Option<usize>,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub height: Option<usize>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub phash: Option<String>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub thumbhash: Option<String>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub focal: Option<u64>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub iso: Option<u64>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub color_space: Option<String>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub icc: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub mp: Option<u32>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub sspeed: Option<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub f_number: Option<f64>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub orientation: Option<usize>,
175
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub duration: Option<usize>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub acodecs: Option<Vec<String>>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub achan: Option<Vec<usize>>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub vcodecs: Option<Vec<String>>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub fps: Option<f64>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub bitrate: Option<u64>,
188
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub long: Option<f64>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub lat: Option<f64>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub model: Option<String>,
195
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub pages: Option<usize>,
198
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub progress: Option<usize>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub tags: Option<Vec<MediaItemReference>>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub series: Option<Vec<FileEpisode>>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub people: Option<Vec<MediaItemReference>>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub faces: Option<Vec<FaceEmbedding>>,
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub backups: Option<Vec<BackupFile>>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub thumb: Option<String>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub thumbv: Option<usize>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub thumbsize: Option<u64>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub iv: Option<String>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub origin: Option<RsLink>,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub movie: Option<String>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub book: Option<String>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub lang: Option<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub uploader: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub uploadkey: Option<String>,
231
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub original_hash: Option<String>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub original_id: Option<String>,
236
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub face_recognition_error: Option<String>,
239}
240
241impl Media {
242    pub fn max_date(&self) -> i64 {
243        *[
244            self.created.unwrap_or(0),
245            self.added.unwrap_or(0),
246            self.modified.unwrap_or(0),
247        ]
248        .iter()
249        .max()
250        .unwrap_or(&0)
251    }
252
253    pub fn bytes_size(&self) -> Option<u64> {
254        if self.iv.is_none() {
255            self.size
256        } else {
257            //16 Bytes to store IV
258            //4 to store encrypted thumb size = T (can be 0)
259            //4 to store encrypted Info size = I (can be 0)
260            //32 to store thumb mimetype
261            //256 to store file mimetype
262            //T Bytes for the encrypted thumb
263            //I Bytes for the encrypted info
264            if let Some(file_size) = self.size {
265                Some(file_size + 16 + 4 + 4 + 32 + 256 + self.thumbsize.unwrap_or(0) + 0)
266            } else {
267                None
268            }
269        }
270    }
271}
272
273#[derive(Debug, Serialize, Deserialize, Clone, Default)]
274#[serde(rename_all = "camelCase")]
275pub struct RsGpsPosition {
276    pub lat: f64,
277    pub long: f64,
278}
279
280
281#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
282#[serde(rename_all = "camelCase")]
283pub struct FaceEmbedding {
284    pub id: String,
285    pub embedding: Vec<f32>,
286    pub media_ref: Option<String>,
287    pub bbox: Option<FaceBBox>,
288    pub confidence: Option<f32>,
289    pub pose: Option<(f32, f32, f32)>,
290    pub person_id: Option<String>,
291}
292
293#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
294#[serde(rename_all = "camelCase")]
295pub struct FaceBBox {
296    pub x1: f32,
297    pub y1: f32,
298    pub x2: f32,
299    pub y2: f32,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub video_s: Option<f32>, // Seconds in video where face was detected
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub video_percent: Option<u32>, // Percent (0-100) of video where face was detected
304}