1use super::types::*;
5use anyhow::Result;
6use blake3::Hasher;
7use serde::{Deserialize, Serialize};
8
9pub struct MediaProcessor {
11 max_file_size: u64,
13 _image_formats: Vec<String>,
15 _video_formats: Vec<String>,
17 _audio_formats: Vec<String>,
19}
20
21impl MediaProcessor {
22 pub fn new() -> Result<Self> {
24 Ok(Self {
25 max_file_size: 100 * 1024 * 1024, _image_formats: ["jpg", "jpeg", "png", "gif", "webp", "avif", "svg"]
27 .iter()
28 .map(|s| s.to_string())
29 .collect(),
30 _video_formats: ["mp4", "webm", "mov", "avi", "mkv"]
31 .iter()
32 .map(|s| s.to_string())
33 .collect(),
34 _audio_formats: ["mp3", "m4a", "ogg", "wav", "aac", "opus"]
35 .iter()
36 .map(|s| s.to_string())
37 .collect(),
38 })
39 }
40
41 pub async fn process_attachment(&self, data: Vec<u8>) -> Result<Attachment> {
43 if data.len() as u64 > self.max_file_size {
45 return Err(anyhow::anyhow!("File too large: {} bytes", data.len()));
46 }
47
48 let mime_type = self.detect_mime_type(&data);
50
51 let dht_hash = self.generate_hash(&data);
53
54 let thumbnail = if self.is_image(&mime_type) || self.is_video(&mime_type) {
56 Some(self.generate_thumbnail(&data, &mime_type).await?)
57 } else {
58 None
59 };
60
61 Ok(Attachment {
63 id: uuid::Uuid::new_v4().to_string(),
64 filename: format!("attachment_{}", chrono::Utc::now().timestamp()),
65 mime_type,
66 size_bytes: data.len() as u64,
67 thumbnail,
68 dht_hash,
69 encryption_key: None, metadata: std::collections::HashMap::new(),
71 })
72 }
73
74 pub async fn process_image(&self, data: Vec<u8>) -> Result<ProcessedImage> {
76 let mime_type = self.detect_mime_type(&data);
77
78 if !self.is_image(&mime_type) {
79 return Err(anyhow::anyhow!("Not an image file"));
80 }
81
82 let thumbnail = self.resize_image(&data, 150, 150).await?;
84 let preview = self.resize_image(&data, 500, 500).await?;
85 let blurhash = self.generate_blurhash(&data).await?;
86
87 Ok(ProcessedImage {
88 original: data.clone(),
89 thumbnail,
90 preview,
91 blurhash,
92 width: 0, height: 0,
94 mime_type,
95 })
96 }
97
98 pub async fn process_video(&self, data: Vec<u8>) -> Result<ProcessedVideo> {
100 let mime_type = self.detect_mime_type(&data);
101
102 if !self.is_video(&mime_type) {
103 return Err(anyhow::anyhow!("Not a video file"));
104 }
105
106 let duration = self.get_video_duration(&data).await?;
108 let thumbnail = self.extract_video_frame(&data, 0.0).await?;
109
110 Ok(ProcessedVideo {
111 data,
112 thumbnail,
113 duration_seconds: duration,
114 width: 0, height: 0,
116 mime_type,
117 streaming_url: None,
118 })
119 }
120
121 pub async fn process_voice_message(&self, data: Vec<u8>) -> Result<VoiceMessage> {
123 let waveform = self.generate_waveform(&data).await?;
125
126 let transcription = self.transcribe_audio(&data).await.ok();
128
129 Ok(VoiceMessage {
130 duration_seconds: self.get_audio_duration(&data).await?,
131 waveform,
132 transcription,
133 mime_type: "audio/opus".to_string(),
134 data,
135 })
136 }
137
138 pub async fn compress_if_needed(&self, data: Vec<u8>, mime_type: &str) -> Result<Vec<u8>> {
140 let size = data.len() as u64;
141
142 if self.is_image(mime_type) && size > 5 * 1024 * 1024 {
144 return self.compress_image(data, 85).await;
145 }
146
147 if self.is_video(mime_type) && size > 20 * 1024 * 1024 {
149 return self.compress_video(data).await;
150 }
151
152 Ok(data)
153 }
154
155 pub async fn create_stream(&self, data: Vec<u8>) -> MediaStream {
157 let chunk_size = 1024 * 1024; let chunks = data
159 .chunks(chunk_size)
160 .map(|chunk| chunk.to_vec())
161 .collect();
162
163 MediaStream {
164 chunks,
165 total_size: data.len() as u64,
166 chunk_size: chunk_size as u32,
167 mime_type: self.detect_mime_type(&data),
168 }
169 }
170
171 pub fn validate_media(&self, data: &[u8], expected_type: &MediaType) -> Result<()> {
173 let mime_type = self.detect_mime_type(data);
174
175 match expected_type {
176 MediaType::Image if !self.is_image(&mime_type) => {
177 Err(anyhow::anyhow!("Expected image, got {}", mime_type))
178 }
179 MediaType::Video if !self.is_video(&mime_type) => {
180 Err(anyhow::anyhow!("Expected video, got {}", mime_type))
181 }
182 MediaType::Audio if !self.is_audio(&mime_type) => {
183 Err(anyhow::anyhow!("Expected audio, got {}", mime_type))
184 }
185 _ => Ok(()),
186 }
187 }
188
189 fn detect_mime_type(&self, data: &[u8]) -> String {
192 if data.starts_with(b"\xFF\xD8\xFF") {
194 "image/jpeg".to_string()
195 } else if data.starts_with(b"\x89PNG") {
196 "image/png".to_string()
197 } else if data.starts_with(b"GIF8") {
198 "image/gif".to_string()
199 } else if data.starts_with(b"RIFF") && data[8..12] == *b"WEBP" {
200 "image/webp".to_string()
201 } else if data.len() > 12 && &data[4..12] == b"ftypavif" {
202 "image/avif".to_string()
203 } else if data.len() > 8 && &data[4..8] == b"ftyp" {
204 "video/mp4".to_string()
205 } else {
206 "application/octet-stream".to_string()
207 }
208 }
209
210 fn is_image(&self, mime_type: &str) -> bool {
211 mime_type.starts_with("image/")
212 }
213
214 fn is_video(&self, mime_type: &str) -> bool {
215 mime_type.starts_with("video/")
216 }
217
218 fn is_audio(&self, mime_type: &str) -> bool {
219 mime_type.starts_with("audio/")
220 }
221
222 fn generate_hash(&self, data: &[u8]) -> String {
223 let mut hasher = Hasher::new();
224 hasher.update(data);
225 hasher.finalize().to_hex().to_string()
226 }
227
228 async fn generate_thumbnail(&self, _data: &[u8], _mime_type: &str) -> Result<Vec<u8>> {
229 Ok(vec![0; 100])
232 }
233
234 async fn resize_image(&self, data: &[u8], _width: u32, _height: u32) -> Result<Vec<u8>> {
235 Ok(data.to_vec())
237 }
238
239 async fn generate_blurhash(&self, _data: &[u8]) -> Result<String> {
240 Ok("LEHV6nWB2yk8pyo0adR*.7kCMdnj".to_string())
242 }
243
244 async fn get_video_duration(&self, _data: &[u8]) -> Result<u32> {
245 Ok(60) }
248
249 async fn extract_video_frame(&self, _data: &[u8], _timestamp: f32) -> Result<Vec<u8>> {
250 Ok(vec![0; 1000])
252 }
253
254 async fn generate_waveform(&self, _data: &[u8]) -> Result<Vec<u8>> {
255 Ok(vec![50; 100]) }
258
259 async fn transcribe_audio(&self, _data: &[u8]) -> Result<String> {
260 Ok("Mock transcription".to_string())
262 }
263
264 async fn get_audio_duration(&self, _data: &[u8]) -> Result<u32> {
265 Ok(30) }
268
269 async fn compress_image(&self, data: Vec<u8>, _quality: u8) -> Result<Vec<u8>> {
270 Ok(data)
272 }
273
274 async fn compress_video(&self, data: Vec<u8>) -> Result<Vec<u8>> {
275 Ok(data)
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct ProcessedImage {
283 pub original: Vec<u8>,
284 pub thumbnail: Vec<u8>,
285 pub preview: Vec<u8>,
286 pub blurhash: String,
287 pub width: u32,
288 pub height: u32,
289 pub mime_type: String,
290}
291
292#[derive(Debug, Clone)]
294pub struct ProcessedVideo {
295 pub data: Vec<u8>,
296 pub thumbnail: Vec<u8>,
297 pub duration_seconds: u32,
298 pub width: u32,
299 pub height: u32,
300 pub mime_type: String,
301 pub streaming_url: Option<String>,
302}
303
304#[derive(Debug, Clone)]
306pub struct MediaStream {
307 pub chunks: Vec<Vec<u8>>,
308 pub total_size: u64,
309 pub chunk_size: u32,
310 pub mime_type: String,
311}
312
313#[derive(Debug, Clone, PartialEq)]
315pub enum MediaType {
316 Image,
317 Video,
318 Audio,
319 Document,
320 Other,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct UploadProgress {
326 pub bytes_uploaded: u64,
327 pub total_bytes: u64,
328 pub percentage: f32,
329 pub estimated_time_remaining: Option<u32>,
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[tokio::test]
337 async fn test_media_processor_creation() {
338 let processor = MediaProcessor::new().unwrap();
339 assert_eq!(processor.max_file_size, 100 * 1024 * 1024);
340 }
341
342 #[tokio::test]
343 async fn test_mime_type_detection() {
344 let processor = MediaProcessor::new().unwrap();
345
346 let jpeg_data = vec![0xFF, 0xD8, 0xFF, 0xE0];
348 assert_eq!(processor.detect_mime_type(&jpeg_data), "image/jpeg");
349
350 let png_data = vec![0x89, 0x50, 0x4E, 0x47];
352 assert_eq!(processor.detect_mime_type(&png_data), "image/png");
353
354 let gif_data = b"GIF89a".to_vec();
356 assert_eq!(processor.detect_mime_type(&gif_data), "image/gif");
357 }
358
359 #[tokio::test]
360 async fn test_file_size_validation() {
361 let processor = MediaProcessor::new().unwrap();
362
363 let large_data = vec![0; 101 * 1024 * 1024];
365 let result = processor.process_attachment(large_data).await;
366 assert!(result.is_err());
367
368 let normal_data = vec![0; 1024];
370 let result = processor.process_attachment(normal_data).await;
371 assert!(result.is_ok());
372 }
373
374 #[tokio::test]
375 async fn test_hash_generation() {
376 let processor = MediaProcessor::new().unwrap();
377
378 let data = b"test data".to_vec();
379 let hash = processor.generate_hash(&data);
380
381 assert_eq!(hash.len(), 64); }
384}