Add very basic support for video thumbnails
Signed-off-by: trivernis <trivernis@protonmail.com>pull/4/head
parent
a89dc06743
commit
b452ff4c8e
@ -1,20 +1,76 @@
|
||||
use std::io;
|
||||
use image::ImageError;
|
||||
use mime::Mime;
|
||||
use thiserror::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io;
|
||||
use vid2img::{CaptureError, StreamError};
|
||||
|
||||
pub type ThumbResult<T> = Result<T, ThumbError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug)]
|
||||
pub enum ThumbError {
|
||||
#[error("IO Error {0}")]
|
||||
IO(#[from] io::Error),
|
||||
IO(io::Error),
|
||||
|
||||
#[error("Image Error {0}")]
|
||||
Image(#[from] image::error::ImageError),
|
||||
Image(image::error::ImageError),
|
||||
|
||||
#[error("Failed to decode image")]
|
||||
Decode,
|
||||
|
||||
#[error("Unsupported media type {0}")]
|
||||
Unsupported(Mime),
|
||||
|
||||
NullVideo,
|
||||
|
||||
CaptureError(vid2img::CaptureError),
|
||||
|
||||
StreamError(vid2img::StreamError),
|
||||
}
|
||||
|
||||
impl Display for ThumbError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ThumbError::IO(_) => write!(f, "an io error occurred"),
|
||||
ThumbError::Image(e) => write!(f, "an image error occurred {}", e),
|
||||
ThumbError::Decode => write!(f, "failed to decode image"),
|
||||
ThumbError::Unsupported(mime) => write!(f, "Unsupported media type {}", mime),
|
||||
ThumbError::NullVideo => write!(f, "no video data found in file"),
|
||||
ThumbError::CaptureError(c) => {
|
||||
write!(f, "capture error when creating video thumbnail: {:?}", c)
|
||||
}
|
||||
ThumbError::StreamError(s) => {
|
||||
write!(f, "stream error when creating video thumbnail: {:?}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ThumbError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
ThumbError::IO(e) => e.source(),
|
||||
ThumbError::Image(i) => i.source(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ThumbError {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self::IO(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image::error::ImageError> for ThumbError {
|
||||
fn from(e: ImageError) -> Self {
|
||||
Self::Image(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<vid2img::CaptureError> for ThumbError {
|
||||
fn from(e: CaptureError) -> Self {
|
||||
Self::CaptureError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<vid2img::StreamError> for ThumbError {
|
||||
fn from(s: StreamError) -> Self {
|
||||
Self::StreamError(s)
|
||||
}
|
||||
}
|
@ -1,15 +1,18 @@
|
||||
use crate::error::{ThumbError, ThumbResult};
|
||||
use crate::formats::image_format::read_image;
|
||||
use crate::formats::video_format::get_video_frame;
|
||||
use image::DynamicImage;
|
||||
use mime::Mime;
|
||||
use std::io::{BufRead, Seek};
|
||||
|
||||
pub mod image_format;
|
||||
pub mod video_format;
|
||||
|
||||
/// Reads the buffer content into an image that can be used for thumbnail generation
|
||||
pub fn get_base_image<R: BufRead + Seek>(reader: R, mime: Mime) -> ThumbResult<DynamicImage> {
|
||||
match mime.type_() {
|
||||
mime::IMAGE => read_image(reader, mime),
|
||||
mime::VIDEO => get_video_frame(reader, mime),
|
||||
_ => Err(ThumbError::Unsupported(mime)),
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
use crate::error::{ThumbError, ThumbResult};
|
||||
use image::png::PngDecoder;
|
||||
use image::DynamicImage;
|
||||
use mime::Mime;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, Seek};
|
||||
use std::path::PathBuf;
|
||||
use vid2img::FileSource;
|
||||
|
||||
pub fn get_video_frame<R: BufRead + Seek>(mut reader: R, mime: Mime) -> ThumbResult<DynamicImage> {
|
||||
let tempdir = tempfile::tempdir()?;
|
||||
tempdir.path();
|
||||
let path = PathBuf::from(tempdir.path())
|
||||
.join("video")
|
||||
.with_extension(mime.subtype().as_str());
|
||||
|
||||
let mut buf = Vec::new();
|
||||
reader.read_to_end(&mut buf)?;
|
||||
fs::write(&path, buf)?;
|
||||
|
||||
let img = extract_frame_from_video(&path)?;
|
||||
tempdir.close()?;
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
fn extract_frame_from_video(path: &PathBuf) -> ThumbResult<DynamicImage> {
|
||||
let source = FileSource::new(path, (2000, 2000))?;
|
||||
for frame in source.into_iter() {
|
||||
if let Ok(Some(data)) = frame {
|
||||
let decoder = PngDecoder::new(data.as_slice())?;
|
||||
let img = DynamicImage::from_decoder(decoder)?;
|
||||
return Ok(img);
|
||||
}
|
||||
}
|
||||
Err(ThumbError::NullVideo)
|
||||
}
|
Binary file not shown.
@ -0,0 +1,21 @@
|
||||
use mime::Mime;
|
||||
use std::io::Cursor;
|
||||
use std::str::FromStr;
|
||||
use thumbnailer::{create_thumbnails, ThumbnailSize};
|
||||
|
||||
const VIDEO_BYTES: &'static [u8] = include_bytes!("assets/test.mp4");
|
||||
|
||||
#[test]
|
||||
fn it_creates_thumbnails_for_mp4() {
|
||||
let reader = Cursor::new(VIDEO_BYTES);
|
||||
create_thumbnails(
|
||||
reader,
|
||||
Mime::from_str("video/mp4").unwrap(),
|
||||
[
|
||||
ThumbnailSize::Small,
|
||||
ThumbnailSize::Medium,
|
||||
ThumbnailSize::Large,
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
Loading…
Reference in New Issue