diff --git a/examples/chapters.rs b/examples/chapters.rs new file mode 100644 index 0000000..97aa643 --- /dev/null +++ b/examples/chapters.rs @@ -0,0 +1,52 @@ +extern crate ffmpeg; + +use std::env; + +fn main() { + ffmpeg::init().unwrap(); + + match ffmpeg::format::input(&env::args().nth(1).expect("missing input file name")) { + Ok(ictx) => { + println!("Nb chapters: {}", ictx.nb_chapters()); + + for chapter in ictx.chapters() { + println!("chapter id {}:", chapter.id()); + println!("\ttime_base: {}", chapter.time_base()); + println!("\tstart: {}", chapter.start()); + println!("\tend: {}", chapter.end()); + + for (k, v) in chapter.metadata().iter() { + println!("\t{}: {}", k, v); + } + } + + let mut octx = ffmpeg::format::output(&"test.mkv".to_owned()).expect(&format!("Couldn't open test file")); + + for chapter in ictx.chapters() { + let title = match chapter.metadata().get("title") { + Some(title) => String::from(title), + None => String::new(), + }; + + match octx.add_chapter(chapter.id(), chapter.time_base(), chapter.start(), chapter.end(), &title) { + Ok(chapter) => println!("Added chapter with id {} to output", chapter.id()), + Err(error) => println!("Error adding chapter with id: {} - {}", chapter.id(), error), + } + } + + println!("\nOuput: nb chapters: {}", octx.nb_chapters()); + for chapter in octx.chapters() { + println!("chapter id {}:", chapter.id()); + println!("\ttime_base: {}", chapter.time_base()); + println!("\tstart: {}", chapter.start()); + println!("\tend: {}", chapter.end()); + for (k, v) in chapter.metadata().iter() { + println!("\t{}: {}", k, v); + } + } + } + + Err(error) => + println!("error: {}", error) + } +} diff --git a/src/format/chapter/chapter.rs b/src/format/chapter/chapter.rs new file mode 100644 index 0000000..ad1dc0d --- /dev/null +++ b/src/format/chapter/chapter.rs @@ -0,0 +1,65 @@ +use ffi::*; +use ::{Rational, DictionaryRef}; + +use format::context::common::Context; + +// WARNING: index refers to the offset in the chapters array (starting from 0) +// it is not necessarly equal to the id (which may start at 1) +pub struct Chapter<'a> { + context: &'a Context, + index: usize, +} + +impl<'a> Chapter<'a> { + pub unsafe fn wrap(context: &Context, index: usize) -> Chapter { + Chapter { context: context, index: index } + } + + pub unsafe fn as_ptr(&self) -> *const AVChapter { + *(*self.context.as_ptr()).chapters.offset(self.index as isize) + } +} + +impl<'a> Chapter<'a> { + pub fn index(&self) -> usize { + self.index + } + + pub fn id(&self) -> i32 { + unsafe { + (*self.as_ptr()).id + } + } + + pub fn time_base(&self) -> Rational { + unsafe { + Rational::from((*self.as_ptr()).time_base) + } + } + + pub fn start(&self) -> i64 { + unsafe { + (*self.as_ptr()).start + } + } + + pub fn end(&self) -> i64 { + unsafe { + (*self.as_ptr()).end + } + } + + pub fn metadata(&self) -> DictionaryRef { + unsafe { + DictionaryRef::wrap((*self.as_ptr()).metadata) + } + } +} + +impl<'a> PartialEq for Chapter<'a> { + fn eq(&self, other: &Self) -> bool { + unsafe { + self.as_ptr() == other.as_ptr() + } + } +} diff --git a/src/format/chapter/chapter_mut.rs b/src/format/chapter/chapter_mut.rs new file mode 100644 index 0000000..9b06af3 --- /dev/null +++ b/src/format/chapter/chapter_mut.rs @@ -0,0 +1,81 @@ +use std::ops::Deref; +use std::mem; + +use ffi::*; +use ::{Rational, Dictionary, DictionaryMut}; +use super::Chapter; +use format::context::common::Context; + +// WARNING: index refers to the offset in the chapters array (starting from 0) +// it is not necessarly equal to the id (which may start at 1) +pub struct ChapterMut<'a> { + context: &'a mut Context, + index: usize, + + immutable: Chapter<'a>, +} + +impl<'a> ChapterMut<'a> { + pub unsafe fn wrap(context: &mut Context, index: usize) -> ChapterMut { + ChapterMut { + context: mem::transmute_copy(&context), + index: index, + + immutable: Chapter::wrap(mem::transmute_copy(&context), index) + } + } + + pub unsafe fn as_mut_ptr(&mut self) -> *mut AVChapter { + *(*self.context.as_mut_ptr()).chapters.offset(self.index as isize) + } +} + +impl<'a> ChapterMut<'a> { + pub fn set_id(&mut self, value: i32) { + unsafe { + (*self.as_mut_ptr()).id = value; + } + } + + pub fn set_time_base>(&mut self, value: R) { + unsafe { + (*self.as_mut_ptr()).time_base = value.into().into(); + } + } + + pub fn set_start(&mut self, value: i64) { + unsafe { + (*self.as_mut_ptr()).start = value; + } + } + + pub fn set_end(&mut self, value: i64) { + unsafe { + (*self.as_mut_ptr()).end = value; + } + } + + pub fn set_metadata, V: AsRef>(&mut self, key: K, value: V) { + // dictionary.set() allocates the AVDictionary the first time a key/value is inserted + // so we want to update the metadata dictionary afterwards + unsafe { + let mut dictionary = Dictionary::own(self.metadata().as_mut_ptr()); + dictionary.set(key.as_ref(), value.as_ref()); + (*self.as_mut_ptr()).metadata = dictionary.disown(); + } + } + + pub fn metadata(&mut self) -> DictionaryMut { + unsafe { + DictionaryMut::wrap((*self.as_mut_ptr()).metadata) + } + } +} + +impl<'a> Deref for ChapterMut<'a> { + type Target = Chapter<'a>; + + fn deref(&self) -> &Self::Target { + &self.immutable + } +} diff --git a/src/format/chapter/mod.rs b/src/format/chapter/mod.rs new file mode 100644 index 0000000..9136f08 --- /dev/null +++ b/src/format/chapter/mod.rs @@ -0,0 +1,5 @@ +mod chapter; +pub use self::chapter::Chapter; + +mod chapter_mut; +pub use self::chapter_mut::ChapterMut; diff --git a/src/format/context/common.rs b/src/format/context/common.rs index dae3e9a..52ded27 100644 --- a/src/format/context/common.rs +++ b/src/format/context/common.rs @@ -4,7 +4,7 @@ use std::mem; use ffi::*; use libc::{c_int, c_uint}; -use ::{media, Stream, StreamMut, DictionaryRef}; +use ::{media, Stream, StreamMut, Chapter, ChapterMut, DictionaryRef}; use super::destructor::{self, Destructor}; pub struct Context { @@ -69,6 +69,42 @@ impl Context { } } + pub fn nb_chapters(&self) -> u32 { + unsafe { + (*self.as_ptr()).nb_chapters + } + } + + pub fn chapter<'a, 'b>(&'a self, index: usize) -> Option> where 'a: 'b { + unsafe { + if index >= (*self.as_ptr()).nb_chapters as usize { + None + } + else { + Some(Chapter::wrap(self, index)) + } + } + } + + pub fn chapter_mut<'a, 'b>(&'a mut self, index: usize) -> Option> where 'a: 'b { + unsafe { + if index >= (*self.as_ptr()).nb_chapters as usize { + None + } + else { + Some(ChapterMut::wrap(self, index)) + } + } + } + + pub fn chapters(&self) -> ChapterIter { + ChapterIter::new(self) + } + + pub fn chapters_mut(&mut self) -> ChapterIterMut { + ChapterIterMut::new(self) + } + pub fn metadata(&self) -> DictionaryRef { unsafe { DictionaryRef::wrap((*self.as_ptr()).metadata) @@ -213,3 +249,77 @@ impl<'a> Iterator for StreamIterMut<'a> { } impl<'a> ExactSizeIterator for StreamIterMut<'a> { } + +pub struct ChapterIter<'a> { + context: &'a Context, + current: c_uint, +} + +impl<'a> ChapterIter<'a> { + pub fn new<'s, 'c: 's>(context: &'c Context) -> ChapterIter<'s> { + ChapterIter { context: context, current: 0 } + } +} + +impl<'a> Iterator for ChapterIter<'a> { + type Item = Chapter<'a>; + + fn next(&mut self) -> Option<::Item> { + unsafe { + if self.current >= (*self.context.as_ptr()).nb_chapters { + return None; + } + + self.current += 1; + + Some(Chapter::wrap(self.context, (self.current - 1) as usize)) + } + } + + fn size_hint(&self) -> (usize, Option) { + unsafe { + let length = (*self.context.as_ptr()).nb_chapters as usize; + + (length - self.current as usize, Some(length - self.current as usize)) + } + } +} + +impl<'a> ExactSizeIterator for ChapterIter<'a> { } + +pub struct ChapterIterMut<'a> { + context: &'a mut Context, + current: c_uint, +} + +impl<'a> ChapterIterMut<'a> { + pub fn new<'s, 'c: 's>(context: &'c mut Context) -> ChapterIterMut<'s> { + ChapterIterMut { context: context, current: 0 } + } +} + +impl<'a> Iterator for ChapterIterMut<'a> { + type Item = ChapterMut<'a>; + + fn next(&mut self) -> Option<::Item> { + unsafe { + if self.current >= (*self.context.as_ptr()).nb_chapters { + return None + } + + self.current += 1; + + Some(ChapterMut::wrap(mem::transmute_copy(&self.context), (self.current - 1) as usize)) + } + } + + fn size_hint(&self) -> (usize, Option) { + unsafe { + let length = (*self.context.as_ptr()).nb_chapters as usize; + + (length - self.current as usize, Some(length - self.current as usize)) + } + } +} + +impl<'a> ExactSizeIterator for ChapterIterMut<'a> { } diff --git a/src/format/context/output.rs b/src/format/context/output.rs index 45bb794..906af67 100644 --- a/src/format/context/output.rs +++ b/src/format/context/output.rs @@ -1,9 +1,12 @@ use std::ops::{Deref, DerefMut}; use std::ptr; +use std::mem::size_of; use std::ffi::CString; +use libc; + use ffi::*; -use ::{Error, StreamMut, Dictionary, format}; +use ::{Error, StreamMut, ChapterMut, Rational, Dictionary, format}; use super::common::Context; use super::destructor; use codec::traits; @@ -81,6 +84,65 @@ impl Output { } } + pub fn add_chapter, S: AsRef>(&mut self, + id: i32, + time_base: R, + start: i64, + end: i64, + title: S) -> Result + { + // avpriv_new_chapter is private (libavformat/internal.h) + + if start > end { + return Err(Error::InvalidData); + } + + let mut existing = None; + for chapter in self.chapters() { + if chapter.id() == id { + existing = Some(chapter.index()); + break; + } + } + + let index = match existing { + Some(index) => index, + None => unsafe { + let ptr = av_mallocz(size_of::()).as_mut().ok_or(Error::Bug)?; + let mut nb_chapters = (*self.as_ptr()).nb_chapters as i32; + + // chapters array will be freed by `avformat_free_context` + av_dynarray_add( + &mut (*self.as_mut_ptr()).chapters as *mut _ as *mut libc::c_void, + &mut nb_chapters, + ptr + ); + + if nb_chapters > 0 { + (*self.as_mut_ptr()).nb_chapters = nb_chapters as u32; + let index = (*self.ctx.as_ptr()).nb_chapters - 1; + index as usize + } + else { + // failed to add the chapter + av_freep(ptr); + return Err(Error::Bug); + } + }, + }; + + let mut chapter = self.chapter_mut(index) + .ok_or(Error::Bug)?; + + chapter.set_id(id); + chapter.set_time_base(time_base); + chapter.set_start(start); + chapter.set_end(end); + chapter.set_metadata("title", title); + + Ok(chapter) + } + pub fn set_metadata(&mut self, dictionary: Dictionary) { unsafe { (*self.as_mut_ptr()).metadata = dictionary.disown(); diff --git a/src/format/mod.rs b/src/format/mod.rs index b300c0d..914ccb0 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -4,6 +4,8 @@ use ::util::interrupt; pub mod stream; +pub mod chapter; + pub mod context; pub use self::context::Context; diff --git a/src/lib.rs b/src/lib.rs index 7b3a38d..c5190c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,8 @@ pub mod format; pub use format::format::Format; #[cfg(feature = "format")] pub use format::stream::{Stream, StreamMut}; +#[cfg(feature = "format")] +pub use format::chapter::{Chapter, ChapterMut}; #[cfg(feature = "codec")] pub mod codec;