scuffle_ffmpeg/io/
output.rsuse ffmpeg_sys_next::*;
use super::internal::{seek, write_packet, Inner, InnerOptions};
use crate::consts::DEFAULT_BUFFER_SIZE;
use crate::dict::Dictionary;
use crate::error::FfmpegError;
use crate::packet::Packet;
use crate::stream::Stream;
#[derive(Debug, Clone)]
pub struct OutputOptions<'a> {
pub buffer_size: usize,
pub format_name: Option<&'a str>,
pub format_mime_type: Option<&'a str>,
pub format_ffi: *const AVOutputFormat,
}
impl<'a> OutputOptions<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn buffer_size(mut self, buffer_size: usize) -> Self {
self.buffer_size = buffer_size;
self
}
pub fn format_name(mut self, format_name: &'a str) -> Self {
self.format_name = format_name.into();
self
}
pub fn format_mime_type(mut self, format_mime_type: &'a str) -> Self {
self.format_mime_type = format_mime_type.into();
self
}
pub fn format_ffi(mut self, format_ffi: *const AVOutputFormat) -> Self {
self.format_ffi = format_ffi;
self
}
fn get_format_ffi(&self) -> Result<*const AVOutputFormat, FfmpegError> {
if !self.format_ffi.is_null() {
return Ok(self.format_ffi);
}
if self.format_name.is_none() && self.format_mime_type.is_none() {
return Err(FfmpegError::Arguments(
"format_ffi, format_name and format_mime_type cannot all be unset",
));
}
let c_format_name = self.format_name.map(|s| std::ffi::CString::new(s).unwrap());
let c_format_mime_type = self.format_mime_type.map(|s| std::ffi::CString::new(s).unwrap());
let c_format_name_ptr = c_format_name.as_ref().map(|s| s.as_ptr()).unwrap_or(std::ptr::null());
let c_format_mime_type_ptr = c_format_mime_type.as_ref().map(|s| s.as_ptr()).unwrap_or(std::ptr::null());
let output_format = unsafe { av_guess_format(c_format_name_ptr, std::ptr::null(), c_format_mime_type_ptr) };
if output_format.is_null() {
return Err(FfmpegError::Arguments("could not determine output format"));
}
Ok(output_format)
}
}
impl Default for OutputOptions<'_> {
fn default() -> Self {
Self {
buffer_size: DEFAULT_BUFFER_SIZE,
format_name: None,
format_mime_type: None,
format_ffi: std::ptr::null(),
}
}
}
pub struct Output<T: Send + Sync> {
inner: Inner<T>,
witten_header: bool,
}
unsafe impl<T: Send + Sync> Send for Output<T> {}
impl<T: Send + Sync> Output<T> {
pub fn into_inner(mut self) -> T {
*(self.inner.data.take().unwrap())
}
}
impl<T: std::io::Write + Send + Sync> Output<T> {
pub fn new(input: T, options: OutputOptions) -> Result<Self, FfmpegError> {
let output_format = options.get_format_ffi()?;
Ok(Self {
inner: Inner::new(
input,
InnerOptions {
buffer_size: options.buffer_size,
write_fn: Some(write_packet::<T>),
output_format,
..Default::default()
},
)?,
witten_header: false,
})
}
pub fn seekable(input: T, options: OutputOptions) -> Result<Self, FfmpegError>
where
T: std::io::Seek,
{
let output_format = options.get_format_ffi()?;
Ok(Self {
inner: Inner::new(
input,
InnerOptions {
buffer_size: options.buffer_size,
write_fn: Some(write_packet::<T>),
seek_fn: Some(seek::<T>),
output_format,
..Default::default()
},
)?,
witten_header: false,
})
}
}
impl<T: Send + Sync> Output<T> {
pub fn set_metadata(&mut self, metadata: Dictionary) {
unsafe {
Dictionary::from_ptr_owned(self.inner.context.as_deref_mut_except().metadata);
self.inner.context.as_deref_mut_except().metadata = metadata.into_ptr();
};
}
pub fn as_ptr(&self) -> *const AVFormatContext {
self.inner.context.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
self.inner.context.as_mut_ptr()
}
pub fn add_stream(&mut self, codec: Option<*const AVCodec>) -> Option<Stream<'_>> {
let stream = unsafe { avformat_new_stream(self.as_mut_ptr(), codec.unwrap_or_else(std::ptr::null)) };
if stream.is_null() {
None
} else {
unsafe {
let stream = &mut *stream;
stream.id = self.inner.context.as_deref_except().nb_streams as i32 - 1;
Some(Stream::new(stream, self.inner.context.as_deref_except()))
}
}
}
pub fn copy_stream<'a>(&'a mut self, stream: &Stream<'_>) -> Option<Stream<'a>> {
let codec_param = stream.codec_parameters()?;
let out_stream = unsafe { avformat_new_stream(self.as_mut_ptr(), std::ptr::null()) };
if out_stream.is_null() {
None
} else {
unsafe {
let out_stream = &mut *out_stream;
avcodec_parameters_copy(out_stream.codecpar, codec_param);
out_stream.id = self.inner.context.as_deref_except().nb_streams as i32 - 1;
let mut out_stream = Stream::new(out_stream, self.inner.context.as_deref_except());
out_stream.set_time_base(stream.time_base());
out_stream.set_start_time(stream.start_time());
out_stream.set_duration(stream.duration());
Some(out_stream)
}
}
}
pub fn write_header(&mut self) -> Result<(), FfmpegError> {
if self.witten_header {
return Err(FfmpegError::Arguments("header already written"));
}
unsafe {
match avformat_write_header(self.as_mut_ptr(), std::ptr::null_mut()) {
0 => Ok(()),
e => Err(FfmpegError::Code(e.into())),
}
}?;
self.witten_header = true;
Ok(())
}
pub fn write_header_with_options(&mut self, options: &mut Dictionary) -> Result<(), FfmpegError> {
if self.witten_header {
return Err(FfmpegError::Arguments("header already written"));
}
unsafe {
match avformat_write_header(self.as_mut_ptr(), options.as_mut_ptr_ref()) {
0 => Ok(()),
e => Err(FfmpegError::Code(e.into())),
}
}?;
self.witten_header = true;
Ok(())
}
pub fn write_trailer(&mut self) -> Result<(), FfmpegError> {
if !self.witten_header {
return Err(FfmpegError::Arguments("header not written"));
}
unsafe {
match av_write_trailer(self.as_mut_ptr()) {
n if n >= 0 => Ok(()),
e => Err(FfmpegError::Code(e.into())),
}
}
}
pub fn write_interleaved_packet(&mut self, mut packet: Packet) -> Result<(), FfmpegError> {
if !self.witten_header {
return Err(FfmpegError::Arguments("header not written"));
}
unsafe {
match av_interleaved_write_frame(self.as_mut_ptr(), packet.as_mut_ptr()) {
0 => Ok(()),
e => Err(FfmpegError::Code(e.into())),
}
}
}
pub fn write_packet(&mut self, packet: &Packet) -> Result<(), FfmpegError> {
if !self.witten_header {
return Err(FfmpegError::Arguments("header not written"));
}
unsafe {
match av_write_frame(self.as_mut_ptr(), packet.as_ptr() as *mut _) {
0 => Ok(()),
e => Err(FfmpegError::Code(e.into())),
}
}
}
pub fn flags(&self) -> i32 {
self.inner.context.as_deref_except().flags
}
}
impl Output<()> {
pub fn open(path: &str) -> Result<Self, FfmpegError> {
Ok(Self {
inner: Inner::open_output(path)?,
witten_header: false,
})
}
}