Add very basic support for video thumbnails

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent a89dc06743
commit b452ff4c8e
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -4,16 +4,18 @@ readme = "README.md"
license = "Apache-2.0" license = "Apache-2.0"
authors = ["trivernis <trivernis@protonmail.com>"] authors = ["trivernis <trivernis@protonmail.com>"]
description = "An image thumbnail creation library" description = "An image thumbnail creation library"
version = "0.2.0" version = "0.2.1"
edition = "2018" edition = "2018"
repository = "https://github.com/Trivernis/thumbnailer"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
webp = "0.2.0" webp = "0.2.0"
mime = "0.3.16" mime = "0.3.16"
thiserror = "1.0.30"
rayon = "1.5.1" rayon = "1.5.1"
vid2img = "0.1.0"
tempfile = "3.2.0"
[dependencies.image] [dependencies.image]
version = "0.23.14" version = "0.23.14"

@ -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…
Cancel
Save