1use crate::error::{MemvidError, Result};
6use image::DynamicImage;
7use std::path::Path;
8
9pub struct VideoDecoder {}
11
12impl VideoDecoder {
13 pub fn new() -> Result<Self> {
15 ffmpeg_next::init()
17 .map_err(|e| MemvidError::Video(format!("FFmpeg init failed: {}", e)))?;
18
19 Ok(Self {})
20 }
21
22 pub async fn extract_frames(&self, video_path: &str) -> Result<Vec<DynamicImage>> {
24 let path = Path::new(video_path);
25 if !path.exists() {
26 return Err(MemvidError::Video(format!(
27 "Video file not found: {}",
28 video_path
29 )));
30 }
31
32 log::info!("Extracting frames from video: {}", video_path);
33
34 let mut input_ctx = ffmpeg_next::format::input(&path)
36 .map_err(|e| MemvidError::Video(format!("Failed to open video file: {}", e)))?;
37
38 let video_stream_index = input_ctx
40 .streams()
41 .best(ffmpeg_next::media::Type::Video)
42 .ok_or_else(|| MemvidError::Video("No video stream found".to_string()))?
43 .index();
44
45 let video_stream = input_ctx
47 .stream(video_stream_index)
48 .ok_or_else(|| MemvidError::Video("Failed to get video stream".to_string()))?;
49
50 let context_decoder =
52 ffmpeg_next::codec::context::Context::from_parameters(video_stream.parameters())
53 .map_err(|e| {
54 MemvidError::Video(format!("Failed to create decoder context: {}", e))
55 })?;
56
57 let mut decoder = context_decoder
58 .decoder()
59 .video()
60 .map_err(|e| MemvidError::Video(format!("Failed to create video decoder: {}", e)))?;
61
62 let width = decoder.width();
64 let height = decoder.height();
65
66 log::info!("Video dimensions: {}x{}", width, height);
67
68 let mut scaler = ffmpeg_next::software::scaling::Context::get(
70 decoder.format(),
71 width,
72 height,
73 ffmpeg_next::format::Pixel::RGB24,
74 width,
75 height,
76 ffmpeg_next::software::scaling::Flags::BILINEAR,
77 )
78 .map_err(|e| MemvidError::Video(format!("Failed to create scaler: {}", e)))?;
79
80 let mut frames = Vec::new();
81 let mut frame_count = 0;
82
83 for (stream, packet) in input_ctx.packets() {
85 if stream.index() == video_stream_index {
86 decoder
87 .send_packet(&packet)
88 .map_err(|e| MemvidError::Video(format!("Failed to send packet: {}", e)))?;
89
90 self.receive_frames(&mut decoder, &mut scaler, &mut frames, &mut frame_count)
91 .await?;
92 }
93 }
94
95 decoder
97 .send_eof()
98 .map_err(|e| MemvidError::Video(format!("Failed to send EOF: {}", e)))?;
99
100 self.receive_frames(&mut decoder, &mut scaler, &mut frames, &mut frame_count)
101 .await?;
102
103 log::info!("Extracted {} frames from video", frames.len());
104 Ok(frames)
105 }
106
107 pub async fn extract_frame(&self, video_path: &str, frame_number: u32) -> Result<DynamicImage> {
109 let path = Path::new(video_path);
110 if !path.exists() {
111 return Err(MemvidError::Video(format!(
112 "Video file not found: {}",
113 video_path
114 )));
115 }
116
117 log::debug!(
118 "Extracting frame {} from video: {}",
119 frame_number,
120 video_path
121 );
122
123 let mut input_ctx = ffmpeg_next::format::input(&path)
125 .map_err(|e| MemvidError::Video(format!("Failed to open video file: {}", e)))?;
126
127 let video_stream_index = input_ctx
129 .streams()
130 .best(ffmpeg_next::media::Type::Video)
131 .ok_or_else(|| MemvidError::Video("No video stream found".to_string()))?
132 .index();
133
134 let (time_base, avg_frame_rate, stream_parameters) = {
136 let video_stream = input_ctx
137 .stream(video_stream_index)
138 .ok_or_else(|| MemvidError::Video("Failed to get video stream".to_string()))?;
139 (
140 video_stream.time_base(),
141 video_stream.avg_frame_rate(),
142 video_stream.parameters(),
143 )
144 };
145
146 let frame_duration = if avg_frame_rate.denominator() > 0 && avg_frame_rate.numerator() > 0 {
148 time_base.denominator() as f64
149 / (time_base.numerator() as f64 * avg_frame_rate.numerator() as f64
150 / avg_frame_rate.denominator() as f64)
151 } else {
152 time_base.denominator() as f64 / (time_base.numerator() as f64 * 30.0)
154 };
155
156 let target_timestamp = (frame_number as f64 * frame_duration) as i64;
157
158 if frame_number > 0 {
160 input_ctx
161 .seek(
162 target_timestamp,
163 ..target_timestamp + (frame_duration as i64),
164 )
165 .map_err(|e| {
166 MemvidError::Video(format!("Failed to seek to frame {}: {}", frame_number, e))
167 })?;
168 }
169
170 let context_decoder = ffmpeg_next::codec::context::Context::from_parameters(
172 stream_parameters,
173 )
174 .map_err(|e| MemvidError::Video(format!("Failed to create decoder context: {}", e)))?;
175
176 let mut decoder = context_decoder
177 .decoder()
178 .video()
179 .map_err(|e| MemvidError::Video(format!("Failed to create video decoder: {}", e)))?;
180
181 let width = decoder.width();
183 let height = decoder.height();
184
185 let mut scaler = ffmpeg_next::software::scaling::Context::get(
187 decoder.format(),
188 width,
189 height,
190 ffmpeg_next::format::Pixel::RGB24,
191 width,
192 height,
193 ffmpeg_next::software::scaling::Flags::BILINEAR,
194 )
195 .map_err(|e| MemvidError::Video(format!("Failed to create scaler: {}", e)))?;
196
197 let mut current_frame = 0u32;
198
199 for (stream, packet) in input_ctx.packets() {
201 if stream.index() == video_stream_index {
202 decoder
203 .send_packet(&packet)
204 .map_err(|e| MemvidError::Video(format!("Failed to send packet: {}", e)))?;
205
206 let mut decoded_frame = ffmpeg_next::frame::Video::empty();
207 while decoder.receive_frame(&mut decoded_frame).is_ok() {
208 if current_frame == frame_number {
209 let mut rgb_frame = ffmpeg_next::frame::Video::new(
211 ffmpeg_next::format::Pixel::RGB24,
212 width,
213 height,
214 );
215
216 scaler.run(&decoded_frame, &mut rgb_frame).map_err(|e| {
217 MemvidError::Video(format!("Failed to scale frame: {}", e))
218 })?;
219
220 let image = self.frame_to_image(&rgb_frame)?;
221
222 log::debug!("Successfully extracted frame {} from video", frame_number);
223 return Ok(image);
224 }
225 current_frame += 1;
226
227 if current_frame > frame_number {
229 break;
230 }
231 }
232
233 if current_frame > frame_number {
234 break;
235 }
236 }
237 }
238
239 Err(MemvidError::Video(format!(
240 "Frame number {} not found (video has fewer frames or seeking failed)",
241 frame_number
242 )))
243 }
244
245 pub async fn extract_frame_slow(
247 &self,
248 video_path: &str,
249 frame_number: u32,
250 ) -> Result<DynamicImage> {
251 let frames = self.extract_frames(video_path).await?;
252
253 if frame_number as usize >= frames.len() {
254 return Err(MemvidError::Video(format!(
255 "Frame number {} out of range (video has {} frames)",
256 frame_number,
257 frames.len()
258 )));
259 }
260
261 Ok(frames[frame_number as usize].clone())
262 }
263
264 pub async fn get_video_info(&self, video_path: &str) -> Result<VideoInfo> {
266 let path = Path::new(video_path);
267 if !path.exists() {
268 return Err(MemvidError::Video(format!(
269 "Video file not found: {}",
270 video_path
271 )));
272 }
273
274 let input_ctx = ffmpeg_next::format::input(&path)
276 .map_err(|e| MemvidError::Video(format!("Failed to open video file: {}", e)))?;
277
278 let video_stream = input_ctx
280 .streams()
281 .best(ffmpeg_next::media::Type::Video)
282 .ok_or_else(|| MemvidError::Video("No video stream found".to_string()))?;
283
284 let context_decoder =
286 ffmpeg_next::codec::context::Context::from_parameters(video_stream.parameters())
287 .map_err(|e| {
288 MemvidError::Video(format!("Failed to create decoder context: {}", e))
289 })?;
290
291 let decoder = context_decoder
292 .decoder()
293 .video()
294 .map_err(|e| MemvidError::Video(format!("Failed to create video decoder: {}", e)))?;
295
296 let duration_seconds = if video_stream.duration() > 0 {
298 let time_base: f64 = video_stream.time_base().into();
299 video_stream.duration() as f64 * time_base
300 } else {
301 input_ctx.duration() as f64 / ffmpeg_next::ffi::AV_TIME_BASE as f64
302 };
303
304 let fps: f64 = video_stream.avg_frame_rate().into();
305 let frame_count = if fps > 0.0 {
306 (duration_seconds * fps) as u32
307 } else {
308 0
309 };
310
311 Ok(VideoInfo {
312 width: decoder.width(),
313 height: decoder.height(),
314 fps,
315 duration_seconds,
316 frame_count,
317 format: format!("{:?}", decoder.format()),
318 codec: "H.264".to_string(), })
320 }
321
322 async fn receive_frames(
324 &self,
325 decoder: &mut ffmpeg_next::decoder::Video,
326 scaler: &mut ffmpeg_next::software::scaling::Context,
327 frames: &mut Vec<DynamicImage>,
328 frame_count: &mut u32,
329 ) -> Result<()> {
330 let mut decoded_frame = ffmpeg_next::frame::Video::empty();
331
332 while decoder.receive_frame(&mut decoded_frame).is_ok() {
333 let width = decoded_frame.width();
334 let height = decoded_frame.height();
335
336 let mut rgb_frame =
338 ffmpeg_next::frame::Video::new(ffmpeg_next::format::Pixel::RGB24, width, height);
339
340 scaler
342 .run(&decoded_frame, &mut rgb_frame)
343 .map_err(|e| MemvidError::Video(format!("Failed to scale frame: {}", e)))?;
344
345 let image = self.frame_to_image(&rgb_frame)?;
347 frames.push(image);
348
349 *frame_count += 1;
350
351 if *frame_count % 10 == 0 {
352 log::info!("Processed {} frames", *frame_count);
353 }
354 }
355
356 Ok(())
357 }
358
359 fn frame_to_image(&self, frame: &ffmpeg_next::frame::Video) -> Result<DynamicImage> {
361 let width = frame.width();
362 let height = frame.height();
363
364 let data = frame.data(0);
366 let linesize = frame.stride(0);
367
368 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
370
371 for y in 0..height {
372 let row_start = (y * linesize as u32) as usize;
373 let row_end = row_start + (width * 3) as usize;
374 rgb_data.extend_from_slice(&data[row_start..row_end]);
375 }
376
377 let rgb_image = image::RgbImage::from_raw(width, height, rgb_data).ok_or_else(|| {
379 MemvidError::Video("Failed to create RGB image from frame data".to_string())
380 })?;
381
382 Ok(DynamicImage::ImageRgb8(rgb_image))
383 }
384
385 pub async fn extract_frames_range(
387 &self,
388 video_path: &str,
389 start_frame: u32,
390 end_frame: u32,
391 ) -> Result<Vec<DynamicImage>> {
392 if start_frame > end_frame {
393 return Err(MemvidError::Video(format!(
394 "Invalid frame range: start {} > end {}",
395 start_frame, end_frame
396 )));
397 }
398
399 let path = Path::new(video_path);
400 if !path.exists() {
401 return Err(MemvidError::Video(format!(
402 "Video file not found: {}",
403 video_path
404 )));
405 }
406
407 log::debug!(
408 "Extracting frames {}-{} from video: {}",
409 start_frame,
410 end_frame,
411 video_path
412 );
413
414 let mut input_ctx = ffmpeg_next::format::input(&path)
416 .map_err(|e| MemvidError::Video(format!("Failed to open video file: {}", e)))?;
417
418 let video_stream_index = input_ctx
420 .streams()
421 .best(ffmpeg_next::media::Type::Video)
422 .ok_or_else(|| MemvidError::Video("No video stream found".to_string()))?
423 .index();
424
425 let (time_base, avg_frame_rate, stream_parameters) = {
427 let video_stream = input_ctx
428 .stream(video_stream_index)
429 .ok_or_else(|| MemvidError::Video("Failed to get video stream".to_string()))?;
430 (
431 video_stream.time_base(),
432 video_stream.avg_frame_rate(),
433 video_stream.parameters(),
434 )
435 };
436
437 let frame_duration = if avg_frame_rate.denominator() > 0 && avg_frame_rate.numerator() > 0 {
439 time_base.denominator() as f64
440 / (time_base.numerator() as f64 * avg_frame_rate.numerator() as f64
441 / avg_frame_rate.denominator() as f64)
442 } else {
443 time_base.denominator() as f64 / (time_base.numerator() as f64 * 30.0)
445 };
446
447 let start_timestamp = (start_frame as f64 * frame_duration) as i64;
448
449 if start_frame > 0 {
451 input_ctx
452 .seek(start_timestamp, ..start_timestamp + (frame_duration as i64))
453 .map_err(|e| {
454 MemvidError::Video(format!("Failed to seek to frame {}: {}", start_frame, e))
455 })?;
456 }
457
458 let context_decoder = ffmpeg_next::codec::context::Context::from_parameters(
460 stream_parameters,
461 )
462 .map_err(|e| MemvidError::Video(format!("Failed to create decoder context: {}", e)))?;
463
464 let mut decoder = context_decoder
465 .decoder()
466 .video()
467 .map_err(|e| MemvidError::Video(format!("Failed to create video decoder: {}", e)))?;
468
469 let width = decoder.width();
471 let height = decoder.height();
472
473 let mut scaler = ffmpeg_next::software::scaling::Context::get(
475 decoder.format(),
476 width,
477 height,
478 ffmpeg_next::format::Pixel::RGB24,
479 width,
480 height,
481 ffmpeg_next::software::scaling::Flags::BILINEAR,
482 )
483 .map_err(|e| MemvidError::Video(format!("Failed to create scaler: {}", e)))?;
484
485 let mut current_frame = 0u32;
486 let mut extracted_frames = Vec::new();
487
488 for (stream, packet) in input_ctx.packets() {
490 if stream.index() == video_stream_index {
491 decoder
492 .send_packet(&packet)
493 .map_err(|e| MemvidError::Video(format!("Failed to send packet: {}", e)))?;
494
495 let mut decoded_frame = ffmpeg_next::frame::Video::empty();
496 while decoder.receive_frame(&mut decoded_frame).is_ok() {
497 if current_frame >= start_frame && current_frame <= end_frame {
498 let mut rgb_frame = ffmpeg_next::frame::Video::new(
500 ffmpeg_next::format::Pixel::RGB24,
501 width,
502 height,
503 );
504
505 scaler.run(&decoded_frame, &mut rgb_frame).map_err(|e| {
506 MemvidError::Video(format!("Failed to scale frame: {}", e))
507 })?;
508
509 let image = self.frame_to_image(&rgb_frame)?;
510 extracted_frames.push(image);
511 }
512
513 current_frame += 1;
514
515 if current_frame > end_frame {
517 log::debug!(
518 "Successfully extracted {} frames ({}-{})",
519 extracted_frames.len(),
520 start_frame,
521 end_frame
522 );
523 return Ok(extracted_frames);
524 }
525 }
526
527 if current_frame > end_frame {
529 break;
530 }
531 }
532 }
533
534 log::debug!(
535 "Successfully extracted {} frames ({}-{})",
536 extracted_frames.len(),
537 start_frame,
538 end_frame
539 );
540 Ok(extracted_frames)
541 }
542
543 pub async fn extract_frames_range_slow(
545 &self,
546 video_path: &str,
547 start_frame: u32,
548 end_frame: u32,
549 ) -> Result<Vec<DynamicImage>> {
550 let all_frames = self.extract_frames(video_path).await?;
551
552 let start_idx = start_frame as usize;
553 let end_idx = (end_frame + 1) as usize;
554
555 if start_idx >= all_frames.len() {
556 return Err(MemvidError::Video(format!(
557 "Start frame {} out of range (video has {} frames)",
558 start_frame,
559 all_frames.len()
560 )));
561 }
562
563 let end_idx = end_idx.min(all_frames.len());
564 Ok(all_frames[start_idx..end_idx].to_vec())
565 }
566}
567
568impl Default for VideoDecoder {
569 fn default() -> Self {
570 Self::new().unwrap_or(Self {})
571 }
572}
573
574#[derive(Debug, Clone)]
576pub struct VideoInfo {
577 pub width: u32,
579 pub height: u32,
581 pub fps: f64,
583 pub duration_seconds: f64,
585 pub frame_count: u32,
587 pub format: String,
589 pub codec: String,
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[tokio::test]
598 async fn test_decoder_creation() {
599 let decoder = VideoDecoder::new();
600 assert!(decoder.is_ok());
601 }
602
603 #[tokio::test]
604 async fn test_nonexistent_video() {
605 let decoder = VideoDecoder::new().unwrap();
606 let result = decoder.extract_frames("nonexistent.mp4").await;
607 assert!(result.is_err());
608 }
609}