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 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>;
|
pub type ThumbResult<T> = Result<T, ThumbError>;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug)]
|
||||||
pub enum ThumbError {
|
pub enum ThumbError {
|
||||||
#[error("IO Error {0}")]
|
IO(io::Error),
|
||||||
IO(#[from] io::Error),
|
|
||||||
|
|
||||||
#[error("Image Error {0}")]
|
Image(image::error::ImageError),
|
||||||
Image(#[from] image::error::ImageError),
|
|
||||||
|
|
||||||
#[error("Failed to decode image")]
|
|
||||||
Decode,
|
Decode,
|
||||||
|
|
||||||
#[error("Unsupported media type {0}")]
|
|
||||||
Unsupported(Mime),
|
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::error::{ThumbError, ThumbResult};
|
||||||
use crate::formats::image_format::read_image;
|
use crate::formats::image_format::read_image;
|
||||||
|
use crate::formats::video_format::get_video_frame;
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use std::io::{BufRead, Seek};
|
use std::io::{BufRead, Seek};
|
||||||
|
|
||||||
pub mod image_format;
|
pub mod image_format;
|
||||||
|
pub mod video_format;
|
||||||
|
|
||||||
/// Reads the buffer content into an image that can be used for thumbnail generation
|
/// 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> {
|
pub fn get_base_image<R: BufRead + Seek>(reader: R, mime: Mime) -> ThumbResult<DynamicImage> {
|
||||||
match mime.type_() {
|
match mime.type_() {
|
||||||
mime::IMAGE => read_image(reader, mime),
|
mime::IMAGE => read_image(reader, mime),
|
||||||
|
mime::VIDEO => get_video_frame(reader, mime),
|
||||||
_ => Err(ThumbError::Unsupported(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