format: add support for Chapters
Read chapters from an input context and add chapters to an output context. Note: unlike avformat_new_stream, the equivalent function for chapter is private: avpriv_new_chapter (part of libavformat/internal.h). I couldn't find any other solution but re-implementing it in format::context::output::add_chapter.
This commit is contained in:
parent
d29deedad9
commit
28b7a82ac1
52
examples/chapters.rs
Normal file
52
examples/chapters.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
65
src/format/chapter/chapter.rs
Normal file
65
src/format/chapter/chapter.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
81
src/format/chapter/chapter_mut.rs
Normal file
81
src/format/chapter/chapter_mut.rs
Normal file
@ -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<R: Into<Rational>>(&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<K: AsRef<str>, V: AsRef<str>>(&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
|
||||
}
|
||||
}
|
5
src/format/chapter/mod.rs
Normal file
5
src/format/chapter/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod chapter;
|
||||
pub use self::chapter::Chapter;
|
||||
|
||||
mod chapter_mut;
|
||||
pub use self::chapter_mut::ChapterMut;
|
@ -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<Chapter<'b>> 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<ChapterMut<'b>> 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<<Self as Iterator>::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<usize>) {
|
||||
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<<Self as Iterator>::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<usize>) {
|
||||
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> { }
|
||||
|
@ -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<R: Into<Rational>, S: AsRef<str>>(&mut self,
|
||||
id: i32,
|
||||
time_base: R,
|
||||
start: i64,
|
||||
end: i64,
|
||||
title: S) -> Result<ChapterMut, Error>
|
||||
{
|
||||
// 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::<AVChapter>()).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();
|
||||
|
@ -4,6 +4,8 @@ use ::util::interrupt;
|
||||
|
||||
pub mod stream;
|
||||
|
||||
pub mod chapter;
|
||||
|
||||
pub mod context;
|
||||
pub use self::context::Context;
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user