playa_ffmpeg/format/
mod.rs

1//! Container format support for muxing and demuxing.
2//!
3//! This module provides functionality for reading (demuxing) and writing (muxing) multimedia
4//! container formats. It wraps FFmpeg's `libavformat` library with safe Rust interfaces.
5//!
6//! # Main Components
7//!
8//! - [`Context`] - Format context managing streams and container metadata
9//! - [`stream`] - Individual media streams within a container
10//! - [`chapter`] - Chapter/bookmark support for seekable formats
11//! - [`mod@format`] - Container format information and discovery
12//!
13//! # Common Operations
14//!
15//! ## Opening Files for Reading
16//!
17//! Use [`input()`] to open a media file for reading:
18//!
19//! ```ignore
20//! let mut input = ffmpeg::format::input(&"video.mp4")?;
21//!
22//! // Find best video stream
23//! let stream = input.streams().best(Type::Video).unwrap();
24//! let decoder = stream.codec().decoder().video()?;
25//! ```
26//!
27//! ## Opening Files for Writing
28//!
29//! Use [`output()`] to create a new media file:
30//!
31//! ```ignore
32//! let mut output = ffmpeg::format::output(&"output.mp4")?;
33//!
34//! // Add video stream
35//! let mut stream = output.add_stream(encoder)?;
36//! stream.set_parameters(&encoder);
37//!
38//! output.write_header()?;
39//! // ... write packets ...
40//! output.write_trailer()?;
41//! ```
42
43pub use crate::util::format::{Pixel, Sample, pixel, sample};
44use crate::util::interrupt;
45
46pub mod stream;
47
48pub mod chapter;
49
50pub mod context;
51pub use self::context::Context;
52
53pub mod format;
54#[cfg(not(feature = "ffmpeg_5_0"))]
55pub use self::format::list;
56pub use self::format::{Flags, Input, Output, flag};
57
58pub mod network;
59
60use std::{
61    ffi::{CStr, CString},
62    path::Path,
63    ptr,
64    str::from_utf8_unchecked,
65};
66
67use crate::{Dictionary, Error, Format, ffi::*};
68
69/// Registers all muxers and demuxers (FFmpeg < 5.0 only).
70///
71/// In FFmpeg 5.0+, formats are automatically registered and this is a no-op.
72/// Called automatically by [`crate::init()`].
73#[cfg(not(feature = "ffmpeg_5_0"))]
74pub fn register_all() {
75    unsafe {
76        av_register_all();
77    }
78}
79
80/// Registers a specific format (FFmpeg < 5.0 only).
81///
82/// In FFmpeg 5.0+, this is a no-op. Most users should rely on [`register_all()`]
83/// or automatic registration instead of manually registering formats.
84#[cfg(not(feature = "ffmpeg_5_0"))]
85pub fn register(format: &Format) {
86    match *format {
87        Format::Input(ref format) => unsafe {
88            av_register_input_format(format.as_ptr() as *mut _);
89        },
90
91        Format::Output(ref format) => unsafe {
92            av_register_output_format(format.as_ptr() as *mut _);
93        },
94    }
95}
96
97/// Returns the libavformat version number.
98///
99/// The version is encoded as `(major << 16) | (minor << 8) | micro`.
100pub fn version() -> u32 {
101    unsafe { avformat_version() }
102}
103
104/// Returns the libavformat build configuration string.
105///
106/// Shows compile-time options used when building FFmpeg's format library.
107pub fn configuration() -> &'static str {
108    unsafe { from_utf8_unchecked(CStr::from_ptr(avformat_configuration()).to_bytes()) }
109}
110
111/// Returns the libavformat license string.
112///
113/// Typically "LGPL version 2.1 or later" unless built with GPL components.
114pub fn license() -> &'static str {
115    unsafe { from_utf8_unchecked(CStr::from_ptr(avformat_license()).to_bytes()) }
116}
117
118/// Converts a path to a C string for FFmpeg API calls.
119///
120/// # Panics
121///
122/// Panics if the path contains invalid UTF-8 or null bytes.
123// XXX: use to_cstring when stable
124fn from_path<P: AsRef<Path> + ?Sized>(path: &P) -> CString {
125    CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
126}
127
128/// Opens a file with a specific format (input or output).
129///
130/// Prefer [`input()`] or [`output()`] unless you need format override.
131///
132/// # Parameters
133///
134/// * `path` - File path to open
135/// * `format` - Format to use (Input for reading, Output for writing)
136///
137/// # Errors
138///
139/// Returns an error if the file cannot be opened or the format is unsupported.
140///
141/// # Note
142///
143/// For input contexts, this automatically probes stream information after opening.
144// NOTE: this will be better with specialization or anonymous return types
145pub fn open<P: AsRef<Path> + ?Sized>(path: &P, format: &Format) -> Result<Context, Error> {
146    unsafe {
147        let mut ps = ptr::null_mut();
148        let path = from_path(path);
149
150        match *format {
151            Format::Input(ref format) => match avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr() as *mut _, ptr::null_mut()) {
152                0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
153                    r if r >= 0 => Ok(Context::Input(context::Input::wrap(ps))),
154                    e => Err(Error::from(e)),
155                },
156
157                e => Err(Error::from(e)),
158            },
159
160            Format::Output(ref format) => match avformat_alloc_output_context2(&mut ps, format.as_ptr() as *mut _, ptr::null(), path.as_ptr()) {
161                0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
162                    0 => Ok(Context::Output(context::Output::wrap(ps))),
163                    e => Err(Error::from(e)),
164                },
165
166                e => Err(Error::from(e)),
167            },
168        }
169    }
170}
171
172/// Opens a file with a specific format and options dictionary.
173///
174/// Like [`open()`] but allows passing codec/format options.
175///
176/// # Parameters
177///
178/// * `path` - File path to open
179/// * `format` - Format to use
180/// * `options` - Dictionary of format-specific options
181pub fn open_with<P: AsRef<Path> + ?Sized>(path: &P, format: &Format, options: Dictionary) -> Result<Context, Error> {
182    unsafe {
183        let mut ps = ptr::null_mut();
184        let path = from_path(path);
185        let mut opts = options.disown();
186
187        match *format {
188            Format::Input(ref format) => {
189                let res = avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr() as *mut _, &mut opts);
190
191                Dictionary::own(opts);
192
193                match res {
194                    0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
195                        r if r >= 0 => Ok(Context::Input(context::Input::wrap(ps))),
196                        e => Err(Error::from(e)),
197                    },
198
199                    e => Err(Error::from(e)),
200                }
201            }
202
203            Format::Output(ref format) => match avformat_alloc_output_context2(&mut ps, format.as_ptr() as *mut _, ptr::null(), path.as_ptr()) {
204                0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
205                    0 => Ok(Context::Output(context::Output::wrap(ps))),
206                    e => Err(Error::from(e)),
207                },
208
209                e => Err(Error::from(e)),
210            },
211        }
212    }
213}
214
215/// Opens a media file for reading (demuxing).
216///
217/// This is the primary function for opening input files. It automatically detects the
218/// container format and probes all streams to gather codec information.
219///
220/// # Parameters
221///
222/// * `path` - Path to the media file (supports various protocols: file://, http://, rtsp://, etc.)
223///
224/// # Returns
225///
226/// An [`context::Input`] that can be used to access streams and read packets.
227///
228/// # Errors
229///
230/// - File not found or inaccessible
231/// - Unsupported or corrupted format
232/// - Permission denied
233///
234/// # Example
235///
236/// ```ignore
237/// let mut input = ffmpeg::format::input(&"video.mp4")?;
238///
239/// // Find the best video stream
240/// let stream = input.streams().best(Type::Video).ok_or(Error::StreamNotFound)?;
241/// let stream_index = stream.index();
242///
243/// // Create decoder for this stream
244/// let decoder = stream.codec().decoder().video()?;
245/// ```
246pub fn input<P: AsRef<Path> + ?Sized>(path: &P) -> Result<context::Input, Error> {
247    unsafe {
248        let mut ps = ptr::null_mut();
249        let path = from_path(path);
250
251        match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut()) {
252            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
253                r if r >= 0 => Ok(context::Input::wrap(ps)),
254                e => {
255                    avformat_close_input(&mut ps);
256                    Err(Error::from(e))
257                }
258            },
259
260            e => Err(Error::from(e)),
261        }
262    }
263}
264
265/// Opens a media file for reading with options dictionary.
266///
267/// Like [`input()`] but allows passing format-specific options (e.g., timeouts,
268/// buffer sizes, protocol options).
269///
270/// # Parameters
271///
272/// * `path` - Path to the media file
273/// * `options` - Dictionary of format/protocol options
274pub fn input_with_dictionary<P: AsRef<Path> + ?Sized>(path: &P, options: Dictionary) -> Result<context::Input, Error> {
275    unsafe {
276        let mut ps = ptr::null_mut();
277        let path = from_path(path);
278        let mut opts = options.disown();
279        let res = avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), &mut opts);
280
281        Dictionary::own(opts);
282
283        match res {
284            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
285                r if r >= 0 => Ok(context::Input::wrap(ps)),
286                e => {
287                    avformat_close_input(&mut ps);
288                    Err(Error::from(e))
289                }
290            },
291
292            e => Err(Error::from(e)),
293        }
294    }
295}
296
297/// Opens a media file for reading with interrupt callback.
298///
299/// Allows cancellation of long-running operations (network streams, slow I/O).
300/// The callback is called periodically; returning `true` aborts the operation.
301///
302/// # Parameters
303///
304/// * `path` - Path to the media file
305/// * `closure` - Callback invoked periodically, return `true` to abort
306///
307/// # Example
308///
309/// ```ignore
310/// use std::sync::atomic::{AtomicBool, Ordering};
311/// use std::sync::Arc;
312///
313/// let should_abort = Arc::new(AtomicBool::new(false));
314/// let abort_flag = should_abort.clone();
315///
316/// let input = ffmpeg::format::input_with_interrupt(&"http://stream.example.com/live", move || {
317///     abort_flag.load(Ordering::Relaxed)
318/// })?;
319/// ```
320pub fn input_with_interrupt<P: AsRef<Path> + ?Sized, F>(path: &P, closure: F) -> Result<context::Input, Error>
321where
322    F: FnMut() -> bool,
323{
324    unsafe {
325        let mut ps = avformat_alloc_context();
326        let path = from_path(path);
327        // Set interrupt callback for cancellation support
328        (*ps).interrupt_callback = interrupt::new(Box::new(closure)).interrupt;
329
330        match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut()) {
331            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
332                r if r >= 0 => Ok(context::Input::wrap(ps)),
333                e => {
334                    avformat_close_input(&mut ps);
335                    Err(Error::from(e))
336                }
337            },
338
339            e => Err(Error::from(e)),
340        }
341    }
342}
343
344/// Opens a media file for writing (muxing).
345///
346/// Creates a new output file with format auto-detected from the file extension.
347/// The file is created/truncated and ready for writing after adding streams.
348///
349/// # Parameters
350///
351/// * `path` - Path to the output file
352///
353/// # Returns
354///
355/// An [`context::Output`] that can be used to add streams and write packets.
356///
357/// # Errors
358///
359/// - File cannot be created (permission denied, invalid path)
360/// - Format cannot be determined from extension
361/// - Unsupported format
362///
363/// # Example
364///
365/// ```ignore
366/// let mut output = ffmpeg::format::output(&"output.mp4")?;
367///
368/// // Add video stream
369/// let mut stream = output.add_stream(encoder)?;
370/// stream.set_parameters(&encoder);
371///
372/// // Write header
373/// output.write_header()?;
374///
375/// // Write packets...
376/// output.write_packet(&packet)?;
377///
378/// // Finalize
379/// output.write_trailer()?;
380/// ```
381pub fn output<P: AsRef<Path> + ?Sized>(path: &P) -> Result<context::Output, Error> {
382    unsafe {
383        let mut ps = ptr::null_mut();
384        let path = from_path(path);
385
386        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr()) {
387            0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
388                0 => Ok(context::Output::wrap(ps)),
389                e => Err(Error::from(e)),
390            },
391
392            e => Err(Error::from(e)),
393        }
394    }
395}
396
397/// Opens a media file for writing with options dictionary.
398///
399/// Like [`output()`] but allows passing I/O and format options.
400///
401/// # Parameters
402///
403/// * `path` - Path to the output file
404/// * `options` - Dictionary of I/O and format options
405pub fn output_with<P: AsRef<Path> + ?Sized>(path: &P, options: Dictionary) -> Result<context::Output, Error> {
406    unsafe {
407        let mut ps = ptr::null_mut();
408        let path = from_path(path);
409        let mut opts = options.disown();
410
411        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr()) {
412            0 => {
413                let res = avio_open2(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE, ptr::null(), &mut opts);
414
415                Dictionary::own(opts);
416
417                match res {
418                    0 => Ok(context::Output::wrap(ps)),
419                    e => Err(Error::from(e)),
420                }
421            }
422
423            e => Err(Error::from(e)),
424        }
425    }
426}
427
428/// Opens a media file for writing with explicit format specification.
429///
430/// Use this when the file extension doesn't match the desired format
431/// or when writing to streams/pipes.
432///
433/// # Parameters
434///
435/// * `path` - Path to the output file
436/// * `format` - Format name (e.g., "mp4", "matroska", "mpeg")
437///
438/// # Example
439///
440/// ```ignore
441/// // Create MP4 file with .bin extension
442/// let output = ffmpeg::format::output_as(&"stream.bin", "mp4")?;
443/// ```
444pub fn output_as<P: AsRef<Path> + ?Sized>(path: &P, format: &str) -> Result<context::Output, Error> {
445    unsafe {
446        let mut ps = ptr::null_mut();
447        let path = from_path(path);
448        let format = CString::new(format).unwrap();
449
450        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), format.as_ptr(), path.as_ptr()) {
451            0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
452                0 => Ok(context::Output::wrap(ps)),
453                e => Err(Error::from(e)),
454            },
455
456            e => Err(Error::from(e)),
457        }
458    }
459}
460
461/// Opens a media file for writing with explicit format and options.
462///
463/// Combines [`output_as()`] with options dictionary support.
464///
465/// # Parameters
466///
467/// * `path` - Path to the output file
468/// * `format` - Format name
469/// * `options` - Dictionary of I/O and format options
470pub fn output_as_with<P: AsRef<Path> + ?Sized>(path: &P, format: &str, options: Dictionary) -> Result<context::Output, Error> {
471    unsafe {
472        let mut ps = ptr::null_mut();
473        let path = from_path(path);
474        let format = CString::new(format).unwrap();
475        let mut opts = options.disown();
476
477        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), format.as_ptr(), path.as_ptr()) {
478            0 => {
479                let res = avio_open2(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE, ptr::null(), &mut opts);
480
481                Dictionary::own(opts);
482
483                match res {
484                    0 => Ok(context::Output::wrap(ps)),
485                    e => Err(Error::from(e)),
486                }
487            }
488
489            e => Err(Error::from(e)),
490        }
491    }
492}