1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[serde(rename_all = "snake_case")]
8pub enum MediaType {
9 #[default]
10 Photo,
11 Video,
12}
13
14impl MediaType {
15 pub fn as_str(self) -> &'static str {
16 match self {
17 Self::Photo => "photo",
18 Self::Video => "video",
19 }
20 }
21
22 pub fn from_db(value: &str) -> Self {
23 match value {
24 "video" => Self::Video,
25 _ => Self::Photo,
26 }
27 }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
31#[serde(rename_all = "snake_case")]
32pub enum ContentCategory {
33 #[default]
34 Photo,
35 BusinessCard,
36 Document,
37 Screenshot,
38 Presentation,
39 Whiteboard,
40 Receipt,
41}
42
43impl ContentCategory {
44 pub fn as_str(self) -> &'static str {
45 match self {
46 Self::Photo => "photo",
47 Self::BusinessCard => "business_card",
48 Self::Document => "document",
49 Self::Screenshot => "screenshot",
50 Self::Presentation => "presentation",
51 Self::Whiteboard => "whiteboard",
52 Self::Receipt => "receipt",
53 }
54 }
55
56 pub fn from_db(value: &str) -> Self {
57 match value {
58 "document" => Self::Document,
59 "business_card" => Self::BusinessCard,
60 "screenshot" => Self::Screenshot,
61 "presentation" => Self::Presentation,
62 "whiteboard" => Self::Whiteboard,
63 "receipt" => Self::Receipt,
64 _ => Self::Photo,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Photo {
72 pub id: i64,
73
74 pub file_path: String,
76 pub file_name: String,
77 pub file_hash: String,
78 pub file_size: i64,
79
80 pub date_taken: Option<DateTime<Utc>>,
82 pub date_taken_source: Option<String>,
83 pub gps_latitude: Option<f64>,
84 pub gps_longitude: Option<f64>,
85 pub location_city: Option<String>,
86 pub location_country: Option<String>,
87 pub camera_make: Option<String>,
88 pub camera_model: Option<String>,
89 pub iso: Option<i32>,
90 pub aperture: Option<String>,
91 pub shutter_speed: Option<String>,
92 pub focal_length: Option<String>,
93 pub lens_model: Option<String>,
94 pub flash: Option<String>,
95 pub gps_altitude: Option<f64>,
96 pub width: Option<i32>,
97 pub height: Option<i32>,
98 pub orientation: i32,
99
100 pub media_type: MediaType,
102 pub duration_ms: Option<i64>,
103 pub video_codec: Option<String>,
104 pub audio_codec: Option<String>,
105 pub frame_rate: Option<f32>,
106 pub bitrate: Option<i64>,
107 pub has_audio: bool,
108
109 pub thumbnail_path: Option<String>,
111 pub faces_processed: bool,
112 pub content_category: ContentCategory,
113 pub ocr_text: Option<String>,
114 pub ocr_processed: bool,
115 pub ocr_confidence: Option<f32>,
116
117 pub is_favorite: bool,
119
120 pub is_trashed: bool,
122 pub trashed_at: Option<DateTime<Utc>>,
123
124 pub indexed_at: DateTime<Utc>,
126 pub updated_at: DateTime<Utc>,
127}
128
129impl Photo {
130 pub fn has_location(&self) -> bool {
132 self.gps_latitude.is_some() && self.gps_longitude.is_some()
133 }
134
135 pub fn location_string(&self) -> Option<String> {
137 match (&self.location_city, &self.location_country) {
138 (Some(city), Some(country)) => Some(format!("{}, {}", city, country)),
139 (Some(city), None) => Some(city.clone()),
140 (None, Some(country)) => Some(country.clone()),
141 (None, None) => None,
142 }
143 }
144}