Skip to content
Snippets Groups Projects
Verified Commit dbbbef37 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement metadata generation functionality

parent 38a6bc5f
No related branches found
No related tags found
No related merge requests found
......@@ -382,6 +382,12 @@ dependencies = [
"adler32",
]
[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "jpeg-decoder"
version = "0.1.18"
......@@ -450,6 +456,12 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "miniz_oxide"
version = "0.3.6"
......@@ -698,6 +710,12 @@ dependencies = [
"syn",
]
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
......@@ -725,6 +743,37 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "0.1.1"
......@@ -809,6 +858,9 @@ dependencies = [
"fraction",
"image",
"log",
"mime",
"serde",
"serde_json",
"time",
]
......
......@@ -13,4 +13,7 @@ image = "0.23.0"
enum_primitive = "0.1.1"
failure = "0.1.6"
fraction = "0.6.2"
mime = "0.3.16"
time = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
\ No newline at end of file
use ffmpeg_dev::sys as ffi;
use failure::bail;
use enum_primitive::*;
use std::marker::PhantomData;
use enum_primitive::*;
use failure::bail;
use ffmpeg_dev::sys as ffi;
use fraction::Fraction;
use crate::ffmpeg_api::enums::*;
use crate::util::media_time;
use std::path::Path;
pub struct AVFormatContext {
base: *mut ffi::AVFormatContext,
......@@ -20,13 +22,16 @@ impl<'a> AVFormatContext {
Ok(AVFormatContext { base })
}
pub fn open_input(&mut self, path: &str) -> Result<(), failure::Error> {
pub fn open_input(&mut self, path: &Path) -> Result<(), failure::Error> {
let path = path.to_str()
.ok_or_else(|| failure::format_err!("Could not convert path to c string"))?;
let path = std::ffi::CString::new(path)
.map_err(|_| failure::format_err!("Could not convert path to c string"))?;
match unsafe {
ffi::avformat_open_input(
&mut self.base,
std::ffi::CString::new(path)
.map_err(|_| failure::format_err!("Could not convert path to c string"))?
.as_ptr(),
path.as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
......@@ -36,6 +41,10 @@ impl<'a> AVFormatContext {
}
}
pub fn input_format(&self) -> AVInputFormat {
AVInputFormat::new(unsafe { (*self.base).iformat.as_mut() }.expect("not null"))
}
pub fn streams(&self) -> Vec<AVStream> {
Vec::from(
unsafe {
......@@ -69,6 +78,15 @@ impl<'a> AVFormatContext {
errno => Err(failure::format_err!("Error while decoding frame: {}", errno))
}
}
pub fn duration(&self) -> Result<media_time::MediaTime, failure::Error> {
media_time::MediaTime::from_rational(
unsafe { (*self.base).duration }, Fraction::new(
1 as u64,
ffi::AV_TIME_BASE as u64,
),
)
}
}
impl Drop for AVFormatContext {
......@@ -77,6 +95,89 @@ impl Drop for AVFormatContext {
}
}
pub struct AVInputFormat<'a> {
base: &'a mut ffi::AVInputFormat
}
impl<'a> AVInputFormat<'a> {
fn new(base: &'a mut ffi::AVInputFormat) -> Self {
return AVInputFormat { base };
}
pub fn long_name(&self) -> Result<String, failure::Error> {
let raw: *const ::std::os::raw::c_char = self.base.long_name;
if raw.is_null() {
Err(failure::format_err!("No mime type found"))
} else {
Ok(String::from(unsafe {
std::ffi::CStr::from_ptr(raw)
}.to_str().map_err(|err| {
failure::format_err!("Could not convert mime type to string: {}", err)
})?))
}
}
pub fn name(&self) -> Result<String, failure::Error> {
let raw: *const ::std::os::raw::c_char = self.base.name;
if raw.is_null() {
Err(failure::format_err!("No mime type found"))
} else {
Ok(String::from(unsafe {
std::ffi::CStr::from_ptr(raw)
}.to_str().map_err(|err| {
failure::format_err!("Could not convert mime type to string: {}", err)
})?))
}
}
pub fn mime(&self) -> Result<String, failure::Error> {
let raw: *const ::std::os::raw::c_char = self.base.mime_type;
if raw.is_null() {
Err(failure::format_err!("No mime type found"))
} else {
Ok(String::from(unsafe {
std::ffi::CStr::from_ptr(raw)
}.to_str().map_err(|err| {
failure::format_err!("Could not convert mime type to string: {}", err)
})?))
}
}
pub fn determine_mime<T: AsRef<str>>(&self, stream_codec: T) -> Result<String, failure::Error> {
let containers = self.name()?;
let stream_codec = stream_codec.as_ref();
for container in containers.split(",") {
match container {
"mp4" => match stream_codec {
"h264" => {
return Ok(String::from("video/mp4"));
}
_ => {
// Do nothing
}
}
"webm" => match stream_codec {
"vp8" | "vp9" | "av1" => {
return Ok(String::from("video/webm"));
}
_ => {
// Do nothing
}
}
_ => {
// Do nothing
}
}
}
return Err(failure::format_err!("Could not determine mime type: {} video in {} container", stream_codec, containers));
}
}
pub struct AVBuffer {
base: *mut u8,
size: usize,
......@@ -349,6 +450,10 @@ impl<'a> AVCodecParameters<'a> {
AVCodecID::from_u32(self.base.codec_id)
}
pub fn bit_rate(&self) -> i64 {
self.base.bit_rate
}
pub fn find_decoder(&self) -> AVCodec {
AVCodec::new(
unsafe { ffi::avcodec_find_decoder(self.base.codec_id).as_mut() }.expect("Decoder not found"),
......
use std::path::Path;
use failure::format_err;
use crate::ffmpeg_api::api::*;
use crate::ffmpeg_api::enums::*;
use crate::util::media_time::*;
use crate::thumbnail::spritesheet::*;
use crate::ingest::spritesheet::*;
use crate::util::stream_metadata::*;
use failure::format_err;
pub fn extract<T: AsRef<str>, U: AsRef<str>>(
pub fn extract(
max_size: u32,
num_horizontal: u32, num_vertical: u32,
frame_interval: MediaTime,
input_file: T,
output_folder: U,
input_file: &Path,
output_folder: &Path,
) -> Result<(), failure::Error> {
let mut avformat_context = AVFormatContext::new().map_err(|error| {
format_err!("Could not allocate a context to process the video: {:?}", error)
})?;
avformat_context.open_input(input_file.as_ref()).map_err(|error| {
avformat_context.open_input(input_file).map_err(|error| {
format_err!("Could not open video input: {:?}", error)
})?;
let duration = avformat_context.duration()?;
let spritesheet_path = output_folder.join("spritesheets");
std::fs::create_dir_all(&spritesheet_path)?;
let mut spritesheet_manager = SpritesheetManager::new(
max_size,
num_horizontal, num_vertical,
frame_interval,
&output_folder,
spritesheet_path,
"preview"
);
......@@ -36,7 +41,6 @@ pub fn extract<T: AsRef<str>, U: AsRef<str>>(
let index = stream.index();
let time_base = stream.time_base();
let duration = stream.duration()?;
let codec_parameters = stream.codec_parameters();
let local_codec = codec_parameters.find_decoder();
......@@ -48,6 +52,12 @@ pub fn extract<T: AsRef<str>, U: AsRef<str>>(
local_codec.name()
);
let mut metadata = StreamMetadata::new(
avformat_context.input_format().determine_mime(local_codec.name())?,
duration,
codec_parameters.bit_rate() / 1000
);
let mut output_frame = AVFrame::new().map_err(|error| {
format_err!("Could not create output frame: {:?}", error)
})?;
......@@ -91,6 +101,7 @@ pub fn extract<T: AsRef<str>, U: AsRef<str>>(
if spritesheet_manager.fulfils_frame_interval(timestamp) {
if !spritesheet_manager.initialized() {
spritesheet_manager.initialize(frame.width() as u32, frame.height() as u32);
metadata.set_frame_size(frame.width(), frame.height());
output_frame.init(
spritesheet_manager.sprite_width() as i32,
spritesheet_manager.sprite_height() as i32,
......@@ -128,5 +139,11 @@ pub fn extract<T: AsRef<str>, U: AsRef<str>>(
spritesheet_manager.save()?;
}
metadata.save(
output_folder.join("metadata.json")
).map_err(|error| {
format_err!("Could not write stream metadata: {}", error)
})?;
Ok(())
}
\ No newline at end of file
File moved
use std::path::PathBuf;
use image::{RgbImage, ImageBuffer};
use failure::{bail, format_err};
......@@ -15,13 +17,13 @@ pub struct SpritesheetManager {
last_timestamp: MediaTime,
frame_interval: MediaTime,
metadata: WebVTTFile,
output_path: std::string::String,
output_path: PathBuf,
name: std::string::String,
initialized: bool,
}
impl SpritesheetManager {
pub fn new<T: AsRef<str>, U: AsRef<str>>(max_side: u32, num_horizontal: u32, num_vertical: u32, frame_interval: MediaTime, output_path: T, name: U) -> SpritesheetManager {
pub fn new<T: AsRef<str>, U: Into<PathBuf>>(max_side: u32, num_horizontal: u32, num_vertical: u32, frame_interval: MediaTime, output_path: U, name: T) -> SpritesheetManager {
SpritesheetManager {
num_horizontal,
num_vertical,
......@@ -33,7 +35,7 @@ impl SpritesheetManager {
last_timestamp: MediaTime::from_millis(0),
frame_interval,
metadata: WebVTTFile::new(),
output_path: std::string::String::from(output_path.as_ref()),
output_path: output_path.into(),
name: std::string::String::from(name.as_ref()),
initialized: false,
}
......@@ -141,7 +143,7 @@ impl SpritesheetManager {
fn save_spritesheet(&mut self) -> Result<(), failure::Error> {
self.spritesheet.save(
format!("{}/{}_{}.jpg", self.output_path, self.name, self.spritesheet_index(self.current_image))
self.output_path.join(format!("{}_{}.jpg", self.name, self.spritesheet_index(self.current_image)))
).map_err(|error| {
format_err!("Could not write spritesheet: {}", error)
})?;
......@@ -151,7 +153,9 @@ impl SpritesheetManager {
pub fn save(&mut self) -> Result<(), failure::Error> {
self.save_spritesheet()?;
self.metadata.save(format!("{}/{}.vtt", self.output_path, self.name)).map_err(|error| {
self.metadata.save(
self.output_path.join(format!("{}.vtt", self.name))
).map_err(|error| {
format_err!("Could not write spritesheet metadata: {}", error)
})?;
Ok(())
......
#![allow(dead_code)]
pub(crate) mod ffmpeg_api;
pub(crate) mod thumbnail;
pub(crate) mod ingest;
pub(crate) mod util;
use crate::util::media_time::MediaTime;
use std::path::Path;
fn main() -> Result<(), failure::Error> {
thumbnail::extract::extract(
ingest::extract::extract(
160, 5, 5,
MediaTime::from_seconds(2),
"/home/janne/Workspace/justflix/data/video.mp4",
"/home/janne/Workspace/justflix/data/spritesheets"
Path::new("/home/kuschku/Workspace/projects/mediaflix/data/movie.mp4"),
Path::new("/home/kuschku/Workspace/projects/mediaflix/data/output")
)?;
Ok(())
......
......@@ -14,21 +14,29 @@ impl MediaTime {
})?;
Ok(MediaTime(time::Duration::milliseconds(
1000 * timestamp * num as i64 / den as i64
(1000 * timestamp as i128 * num as i128 / den as i128) as i64
)))
}
#[inline(always)]
pub fn from_millis(timestamp: i64) -> MediaTime {
MediaTime(time::Duration::milliseconds(timestamp))
}
#[inline(always)]
pub fn from_seconds(timestamp: i64) -> MediaTime {
MediaTime(time::Duration::seconds(timestamp))
}
#[inline(always)]
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
#[inline(always)]
pub fn seconds(&self) -> i64 {
self.0.whole_seconds()
}
}
impl std::fmt::Display for MediaTime {
......
pub(crate) mod webvtt;
pub(crate) mod media_time;
pub(crate) mod stream_metadata;
pub(crate) mod webvtt;
use std::fs::File;
use std::path::Path;
use std::io::BufWriter;
use serde::{Deserialize, Serialize};
use crate::util::media_time::MediaTime;
#[derive(Serialize, Deserialize)]
pub struct StreamMetadata {
content_type: String,
duration: i64,
bitrate: i64,
aspect_ratio: f32,
width: i32,
height: i32,
}
impl StreamMetadata {
pub fn new<T: AsRef<str>>(content_type: T, duration: MediaTime, bitrate: i64) -> StreamMetadata {
StreamMetadata {
content_type: String::from(content_type.as_ref()),
duration: duration.seconds(),
bitrate,
aspect_ratio: 0.0,
width: 0,
height: 0,
}
}
pub fn set_frame_size(&mut self, width: i32, height: i32) {
self.width = width;
self.height = height;
self.aspect_ratio = (width as f64 / height as f64) as f32;
}
pub fn save<T: AsRef<Path>>(&self, path: T) -> Result<(), std::io::Error> {
serde_json::to_writer(BufWriter::new(File::create(path)?), self)?;
Ok(())
}
}
\ No newline at end of file
use std::fs::File;
use std::io::prelude::*;
use std::io::LineWriter;
use std::path::Path;
use std::string::String;
use crate::util::media_time::MediaTime;
......@@ -11,7 +13,7 @@ pub struct WebVTTFile {
pub struct WebVTTCue {
start: MediaTime,
end: MediaTime,
payload: std::string::String,
payload: String,
}
impl WebVTTFile {
......@@ -25,7 +27,7 @@ impl WebVTTFile {
self.cues.push(cue);
}
pub fn save(&self, path: std::string::String) -> Result<(), std::io::Error> {
pub fn save<T: AsRef<Path>>(&self, path: T) -> Result<(), std::io::Error> {
let file = File::create(path)?;
let mut file = LineWriter::new(file);
file.write_all(b"WEBVTT\n\n")?;
......@@ -38,7 +40,7 @@ impl WebVTTFile {
}
impl WebVTTCue {
pub fn new(start: MediaTime, end: MediaTime, payload: std::string::String) -> WebVTTCue {
pub fn new(start: MediaTime, end: MediaTime, payload: String) -> WebVTTCue {
WebVTTCue { start, end, payload }
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment