Skip to main content

ffmpeg_next/format/
mod.rs

1pub use crate::util::format::{Pixel, pixel};
2pub use crate::util::format::{Sample, sample};
3use crate::util::interrupt;
4
5pub mod stream;
6
7pub mod chapter;
8
9pub mod context;
10pub use self::context::Context;
11
12pub mod format;
13#[cfg(not(feature = "ffmpeg_5_0"))]
14pub use self::format::list;
15pub use self::format::{Flags, flag};
16pub use self::format::{Input, Output};
17
18pub mod network;
19
20use std::ffi::{CStr, CString};
21use std::path::Path;
22use std::ptr;
23use std::str::from_utf8_unchecked;
24
25use crate::ffi::*;
26use crate::{Dictionary, Error, Format};
27
28#[cfg(not(feature = "ffmpeg_5_0"))]
29pub fn register_all() {
30    unsafe {
31        av_register_all();
32    }
33}
34
35#[cfg(not(feature = "ffmpeg_5_0"))]
36pub fn register(format: &Format) {
37    match *format {
38        Format::Input(ref format) => unsafe {
39            av_register_input_format(format.as_ptr() as *mut _);
40        },
41
42        Format::Output(ref format) => unsafe {
43            av_register_output_format(format.as_ptr() as *mut _);
44        },
45    }
46}
47
48pub fn version() -> u32 {
49    unsafe { avformat_version() }
50}
51
52pub fn configuration() -> &'static str {
53    unsafe { from_utf8_unchecked(CStr::from_ptr(avformat_configuration()).to_bytes()) }
54}
55
56pub fn license() -> &'static str {
57    unsafe { from_utf8_unchecked(CStr::from_ptr(avformat_license()).to_bytes()) }
58}
59
60// XXX: use to_cstring when stable
61fn from_path<P: AsRef<Path> + ?Sized>(path: &P) -> CString {
62    CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
63}
64
65fn opt_cstring(s: Option<&str>) -> Result<Option<CString>, Error> {
66    s.map(CString::new)
67        .transpose()
68        .map_err(|_| Error::Other { errno: EINVAL })
69}
70
71// NOTE: this will be better with specialization or anonymous return types
72pub fn open<P: AsRef<Path> + ?Sized>(path: &P, format: &Format) -> Result<Context, Error> {
73    unsafe {
74        let mut ps = ptr::null_mut();
75        let path = from_path(path);
76
77        match *format {
78            Format::Input(ref format) => match avformat_open_input(
79                &mut ps,
80                path.as_ptr(),
81                format.as_ptr() as *mut _,
82                ptr::null_mut(),
83            ) {
84                0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
85                    r if r >= 0 => Ok(Context::Input(context::Input::wrap(ps))),
86                    e => Err(Error::from(e)),
87                },
88
89                e => Err(Error::from(e)),
90            },
91
92            Format::Output(ref format) => match avformat_alloc_output_context2(
93                &mut ps,
94                format.as_ptr() as *mut _,
95                ptr::null(),
96                path.as_ptr(),
97            ) {
98                0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
99                    0 => Ok(Context::Output(context::Output::wrap(ps))),
100                    e => Err(Error::from(e)),
101                },
102
103                e => Err(Error::from(e)),
104            },
105        }
106    }
107}
108
109pub fn open_with<P: AsRef<Path> + ?Sized>(
110    path: &P,
111    format: &Format,
112    options: Dictionary,
113) -> Result<Context, Error> {
114    unsafe {
115        let mut ps = ptr::null_mut();
116        let path = from_path(path);
117        let mut opts = options.disown();
118
119        match *format {
120            Format::Input(ref format) => {
121                let res = avformat_open_input(
122                    &mut ps,
123                    path.as_ptr(),
124                    format.as_ptr() as *mut _,
125                    &mut opts,
126                );
127
128                Dictionary::own(opts);
129
130                match res {
131                    0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
132                        r if r >= 0 => Ok(Context::Input(context::Input::wrap(ps))),
133                        e => Err(Error::from(e)),
134                    },
135
136                    e => Err(Error::from(e)),
137                }
138            }
139
140            Format::Output(ref format) => match avformat_alloc_output_context2(
141                &mut ps,
142                format.as_ptr() as *mut _,
143                ptr::null(),
144                path.as_ptr(),
145            ) {
146                0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
147                    0 => Ok(Context::Output(context::Output::wrap(ps))),
148                    e => Err(Error::from(e)),
149                },
150
151                e => Err(Error::from(e)),
152            },
153        }
154    }
155}
156
157pub fn input<P: AsRef<Path> + ?Sized>(path: &P) -> Result<context::Input, Error> {
158    unsafe {
159        let mut ps = ptr::null_mut();
160        let path = from_path(path);
161
162        match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut()) {
163            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
164                r if r >= 0 => Ok(context::Input::wrap(ps)),
165                e => {
166                    avformat_close_input(&mut ps);
167                    Err(Error::from(e))
168                }
169            },
170
171            e => Err(Error::from(e)),
172        }
173    }
174}
175
176pub fn input_with_dictionary<P: AsRef<Path> + ?Sized>(
177    path: &P,
178    options: Dictionary,
179) -> Result<context::Input, Error> {
180    unsafe {
181        let mut ps = ptr::null_mut();
182        let path = from_path(path);
183        let mut opts = options.disown();
184        let res = avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), &mut opts);
185
186        Dictionary::own(opts);
187
188        match res {
189            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
190                r if r >= 0 => Ok(context::Input::wrap(ps)),
191                e => {
192                    avformat_close_input(&mut ps);
193                    Err(Error::from(e))
194                }
195            },
196
197            e => Err(Error::from(e)),
198        }
199    }
200}
201
202pub fn input_with_interrupt<P: AsRef<Path> + ?Sized, F>(
203    path: &P,
204    closure: F,
205) -> Result<context::Input, Error>
206where
207    F: FnMut() -> bool + Send + 'static,
208{
209    unsafe {
210        let mut ps = avformat_alloc_context();
211        if ps.is_null() {
212            return Err(Error::Other { errno: ENOMEM });
213        }
214        let path = from_path(path);
215        let interrupt = interrupt::new(Box::new(closure));
216        (*ps).interrupt_callback = interrupt.interrupt;
217
218        match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut()) {
219            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
220                r if r >= 0 => Ok(context::Input::wrap_with_interrupt(ps, interrupt.guard)),
221                e => {
222                    avformat_close_input(&mut ps);
223                    Err(Error::from(e))
224                }
225            },
226
227            e => Err(Error::from(e)),
228        }
229    }
230}
231
232pub fn input_with_interrupt_and_dictionary<P: AsRef<Path> + ?Sized, F>(
233    path: &P,
234    closure: F,
235    options: Dictionary,
236) -> Result<context::Input, Error>
237where
238    F: FnMut() -> bool + Send + 'static,
239{
240    unsafe {
241        let mut ps = avformat_alloc_context();
242        if ps.is_null() {
243            return Err(Error::Other { errno: ENOMEM });
244        }
245        let interrupt = interrupt::new(Box::new(closure));
246        (*ps).interrupt_callback = interrupt.interrupt;
247        let path = from_path(path);
248
249        let mut opts = options.disown();
250        let res = avformat_open_input(&raw mut ps, path.as_ptr(), ptr::null_mut(), &raw mut opts);
251        Dictionary::own(opts);
252
253        match res {
254            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
255                r if r >= 0 => Ok(context::Input::wrap_with_interrupt(ps, interrupt.guard)),
256                e => {
257                    avformat_close_input(&raw mut ps);
258                    Err(Error::from(e))
259                }
260            },
261
262            e => Err(Error::from(e)),
263        }
264    }
265}
266/// Opens an input from a readable `context::StreamIo` (created with
267/// `StreamIo::from_read` or `StreamIo::from_read_seek`).
268///
269/// An optional filename helps with format detection; options configure the
270/// format context. Fails with `EINVAL` if `custom_io` is a write context or
271/// `filename` contains an interior NUL byte.
272pub fn input_from_stream(
273    custom_io: context::StreamIo,
274    filename: Option<&str>,
275    options: Option<Dictionary>,
276) -> Result<context::Input, Error> {
277    input_from_stream_impl(custom_io, filename, options, None)
278}
279
280/// Like [`input_from_stream`], with an interrupt callback FFmpeg polls to
281/// cancel a stalled open or read. `closure` returns `true` to abort; a
282/// cancelled blocking read then surfaces as `Error::Exit`. To resume the same
283/// context afterward, re-arm the token and either seek (seekable streams) or
284/// call [`context::Input::clear_interrupt`] (non-seekable streams).
285///
286/// Fails with `EINVAL` if `custom_io` is a write context or `filename`
287/// contains an interior NUL byte.
288pub fn input_from_stream_with_interrupt<F>(
289    custom_io: context::StreamIo,
290    filename: Option<&str>,
291    options: Option<Dictionary>,
292    closure: F,
293) -> Result<context::Input, Error>
294where
295    F: FnMut() -> bool + Send + 'static,
296{
297    input_from_stream_impl(
298        custom_io,
299        filename,
300        options,
301        Some(interrupt::new(Box::new(closure))),
302    )
303}
304
305/// Shared body for [`input_from_stream`] / [`input_from_stream_with_interrupt`].
306///
307/// When `interrupt` is present it is installed on the format context BEFORE
308/// open (so a stalled probe/connect is cancellable) AND mirrored into the
309/// `StreamIo` opaque — both in one place, so the mirror is impossible to forget
310/// when adding another `_with_interrupt` variant. The mirror is required
311/// because FFmpeg's custom-AVIO read path (`fill_buffer` → `read_packet`) never
312/// polls `AVFormatContext.interrupt_callback` itself — unlike its URL
313/// protocols' `retry_transfer_wrapper` — so the `StreamIo` read/write/seek
314/// callbacks poll the mirrored copy at the top of each attempt (see
315/// `StreamIo::set_interrupt`).
316fn input_from_stream_impl(
317    mut custom_io: context::StreamIo,
318    filename: Option<&str>,
319    options: Option<Dictionary>,
320    interrupt: Option<interrupt::Interrupt>,
321) -> Result<context::Input, Error> {
322    if custom_io.is_writable() {
323        return Err(Error::Other { errno: EINVAL });
324    }
325
326    let filename = opt_cstring(filename)?;
327    let filename_ptr = filename.as_ref().map_or(ptr::null(), |f| f.as_ptr());
328
329    unsafe {
330        let mut ps = avformat_alloc_context();
331        if ps.is_null() {
332            return Err(Error::Other { errno: ENOMEM });
333        }
334        if let Some(ref it) = interrupt {
335            (*ps).interrupt_callback = it.interrupt;
336            custom_io.set_interrupt(it.interrupt);
337        }
338        (*ps).pb = custom_io.as_mut_ptr();
339        (*ps).flags |= AVFMT_FLAG_CUSTOM_IO;
340
341        let result = if let Some(opts) = options {
342            let mut opts = opts.disown();
343            let res = avformat_open_input(&mut ps, filename_ptr, ptr::null_mut(), &mut opts);
344            Dictionary::own(opts);
345            res
346        } else {
347            avformat_open_input(&mut ps, filename_ptr, ptr::null_mut(), ptr::null_mut())
348        };
349
350        match result {
351            0 => match avformat_find_stream_info(ps, ptr::null_mut()) {
352                r if r >= 0 => Ok(match interrupt {
353                    Some(it) => {
354                        context::Input::wrap_with_custom_io_and_interrupt(ps, custom_io, it.guard)
355                    }
356                    None => context::Input::wrap_with_custom_io(ps, custom_io),
357                }),
358                e => {
359                    avformat_close_input(&mut ps);
360                    Err(Error::from(e))
361                }
362            },
363
364            e => Err(Error::from(e)),
365        }
366    }
367}
368
369pub fn output<P: AsRef<Path> + ?Sized>(path: &P) -> Result<context::Output, Error> {
370    unsafe {
371        let mut ps = ptr::null_mut();
372        let path = from_path(path);
373
374        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr()) {
375            0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
376                0 => Ok(context::Output::wrap(ps)),
377                e => Err(Error::from(e)),
378            },
379
380            e => Err(Error::from(e)),
381        }
382    }
383}
384
385pub fn output_with<P: AsRef<Path> + ?Sized>(
386    path: &P,
387    options: Dictionary,
388) -> Result<context::Output, Error> {
389    unsafe {
390        let mut ps = ptr::null_mut();
391        let path = from_path(path);
392        let mut opts = options.disown();
393
394        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr()) {
395            0 => {
396                let res = avio_open2(
397                    &mut (*ps).pb,
398                    path.as_ptr(),
399                    AVIO_FLAG_WRITE,
400                    ptr::null(),
401                    &mut opts,
402                );
403
404                Dictionary::own(opts);
405
406                match res {
407                    0 => Ok(context::Output::wrap(ps)),
408                    e => Err(Error::from(e)),
409                }
410            }
411
412            e => Err(Error::from(e)),
413        }
414    }
415}
416
417pub fn output_as<P: AsRef<Path> + ?Sized>(
418    path: &P,
419    format: &str,
420) -> Result<context::Output, Error> {
421    unsafe {
422        let mut ps = ptr::null_mut();
423        let path = from_path(path);
424        let format = CString::new(format).unwrap();
425
426        match avformat_alloc_output_context2(
427            &mut ps,
428            ptr::null_mut(),
429            format.as_ptr(),
430            path.as_ptr(),
431        ) {
432            0 => match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
433                0 => Ok(context::Output::wrap(ps)),
434                e => Err(Error::from(e)),
435            },
436
437            e => Err(Error::from(e)),
438        }
439    }
440}
441
442pub fn output_as_with<P: AsRef<Path> + ?Sized>(
443    path: &P,
444    format: &str,
445    options: Dictionary,
446) -> Result<context::Output, Error> {
447    unsafe {
448        let mut ps = ptr::null_mut();
449        let path = from_path(path);
450        let format = CString::new(format).unwrap();
451        let mut opts = options.disown();
452
453        match avformat_alloc_output_context2(
454            &mut ps,
455            ptr::null_mut(),
456            format.as_ptr(),
457            path.as_ptr(),
458        ) {
459            0 => {
460                let res = avio_open2(
461                    &mut (*ps).pb,
462                    path.as_ptr(),
463                    AVIO_FLAG_WRITE,
464                    ptr::null(),
465                    &mut opts,
466                );
467
468                Dictionary::own(opts);
469
470                match res {
471                    0 => Ok(context::Output::wrap(ps)),
472                    e => Err(Error::from(e)),
473                }
474            }
475
476            e => Err(Error::from(e)),
477        }
478    }
479}
480
481/// Creates an output context that writes to a writable `context::StreamIo`
482/// (created with `StreamIo::from_write` or `StreamIo::from_write_seek`).
483///
484/// The output format is inferred from `filename` or given explicitly via
485/// `format`; most muxers need a seekable stream for well-formed output. Call
486/// `write_trailer` before dropping the returned context — dropping only
487/// flushes what the muxer already emitted, it cannot finalize the file.
488/// Fails with `EINVAL` if `custom_io` is not a write context, if `filename` /
489/// `format` contain an interior NUL byte, or if the resolved muxer does its
490/// own I/O and would never write to the stream (`AVFMT_NOFILE` formats like
491/// `image2` or output devices).
492pub fn output_to_stream(
493    mut custom_io: context::StreamIo,
494    filename: Option<&str>,
495    format: Option<&str>,
496) -> Result<context::Output, Error> {
497    if !custom_io.is_writable() {
498        return Err(Error::Other { errno: EINVAL });
499    }
500
501    let filename = opt_cstring(filename)?;
502    let filename_ptr = filename.as_ref().map_or(ptr::null(), |f| f.as_ptr());
503
504    let format = opt_cstring(format)?;
505    let format_ptr = format.as_ref().map_or(ptr::null(), |f| f.as_ptr());
506
507    unsafe {
508        let mut ps = ptr::null_mut();
509
510        match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), format_ptr, filename_ptr) {
511            0 => {
512                // AVFMT_NOFILE muxers (image2's one-file-per-frame, devices,
513                // ...) do their own I/O, and `AVFormatContext.pb` is
514                // documented to stay NULL for them; the caller's stream would
515                // silently never receive the muxed output.
516                if (*(*ps).oformat).flags & AVFMT_NOFILE != 0 {
517                    avformat_free_context(ps);
518                    return Err(Error::Other { errno: EINVAL });
519                }
520
521                (*ps).pb = custom_io.as_mut_ptr();
522                (*ps).flags |= AVFMT_FLAG_CUSTOM_IO;
523
524                Ok(context::Output::wrap_with_custom_io(ps, custom_io))
525            }
526
527            e => Err(Error::from(e)),
528        }
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn input_with_interrupt_and_dictionary_accepts_str_path() {
538        let result = input_with_interrupt_and_dictionary(
539            "/ffmpeg-next-input-does-not-exist",
540            || false,
541            Dictionary::new(),
542        );
543
544        assert!(result.is_err());
545    }
546}