use std::str::FromStr;
use crate::{RsRequest, url::RsLink};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use strum_macros::EnumString;
use crate::domain::backup::BackupFile;
pub const DEFAULT_MIME: &str = "application/octet-stream";
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FileEpisode {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub season: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub episode: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub episode_to: Option<u32>,
}
impl FromStr for FileEpisode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let splitted: Vec<&str> = s.split("|").collect();
if splitted.len() == 3 {
Ok(FileEpisode {
id: splitted[0].to_string(),
season: splitted[1].parse::<u32>().ok().and_then(|i| {
if i == 0 {
None
} else {
Some(i)
}
}),
episode: splitted[2].parse::<u32>().ok().and_then(|i| {
if i == 0 {
None
} else {
Some(i)
}
}),
episode_to: None,
})
} else if splitted.len() == 2 {
Ok(FileEpisode {
id: splitted[0].to_string(),
season: splitted[1].parse::<u32>().ok().and_then(|i| {
if i == 0 {
None
} else {
Some(i)
}
}),
episode: None,
episode_to: None,
})
} else {
Ok(FileEpisode {
id: splitted[0].to_string(),
season: None,
episode: None,
episode_to: None,
})
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MediaItemReference {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub conf: Option<u16>,
}
impl FromStr for MediaItemReference {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let splitted: Vec<&str> = s.split('|').collect();
if splitted.len() == 2 {
Ok(MediaItemReference {
id: splitted[0].to_string(),
conf: splitted[1].parse::<u16>().ok().and_then(|e| {
if e == 100 {
None
} else {
Some(e)
}
}),
})
} else {
Ok(MediaItemReference {
id: splitted[0].to_string(),
conf: None,
})
}
}
}
#[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, strum_macros::Display, EnumString, Default,
)]
#[strum(serialize_all = "camelCase")]
#[serde(rename_all = "camelCase")]
pub enum FileType {
Directory,
Photo,
Video,
Archive,
Album,
Book,
#[default]
Other,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Media {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "type")]
pub kind: FileType,
pub mimetype: String,
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
pub added: Option<i64>,
pub modified: Option<i64>,
pub created: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rating: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub avg_rating: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub md5: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbhash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub focal: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iso: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color_space: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mp: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sspeed: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub f_number: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub orientation: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub acodecs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub achan: Option<Vec<usize>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vcodecs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fps: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bitrate: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub long: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lat: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pages: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub progress: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub faces: Option<Vec<FaceEmbedding>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub backups: Option<Vec<BackupFile>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumb: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbv: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thumbsize: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iv: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<RsLink>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uploader: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uploadkey: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub original_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub original_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub face_recognition_error: Option<String>,
}
impl Media {
pub fn max_date(&self) -> i64 {
*[
self.created.unwrap_or(0),
self.added.unwrap_or(0),
self.modified.unwrap_or(0),
]
.iter()
.max()
.unwrap_or(&0)
}
pub fn bytes_size(&self) -> Option<u64> {
if self.iv.is_none() {
self.size
} else {
if let Some(file_size) = self.size {
Some(file_size + 16 + 4 + 4 + 32 + 256 + self.thumbsize.unwrap_or(0) + 0)
} else {
None
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct RsGpsPosition {
pub lat: f64,
pub long: f64,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FaceEmbedding {
pub id: String,
pub embedding: Vec<f32>,
pub media_ref: Option<String>,
pub bbox: Option<FaceBBox>,
pub confidence: Option<f32>,
pub pose: Option<(f32, f32, f32)>,
pub person_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FaceBBox {
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
#[serde(skip_serializing_if = "Option::is_none")]
pub video_s: Option<f32>, #[serde(skip_serializing_if = "Option::is_none")]
pub video_percent: Option<u32>, }
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MediaForUpdate {
pub name: Option<String>,
pub description: Option<String>,
pub mimetype: Option<String>,
pub kind: Option<FileType>,
pub size: Option<u64>,
pub md5: Option<String>,
pub modified: Option<i64>,
pub created: Option<i64>,
pub width: Option<u32>,
pub height: Option<u32>,
pub orientation: Option<u8>,
pub color_space: Option<String>,
pub icc: Option<String>,
pub mp: Option<u32>,
pub vcodecs: Option<Vec<String>>,
pub acodecs: Option<Vec<String>>,
pub fps: Option<f64>,
pub bitrate: Option<u64>,
pub focal: Option<u64>,
pub iso: Option<u64>,
pub model: Option<String>,
pub sspeed: Option<String>,
pub f_number: Option<f64>,
pub pages: Option<usize>,
pub duration: Option<u64>,
pub progress: Option<usize>,
pub add_tags: Option<Vec<MediaItemReference>>,
pub remove_tags: Option<Vec<String>>,
pub tags_lookup: Option<Vec<String>>,
pub add_series: Option<Vec<FileEpisode>>,
pub remove_series: Option<Vec<FileEpisode>>,
pub series_lookup: Option<Vec<String>>,
pub season: Option<u32>,
pub episode: Option<u32>,
pub add_people: Option<Vec<MediaItemReference>>,
pub remove_people: Option<Vec<String>>,
pub people_lookup: Option<Vec<String>>,
pub long: Option<f64>,
pub lat: Option<f64>,
pub gps: Option<String>,
pub origin: Option<RsLink>,
pub origin_url: Option<String>,
#[serde(default)]
pub ignore_origin_duplicate: bool,
pub movie: Option<String>,
pub book: Option<String>,
pub lang: Option<String>,
pub rating: Option<u16>,
pub thumbsize: Option<usize>,
pub iv: Option<String>,
pub uploader: Option<String>,
pub uploadkey: Option<String>,
pub upload_id: Option<String>,
pub original_hash: Option<String>,
pub original_id: Option<String>,
}
impl MediaForUpdate {
pub fn merge_from(&mut self, mut patch: Self) {
fn overwrite_if_some<T>(dst: &mut Option<T>, src: &mut Option<T>) {
if src.is_some() {
*dst = src.take();
}
}
fn append_vec<T>(dst: &mut Option<Vec<T>>, src: &mut Option<Vec<T>>) {
match (dst.as_mut(), src.take()) {
(Some(d), Some(mut s)) => d.append(&mut s),
(None, Some(s)) => *dst = Some(s),
_ => {}
}
}
overwrite_if_some(&mut self.name, &mut patch.name);
overwrite_if_some(&mut self.description, &mut patch.description);
overwrite_if_some(&mut self.mimetype, &mut patch.mimetype);
overwrite_if_some(&mut self.kind, &mut patch.kind);
overwrite_if_some(&mut self.size, &mut patch.size);
overwrite_if_some(&mut self.md5, &mut patch.md5);
overwrite_if_some(&mut self.modified, &mut patch.modified);
overwrite_if_some(&mut self.created, &mut patch.created);
overwrite_if_some(&mut self.width, &mut patch.width);
overwrite_if_some(&mut self.height, &mut patch.height);
overwrite_if_some(&mut self.orientation, &mut patch.orientation);
overwrite_if_some(&mut self.color_space, &mut patch.color_space);
overwrite_if_some(&mut self.icc, &mut patch.icc);
overwrite_if_some(&mut self.mp, &mut patch.mp);
overwrite_if_some(&mut self.vcodecs, &mut patch.vcodecs);
overwrite_if_some(&mut self.acodecs, &mut patch.acodecs);
overwrite_if_some(&mut self.fps, &mut patch.fps);
overwrite_if_some(&mut self.bitrate, &mut patch.bitrate);
overwrite_if_some(&mut self.focal, &mut patch.focal);
overwrite_if_some(&mut self.iso, &mut patch.iso);
overwrite_if_some(&mut self.model, &mut patch.model);
overwrite_if_some(&mut self.sspeed, &mut patch.sspeed);
overwrite_if_some(&mut self.f_number, &mut patch.f_number);
overwrite_if_some(&mut self.pages, &mut patch.pages);
overwrite_if_some(&mut self.duration, &mut patch.duration);
overwrite_if_some(&mut self.progress, &mut patch.progress);
overwrite_if_some(&mut self.season, &mut patch.season);
overwrite_if_some(&mut self.episode, &mut patch.episode);
overwrite_if_some(&mut self.long, &mut patch.long);
overwrite_if_some(&mut self.lat, &mut patch.lat);
overwrite_if_some(&mut self.gps, &mut patch.gps);
overwrite_if_some(&mut self.origin, &mut patch.origin);
overwrite_if_some(&mut self.origin_url, &mut patch.origin_url);
overwrite_if_some(&mut self.movie, &mut patch.movie);
overwrite_if_some(&mut self.book, &mut patch.book);
overwrite_if_some(&mut self.lang, &mut patch.lang);
overwrite_if_some(&mut self.rating, &mut patch.rating);
overwrite_if_some(&mut self.thumbsize, &mut patch.thumbsize);
overwrite_if_some(&mut self.iv, &mut patch.iv);
overwrite_if_some(&mut self.uploader, &mut patch.uploader);
overwrite_if_some(&mut self.uploadkey, &mut patch.uploadkey);
overwrite_if_some(&mut self.upload_id, &mut patch.upload_id);
overwrite_if_some(&mut self.original_hash, &mut patch.original_hash);
overwrite_if_some(&mut self.original_id, &mut patch.original_id);
append_vec(&mut self.add_tags, &mut patch.add_tags);
append_vec(&mut self.remove_tags, &mut patch.remove_tags);
append_vec(&mut self.tags_lookup, &mut patch.tags_lookup);
append_vec(&mut self.add_series, &mut patch.add_series);
append_vec(&mut self.remove_series, &mut patch.remove_series);
append_vec(&mut self.series_lookup, &mut patch.series_lookup);
append_vec(&mut self.add_people, &mut patch.add_people);
append_vec(&mut self.remove_people, &mut patch.remove_people);
append_vec(&mut self.people_lookup, &mut patch.people_lookup);
self.ignore_origin_duplicate |= patch.ignore_origin_duplicate;
}
pub fn merged(mut self, patch: Self) -> Self {
self.merge_from(patch);
self
}
}
impl From<Media> for MediaForUpdate {
fn from(value: Media) -> Self {
MediaForUpdate {
description: value.description,
add_people: None,
add_tags: None,
long: value.long,
lat: value.lat,
created: value.created,
origin: value.origin,
add_series: None,
pages: value.pages,
original_hash: value.original_hash.or(value.md5),
original_id: Some(value.original_id.unwrap_or(value.id)),
book: None,
..Default::default()
}
}
}
impl From<RsRequest> for MediaForUpdate {
fn from(value: RsRequest) -> Self {
let (add_series, season, episode) = if let Some(albums) = &value.albums {
if let Some(serie_id) = albums.first() {
(
Some(vec![FileEpisode {
id: serie_id.clone(),
season: value.season,
episode: value.episode,
episode_to: None,
}]),
None,
None,
)
} else {
(None, value.season, value.episode)
}
} else {
(None, value.season, value.episode)
};
MediaForUpdate {
name: value.filename_or_extract_from_url(),
description: value.description,
ignore_origin_duplicate: value.ignore_origin_duplicate,
size: value.size,
people_lookup: value.people_lookup,
tags_lookup: value.tags_lookup,
series_lookup: value.albums_lookup,
add_series,
movie: value.movie,
season,
episode,
..Default::default()
}
}
}