Skip to main content

smriti/models/
photo.rs

1//! Photo data model
2
3use 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/// Represents a photo in the library
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Photo {
72    pub id: i64,
73
74    // File info
75    pub file_path: String,
76    pub file_name: String,
77    pub file_hash: String,
78    pub file_size: i64,
79
80    // EXIF data
81    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    // Media metadata
101    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    // Processing state
110    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    // User flags
118    pub is_favorite: bool,
119
120    // Soft delete
121    pub is_trashed: bool,
122    pub trashed_at: Option<DateTime<Utc>>,
123
124    // Timestamps
125    pub indexed_at: DateTime<Utc>,
126    pub updated_at: DateTime<Utc>,
127}
128
129impl Photo {
130    /// Check if this photo has GPS coordinates
131    pub fn has_location(&self) -> bool {
132        self.gps_latitude.is_some() && self.gps_longitude.is_some()
133    }
134
135    /// Get a human-readable location string
136    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}