-
-
Save trizist/a04f3d2c2bba0bb14a0e181ea13c3be9 to your computer and use it in GitHub Desktop.
Example FFmpeg to image output
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::error::Error; | |
| use std::path::{Path, PathBuf}; | |
| use structopt::StructOpt; | |
| use image::{ImageBuffer, Rgba}; | |
| use std::time::Instant; | |
| use ffmpeg_next::ffi::AV_TIME_BASE; | |
| use ffmpeg_next::software::scaling::flag::BICUBIC; | |
| use ffmpeg_next::{decoder, format, frame, media, software::scaling, Error as FFMpegError}; | |
| #[derive(StructOpt)] | |
| struct Opt { | |
| #[structopt(parse(from_os_str))] | |
| input: PathBuf, | |
| /// Seek time in seconds | |
| #[structopt(short = "t", long = "time", default_value = "0.0")] | |
| seek_time: f64, | |
| #[structopt(parse(from_os_str))] | |
| output: PathBuf, | |
| } | |
| struct ImageIter { | |
| input: format::context::Input, | |
| stream_index: usize, | |
| decoder: decoder::Video, | |
| context: Option<scaling::Context>, | |
| } | |
| impl ImageIter { | |
| fn new<I: AsRef<Path>>(path: I) -> Result<Self, Box<dyn Error>> { | |
| let input = format::input(&path)?; | |
| if let Some(stream) = input.streams().best(media::Type::Video) { | |
| let stream_index = stream.index(); | |
| if let Ok(video) = stream.codec().decoder().video() { | |
| println!("Video width:{}", video.width()); | |
| println!("Video format:{:?}", video.format()); | |
| println!("Video height:{}", video.height()); | |
| let mut context = None; | |
| if video.format() != format::Pixel::RGBA { | |
| println!("Need to convert to RGBA"); | |
| context = Some(scaling::Context::get( | |
| video.format(), | |
| video.width(), | |
| video.height(), | |
| format::Pixel::RGBA, | |
| video.width(), | |
| video.height(), | |
| BICUBIC, | |
| )?); | |
| } | |
| return Ok(Self { | |
| input, | |
| stream_index, | |
| decoder: video, | |
| context, | |
| }); | |
| } | |
| } | |
| return Err(Box::new(FFMpegError::Unknown)); | |
| } | |
| fn seek(&mut self, time_s: f64) -> Result<(), Box<dyn Error>> { | |
| let timestamp = (time_s * AV_TIME_BASE as f64) as i64; | |
| self.input.seek(timestamp, timestamp..)?; | |
| Ok(()) | |
| } | |
| } | |
| pub fn get_ms(now: Instant) -> f32 { | |
| let duration = now.elapsed(); | |
| let nanos = duration.subsec_nanos() as f32; | |
| (1000000000f32 * duration.as_secs() as f32 + nanos) / (1000000f32) | |
| } | |
| impl Iterator for ImageIter { | |
| type Item = Result<ImageBuffer<Rgba<u8>, Vec<u8>>, Box<dyn Error>>; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| let imagetime = Instant::now(); | |
| let mut output = frame::Video::empty(); | |
| while let Some((stream, packet)) = self.input.packets().next() { | |
| if stream.index() != self.stream_index { | |
| continue; | |
| } | |
| let decodetime = Instant::now(); | |
| if let Err(err) = self.decoder.decode(&packet, &mut output) { | |
| return Some(Err(Box::new(err))); | |
| } | |
| println!("Image decode time: {}", get_ms(decodetime)); | |
| if output.format() == format::Pixel::None { | |
| println!("Skipping null format frame"); | |
| continue; | |
| } | |
| if let Some(ref mut context) = self.context { | |
| let convert_time = Instant::now(); | |
| let mut new_output = frame::Video::empty(); | |
| if let Err(err) = context.run(&output, &mut new_output) { | |
| return Some(Err(Box::new(err))); | |
| } | |
| println!("Image convert time: {}", get_ms(convert_time)); | |
| output = new_output; | |
| } | |
| let data = output.data(0).to_vec(); | |
| if let Some(image) = | |
| ImageBuffer::<Rgba<u8>, _>::from_raw(output.width(), output.height(), data) | |
| { | |
| println!("Image read time: {}", get_ms(imagetime)); | |
| return Some(Ok(image)); | |
| } | |
| } | |
| None | |
| } | |
| } | |
| fn main() -> Result<(), Box<dyn Error>> { | |
| let opt = Opt::from_args(); | |
| let mut iter = ImageIter::new(opt.input)?; | |
| iter.seek(opt.seek_time)?; | |
| if let Some(image_result) = iter.next() { | |
| let image = image_result?; | |
| image.save(opt.output)?; | |
| } | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment