Compare commits

..

No commits in common. "bdc65868e76f241bb88e89c69464156c80bea51a" and "ef14631f71dd6b791c32505ea9ea4bf24a2ed757" have entirely different histories.

38 changed files with 735 additions and 1122 deletions

View File

@ -23,49 +23,30 @@ env:
jobs:
build-test-lint-linux:
name: Linux - FFmpeg ${{ matrix.ffmpeg.version }} - build, test and lint
name: Linux - FFmpeg ${{ matrix.ffmpeg_version }} - build, test and lint
runs-on: ubuntu-22.04
container: jrottenberg/ffmpeg:${{ matrix.ffmpeg_version }}-ubuntu
strategy:
matrix:
ffmpeg:
- version: "4.2"
file: "ffmpeg-4.2-linux-gcc.tar.xz"
- version: "4.3"
file: "ffmpeg-4.3-linux-gcc.tar.xz"
- version: "4.4"
file: "ffmpeg-4.4-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "5.1"
file: "ffmpeg-5.1-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "6.1"
file: "ffmpeg-6.1-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "7.0"
file: "ffmpeg-7.0-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "7.1"
file: "ffmpeg-7.1-linux-clang-default.tar.xz"
lib_subdir: "amd64"
ffmpeg_version:
- "4.2"
- "4.3"
- "4.4"
- "5.0"
- "5.1"
- "6.0"
- "6.1"
- "7.0"
- "7.1"
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libc++1 libva2 libva-drm2 libva-x11-2 libvdpau1 libxv1
- name: Download FFmpeg
shell: bash
run: |
mkdir ffmpeg-libs
curl -L "https://sourceforge.net/projects/avbuild/files/linux/${{ matrix.ffmpeg.file }}/download" \
| tar xJf - --strip 1 -C ffmpeg-libs
echo "PKG_CONFIG_PATH=$PWD/ffmpeg-libs/lib/${{ matrix.ffmpeg.lib_subdir }}/pkgconfig" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$PWD/ffmpeg-libs/lib/${{ matrix.ffmpeg.lib_subdir }}" >> "$GITHUB_ENV"
apt-get update
apt-get install -y --no-install-recommends clang curl pkg-config
- name: Install Rust stable with clippy and rustfmt
uses: dtolnay/rust-toolchain@stable
with:
@ -74,7 +55,7 @@ jobs:
with:
prefix-key: "v3-rust"
# Only save cache for one FFmpeg version
save-if: ${{ matrix.ffmpeg.version == '7.1' }}
save-if: ${{ matrix.ffmpeg_version == '7.1' }}
- name: Check format
run: cargo fmt -- --check
@ -185,18 +166,14 @@ jobs:
msrv:
runs-on: ubuntu-22.04
container: jrottenberg/ffmpeg:7.0-ubuntu
steps:
- uses: actions/checkout@v4
- name: Download FFmpeg
shell: bash
- name: Install dependencies
run: |
mkdir ffmpeg-libs
curl -L "https://sourceforge.net/projects/avbuild/files/linux/ffmpeg-7.1-linux-clang-lite.tar.xz/download" \
| tar xJf - --strip 1 -C ffmpeg-libs
echo "PKG_CONFIG_PATH=$PWD/ffmpeg-libs/lib/amd64/pkgconfig" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$PWD/ffmpeg-libs" >> "$GITHUB_ENV"
apt-get update
apt-get install -y --no-install-recommends clang curl pkg-config
# rust-version from Cargo.toml
- name: Install Rust 1.65.0
uses: dtolnay/rust-toolchain@1.65.0

View File

@ -58,6 +58,7 @@ codec = ["ffmpeg-sys-the-third/avcodec"]
device = ["ffmpeg-sys-the-third/avdevice", "format"]
filter = ["ffmpeg-sys-the-third/avfilter"]
format = ["ffmpeg-sys-the-third/avformat", "codec"]
resampling = ["ffmpeg-sys-the-third/avresample"]
postprocessing = ["ffmpeg-sys-the-third/postproc"]
software-resampling = ["ffmpeg-sys-the-third/swresample"]
software-scaling = ["ffmpeg-sys-the-third/swscale", "codec"]

View File

@ -4,26 +4,19 @@
This is a fork of the abandoned [ffmpeg-next](https://crates.io/crates/ffmpeg-next) crate which is a fork of the abandoned [ffmpeg](https://crates.io/crates/ffmpeg) crate.
Currently supported FFmpeg versions: 4.2 - 7.1.
Currently supported FFmpeg versions: 4.x, 5.x, 6.x.
Versions that are considered [old and unmaintained](https://ffmpeg.org/olddownload.html) by FFmpeg like 5.0 or 6.0 usually work, but are not actively tested during development.
Build instructions can be found on the [wiki](https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building).
## Usage
Documentation:
Build instructions can be found on the [wiki](https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building). API documentation for this crate can be found on [docs.rs](https://docs.rs/ffmpeg-the-third/).
- [docs.rs](https://docs.rs/ffmpeg-the-third/);
- [FFmpeg user manual](https://ffmpeg.org/ffmpeg-all.html);
- [FFmpeg Doxygen](https://ffmpeg.org/doxygen/trunk/).
_See [CHANGELOG.md](CHANGELOG.md) for information on version upgrades._
_See [CHANGELOG.md](CHANGELOG.md) for other information on version upgrades._
### FFmpeg documentation
- [FFmpeg user manual](https://ffmpeg.org/ffmpeg-all.html)
- [FFmpeg Doxygen](https://ffmpeg.org/doxygen/trunk/)
## Contributing
Issues and PRs are welcome.
If you have significant, demonstrable experience in Rust and multimedia-related programming, please let me know, I'll be more than happy to invite you as a collaborator.
**If you have significant, demonstrable experience in Rust and multimedia-related programming, please let me know, I'll be more than happy to invite you as a collaborator.**
## Minimum supported Rust version (MSRV)

View File

@ -5,7 +5,7 @@ use std::env;
fn main() {
ffmpeg::init().unwrap();
match ffmpeg::format::input(env::args().nth(1).expect("missing input file name")) {
match ffmpeg::format::input(&env::args().nth(1).expect("missing input file name")) {
Ok(ictx) => {
println!("Nb chapters: {}", ictx.nb_chapters());
@ -20,7 +20,7 @@ fn main() {
}
}
let mut octx = ffmpeg::format::output("test.mkv").expect("Couldn't open test file");
let mut octx = ffmpeg::format::output(&"test.mkv").expect("Couldn't open test file");
for chapter in ictx.chapters() {
let title = match chapter.metadata().get("title") {

View File

@ -20,7 +20,7 @@ fn main() {
println!("\t profiles: none");
}
if let Some(video) = codec.video() {
if let Ok(video) = codec.video() {
if let Some(rates) = video.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else {
@ -34,7 +34,7 @@ fn main() {
}
}
if let Some(audio) = codec.audio() {
if let Ok(audio) = codec.audio() {
if let Some(rates) = audio.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else {
@ -71,7 +71,7 @@ fn main() {
println!("\t profiles: {:?}", profiles.collect::<Vec<_>>());
}
if let Some(video) = codec.video() {
if let Ok(video) = codec.video() {
if let Some(rates) = video.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else {
@ -85,7 +85,7 @@ fn main() {
}
}
if let Some(audio) = codec.audio() {
if let Ok(audio) = codec.audio() {
if let Some(rates) = audio.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else {

View File

@ -11,7 +11,7 @@ use std::io::prelude::*;
fn main() -> Result<(), ffmpeg::Error> {
ffmpeg::init().unwrap();
if let Ok(mut ictx) = input(env::args().nth(1).expect("Cannot open file.")) {
if let Ok(mut ictx) = input(&env::args().nth(1).expect("Cannot open file.")) {
let input = ictx
.streams()
.best(Type::Video)

View File

@ -5,7 +5,7 @@ use std::env;
fn main() -> Result<(), ffmpeg::Error> {
ffmpeg::init().unwrap();
match ffmpeg::format::input(env::args().nth(1).expect("missing file")) {
match ffmpeg::format::input(&env::args().nth(1).expect("missing file")) {
Ok(context) => {
for (k, v) in context.metadata().iter() {
println!("{k}: {v}");

View File

@ -72,7 +72,7 @@ struct Transcoder {
fn transcoder<P: AsRef<Path>>(
ictx: &mut format::context::Input,
octx: &mut format::context::Output,
path: P,
path: &P,
filter_spec: &str,
) -> Result<Transcoder, ffmpeg::Error> {
let input = ictx
@ -83,8 +83,7 @@ fn transcoder<P: AsRef<Path>>(
let mut decoder = context.decoder().audio()?;
let codec = ffmpeg::encoder::find(octx.format().codec(path, media::Type::Audio))
.expect("failed to find encoder")
.audio()
.expect("encoder is not audio encoder");
.audio()?;
let global = octx
.format()
.flags()

View File

@ -58,6 +58,7 @@ avcodec = []
avdevice = ["avformat"]
avfilter = []
avformat = ["avcodec"]
avresample = []
postproc = []
swresample = []
swscale = []

View File

@ -20,7 +20,6 @@ In addition to feature flags declared in `Cargo.toml`, this crate performs vario
- "ffmpeg_6_0"
- "ffmpeg_6_1"
- "ffmpeg_7_0"
- "ffmpeg_7_1"
- `avcodec_version_greater_than_<x>_<y>`, e.g., `avcodec_version_greater_than_58_90`. The name should be self-explanatory.

View File

@ -22,7 +22,6 @@ struct Library {
optional: bool,
features: &'static [AVFeature],
headers: &'static [AVHeader],
min_major_version: u64,
}
impl Library {
@ -30,14 +29,12 @@ impl Library {
name: &'static str,
features: &'static [AVFeature],
headers: &'static [AVHeader],
min_version: u64,
) -> Self {
Self {
name,
optional: false,
features,
headers,
min_major_version: min_version,
}
}
@ -45,14 +42,12 @@ impl Library {
name: &'static str,
features: &'static [AVFeature],
headers: &'static [AVHeader],
min_version: u64,
) -> Self {
Self {
name,
optional: true,
features,
headers,
min_major_version: min_version,
}
}
@ -66,14 +61,15 @@ impl Library {
}
static LIBRARIES: &[Library] = &[
Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS, 50),
Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS, 50),
Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS, 50),
Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS, 50),
Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS, 0),
Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS, 0),
Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS, 0),
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS, 50),
Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS),
Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS),
Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS),
Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS),
Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS),
Library::optional("avresample", AVRESAMPLE_FEATURES, AVRESAMPLE_HEADERS),
Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS),
Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS),
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS),
];
#[derive(Debug)]
@ -220,6 +216,8 @@ static AVFILTER_FEATURES: &[AVFeature] = &[
AVFeature::new("LINK_PUBLIC"),
];
static AVRESAMPLE_FEATURES: &[AVFeature] = &[];
static SWSCALE_FEATURES: &[AVFeature] = &[];
static SWRESAMPLE_FEATURES: &[AVFeature] = &[];
@ -304,6 +302,7 @@ static AVFILTER_HEADERS: &[AVHeader] = &[
AVHeader::new("buffersrc.h"),
AVHeader::new("avfilter.h"),
];
static AVRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("avresample.h")];
static SWSCALE_HEADERS: &[AVHeader] = &[AVHeader::new("swscale.h")];
static SWRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("swresample.h")];
static POSTPROC_HEADERS: &[AVHeader] = &[AVHeader::new("postprocess.h")];
@ -563,8 +562,14 @@ fn build(out_dir: &Path, ffmpeg_version: &str) -> io::Result<PathBuf> {
// the binary using ffmpeg-sys cannot be redistributed
configure.switch("BUILD_LICENSE_NONFREE", "nonfree");
let ffmpeg_major_version: u32 = get_major_version(ffmpeg_version);
// configure building libraries based on features
for lib in LIBRARIES.iter().filter(|lib| lib.optional) {
for lib in LIBRARIES
.iter()
.filter(|lib| lib.optional)
.filter(|lib| !(lib.name == "avresample" && ffmpeg_major_version >= 5))
{
configure.switch(&lib.name.to_uppercase(), lib.name);
}
@ -774,22 +779,22 @@ fn check_features(include_paths: &[PathBuf]) {
}
}
for lib in enabled_libraries() {
let ver = if let Some(v) = versions.get(&lib.name) {
v
} else {
continue;
};
for major in lib.min_major_version..=(*ver).0 {
for minor in 0..=135 {
if *ver >= (major, minor) {
let version_check_info = [("avcodec", 57, 62, 0, 101)];
for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in
&version_check_info
{
let libversion = *versions
.get(lib)
.expect("Unable to find the version for lib{lib}");
for version_major in begin_version_major..end_version_major {
for version_minor in begin_version_minor..end_version_minor {
if libversion >= (version_major, version_minor) {
println!(
r#"cargo:rustc-cfg=feature="{}_version_greater_than_{major}_{minor}""#,
lib.name,
r#"cargo:rustc-cfg=feature="{lib}_version_greater_than_{version_major}_{version_minor}""#
);
println!(
r#"cargo:{}_version_greater_than_{major}_{minor}=true"#,
lib.name,
r#"cargo:{lib}_version_greater_than_{version_major}_{version_minor}=true"#
);
}
}
@ -966,6 +971,7 @@ fn main() {
.allowlist_file(r#".*[/\\]libavformat[/\\].*"#)
.allowlist_file(r#".*[/\\]libavdevice[/\\].*"#)
.allowlist_file(r#".*[/\\]libavfilter[/\\].*"#)
.allowlist_file(r#".*[/\\]libavresample[/\\].*"#)
.allowlist_file(r#".*[/\\]libswscale[/\\].*"#)
.allowlist_file(r#".*[/\\]libswresample[/\\].*"#)
.allowlist_file(r#".*[/\\]libpostproc[/\\].*"#)

215
src/codec/audio.rs Normal file
View File

@ -0,0 +1,215 @@
use std::ops::Deref;
use super::codec::Codec;
use crate::ffi::*;
use crate::format;
#[cfg(not(feature = "ffmpeg_7_0"))]
use crate::ChannelLayoutMask;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Audio {
codec: Codec,
}
impl Audio {
pub unsafe fn new(codec: Codec) -> Audio {
Audio { codec }
}
}
impl Audio {
pub fn rates(&self) -> Option<RateIter> {
unsafe {
if (*self.as_ptr()).supported_samplerates.is_null() {
None
} else {
Some(RateIter::new((*self.codec.as_ptr()).supported_samplerates))
}
}
}
pub fn formats(&self) -> Option<FormatIter> {
unsafe {
if (*self.codec.as_ptr()).sample_fmts.is_null() {
None
} else {
Some(FormatIter::new((*self.codec.as_ptr()).sample_fmts))
}
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub fn channel_layouts(&self) -> Option<ChannelLayoutMaskIter> {
unsafe {
if (*self.codec.as_ptr()).channel_layouts.is_null() {
None
} else {
Some(ChannelLayoutMaskIter::new(
(*self.codec.as_ptr()).channel_layouts,
))
}
}
}
#[cfg(feature = "ffmpeg_5_1")]
pub fn ch_layouts(&self) -> Option<ChannelLayoutIter> {
unsafe { ChannelLayoutIter::from_raw((*self.codec.as_ptr()).ch_layouts) }
}
}
impl Deref for Audio {
type Target = Codec;
fn deref(&self) -> &Self::Target {
&self.codec
}
}
pub struct RateIter {
ptr: *const i32,
}
impl RateIter {
pub fn new(ptr: *const i32) -> Self {
RateIter { ptr }
}
}
impl Iterator for RateIter {
type Item = i32;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == 0 {
return None;
}
let rate = *self.ptr;
self.ptr = self.ptr.offset(1);
Some(rate)
}
}
}
pub struct FormatIter {
ptr: *const AVSampleFormat,
}
impl FormatIter {
pub fn new(ptr: *const AVSampleFormat) -> Self {
FormatIter { ptr }
}
}
impl Iterator for FormatIter {
type Item = format::Sample;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == AVSampleFormat::AV_SAMPLE_FMT_NONE {
return None;
}
let format = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(format)
}
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub struct ChannelLayoutMaskIter {
ptr: *const u64,
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl ChannelLayoutMaskIter {
pub fn new(ptr: *const u64) -> Self {
ChannelLayoutMaskIter { ptr }
}
pub fn best(self, max: i32) -> ChannelLayoutMask {
self.fold(ChannelLayoutMask::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl Iterator for ChannelLayoutMaskIter {
type Item = ChannelLayoutMask;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == 0 {
return None;
}
let layout = ChannelLayoutMask::from_bits_truncate(*self.ptr);
self.ptr = self.ptr.offset(1);
Some(layout)
}
}
}
#[cfg(feature = "ffmpeg_5_1")]
pub use ch_layout::ChannelLayoutIter;
#[cfg(feature = "ffmpeg_5_1")]
mod ch_layout {
use super::*;
use crate::ChannelLayout;
pub struct ChannelLayoutIter<'a> {
next: &'a AVChannelLayout,
}
impl<'a> ChannelLayoutIter<'a> {
pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option<Self> {
ptr.as_ref().map(|next| Self { next })
}
}
impl<'a> ChannelLayoutIter<'a> {
pub fn best(self, max: u32) -> ChannelLayout<'a> {
self.fold(ChannelLayout::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
impl<'a> Iterator for ChannelLayoutIter<'a> {
type Item = ChannelLayout<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let curr = self.next;
if *curr == zeroed_layout() {
return None;
}
// SAFETY: We trust that there is always an initialized layout up until
// the zeroed-out AVChannelLayout, which signals the end of iteration.
self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap();
Some(ChannelLayout::from(curr))
}
}
}
// TODO: Remove this with a const variable when zeroed() is const (1.75.0)
unsafe fn zeroed_layout() -> AVChannelLayout {
std::mem::zeroed()
}
}

View File

@ -1,58 +1,30 @@
use std::marker::PhantomData;
use std::ptr::NonNull;
use super::config::{
FrameRateIter, PixelFormatIter, SampleFormatIter, SampleRateIter, TerminatedPtrIter,
};
use super::descriptor::{CodecDescriptor, CodecDescriptorIter};
use super::profile::ProfileIter;
use super::{Capabilities, Id};
use super::{Audio, Capabilities, Id, Profile, Video};
use crate::ffi::*;
use crate::{media, utils};
#[cfg(feature = "ffmpeg_7_1")]
use crate::codec::config::{ColorRangeIter, ColorSpaceIter, Supported};
pub fn list_descriptors() -> CodecDescriptorIter {
CodecDescriptorIter::new()
}
pub type Audio = Codec<AudioType>;
pub type Video = Codec<VideoType>;
use crate::{media, utils, Error};
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Codec<Type = UnknownType> {
ptr: NonNull<AVCodec>,
_marker: PhantomData<Type>,
pub struct Codec {
ptr: *mut AVCodec,
}
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct UnknownType;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct AudioType;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct VideoType;
unsafe impl Send for Codec {}
unsafe impl Sync for Codec {}
unsafe impl<T> Send for Codec<T> {}
unsafe impl<T> Sync for Codec<T> {}
impl Codec {
pub unsafe fn wrap(ptr: *mut AVCodec) -> Self {
Codec { ptr }
}
impl Codec<UnknownType> {
/// Create a new reference to a codec from a raw pointer.
///
/// Returns `None` if `ptr` is null.
pub unsafe fn from_raw(ptr: *const AVCodec) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self {
ptr,
_marker: PhantomData,
})
pub unsafe fn as_ptr(&self) -> *const AVCodec {
self.ptr as *const _
}
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVCodec {
self.ptr
}
}
impl<T> Codec<T> {
pub fn as_ptr(&self) -> *const AVCodec {
self.ptr.as_ptr()
}
impl Codec {
pub fn is_encoder(&self) -> bool {
unsafe { av_codec_is_encoder(self.as_ptr()) != 0 }
}
@ -81,14 +53,13 @@ impl<T> Codec<T> {
self.medium() == media::Type::Video
}
pub fn video(self) -> Option<Video> {
pub fn video(self) -> Result<Video, Error> {
unsafe {
if self.medium() == media::Type::Video {
Some(Codec {
ptr: self.ptr,
_marker: PhantomData,
})
Ok(Video::new(self))
} else {
None
Err(Error::InvalidData)
}
}
}
@ -96,14 +67,13 @@ impl<T> Codec<T> {
self.medium() == media::Type::Audio
}
pub fn audio(self) -> Option<Audio> {
pub fn audio(self) -> Result<Audio, Error> {
unsafe {
if self.medium() == media::Type::Audio {
Some(Codec {
ptr: self.ptr,
_marker: PhantomData,
})
Ok(Audio::new(self))
} else {
None
Err(Error::InvalidData)
}
}
}
@ -124,217 +94,32 @@ impl<T> Codec<T> {
}
}
}
pub fn descriptor(self) -> Option<CodecDescriptor> {
unsafe {
let ptr = avcodec_descriptor_get(self.id().into());
CodecDescriptor::from_raw(ptr)
}
pub struct ProfileIter {
id: Id,
ptr: *const AVProfile,
}
impl ProfileIter {
pub fn new(id: Id, ptr: *const AVProfile) -> Self {
ProfileIter { id, ptr }
}
}
impl Codec<AudioType> {
/// Checks if the given sample rate is supported by this audio codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_rate(self, rate: libc::c_int) -> bool {
self.supported_rates().supports(rate)
}
/// Returns a [`Supported`] representing the supported sample rates.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_rates(self) -> Supported<SampleRateIter<'static>> {
use super::config::supported_sample_rates;
supported_sample_rates(self, None).expect("audio codec returns supported sample rates")
}
pub fn rates(&self) -> Option<SampleRateIter> {
unsafe { SampleRateIter::from_raw((*self.as_ptr()).supported_samplerates) }
}
/// Checks if the given sample format is supported by this audio codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_format(self, format: crate::format::Sample) -> bool {
self.supported_formats().supports(format)
}
/// Returns a [`Supported`] representing the supported sample formats.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_formats(self) -> Supported<SampleFormatIter<'static>> {
use super::config::supported_sample_formats;
supported_sample_formats(self, None).expect("audio codec returns supported sample formats")
}
pub fn formats(&self) -> Option<SampleFormatIter> {
unsafe { SampleFormatIter::from_raw((*self.as_ptr()).sample_fmts) }
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub fn channel_layouts(&self) -> Option<ChannelLayoutMaskIter> {
unsafe { ChannelLayoutMaskIter::from_raw((*self.as_ptr()).channel_layouts) }
}
#[cfg(feature = "ffmpeg_5_1")]
pub fn ch_layouts(&self) -> Option<ChannelLayoutIter> {
unsafe { ChannelLayoutIter::from_raw((*self.as_ptr()).ch_layouts) }
}
}
impl Codec<VideoType> {
/// Checks if the given frame rate is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_rate(self, rate: crate::Rational) -> bool {
self.supported_rates().supports(rate)
}
/// Returns a [`Supported`] representing the supported frame rates.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_rates(self) -> Supported<FrameRateIter<'static>> {
use crate::codec::config::supported_frame_rates;
supported_frame_rates(self, None).expect("video codec returns supported frame rates")
}
pub fn rates(&self) -> Option<FrameRateIter> {
unsafe { FrameRateIter::from_raw((*self.as_ptr()).supported_framerates) }
}
/// Checks if the given pixel format is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_format(self, format: crate::format::Pixel) -> bool {
self.supported_formats().supports(format)
}
/// Returns a [`Supported`] representing the supported pixel formats.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_formats(self) -> Supported<PixelFormatIter<'static>> {
use crate::codec::config::supported_pixel_formats;
supported_pixel_formats(self, None).expect("video codec returns supported pixel formats")
}
pub fn formats(&self) -> Option<PixelFormatIter> {
unsafe { PixelFormatIter::from_raw((*self.as_ptr()).pix_fmts) }
}
/// Checks if the given color space is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_color_space(self, space: crate::color::Space) -> bool {
self.supported_color_spaces().supports(space)
}
/// Returns a [`Supported`] representing the supported color spaces.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_color_spaces(self) -> Supported<ColorSpaceIter<'static>> {
use crate::codec::config::supported_color_spaces;
supported_color_spaces(self, None).expect("video codec returns supported color spaces")
}
/// Checks if the given color range is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_color_range(self, range: crate::color::Range) -> bool {
self.supported_color_ranges().supports(range)
}
/// Returns a [`Supported`] representing the supported color ranges.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_color_ranges(self) -> Supported<ColorRangeIter<'static>> {
use crate::codec::config::supported_color_ranges;
supported_color_ranges(self, None).expect("video codec returns supported color ranges")
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
use crate::ChannelLayoutMask;
#[cfg(not(feature = "ffmpeg_7_0"))]
pub struct ChannelLayoutMaskIter {
ptr: NonNull<u64>,
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl ChannelLayoutMaskIter {
pub fn from_raw(ptr: *const u64) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
pub fn best(self, max: i32) -> ChannelLayoutMask {
self.fold(ChannelLayoutMask::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl Iterator for ChannelLayoutMaskIter {
type Item = ChannelLayoutMask;
impl Iterator for ProfileIter {
type Item = Profile;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
let ptr = self.ptr.as_ptr();
if *ptr == 0 {
if (*self.ptr).profile == FF_PROFILE_UNKNOWN {
return None;
}
let layout = ChannelLayoutMask::from_bits_truncate(*ptr);
self.ptr = NonNull::new_unchecked(ptr.add(1));
let profile = Profile::from((self.id, (*self.ptr).profile));
self.ptr = self.ptr.offset(1);
Some(layout)
Some(profile)
}
}
}
#[cfg(feature = "ffmpeg_5_1")]
pub use ch_layout::ChannelLayoutIter;
#[cfg(feature = "ffmpeg_5_1")]
mod ch_layout {
use super::*;
use crate::ChannelLayout;
pub struct ChannelLayoutIter<'a> {
next: &'a AVChannelLayout,
}
impl<'a> ChannelLayoutIter<'a> {
pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option<Self> {
ptr.as_ref().map(|next| Self { next })
}
}
impl<'a> ChannelLayoutIter<'a> {
pub fn best(self, max: u32) -> ChannelLayout<'a> {
self.fold(ChannelLayout::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
impl<'a> Iterator for ChannelLayoutIter<'a> {
type Item = ChannelLayout<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let curr = self.next;
if *curr == zeroed_layout() {
return None;
}
// SAFETY: We trust that there is always an initialized layout up until
// the zeroed-out AVChannelLayout, which signals the end of iteration.
self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap();
Some(ChannelLayout::from(curr))
}
}
}
// TODO: Remove this with a const variable when zeroed() is const (1.75.0)
unsafe fn zeroed_layout() -> AVChannelLayout {
std::mem::zeroed()
}
}

View File

@ -1,397 +0,0 @@
use std::ptr::NonNull;
#[cfg(feature = "ffmpeg_7_1")]
use crate::codec::Context;
#[cfg(feature = "ffmpeg_7_1")]
use crate::ffi::*;
#[cfg(feature = "ffmpeg_7_1")]
use crate::Codec;
#[cfg(feature = "ffmpeg_7_1")]
use crate::Error;
#[cfg(feature = "ffmpeg_7_1")]
#[derive(Debug, Clone)]
pub enum Supported<I> {
All,
Specific(I),
}
#[cfg(feature = "ffmpeg_7_1")]
impl<T, I> Supported<I>
where
T: PartialEq,
I: Iterator<Item = T>,
{
/// Check if all possible configuration values are supported.
///
/// # Example
///
/// ```
/// use ffmpeg_the_third::codec::{encoder, Id};
///
/// let codec = encoder::find(Id::VP9)
/// .expect("Can find a VP9 encoder")
/// .video()
/// .unwrap();
///
/// let supported = codec.supported_rates();
/// assert!(supported.all())
/// ```
pub fn all(&self) -> bool {
matches!(self, Supported::All)
}
/// Check if a specific configuration value is supported.
///
/// # Example
///
/// ```
/// use ffmpeg_the_third::codec::{decoder, Id};
/// use ffmpeg_the_third::format::sample::{Sample, Type};
///
/// let codec = decoder::find(Id::MP3)
/// .expect("Can find an MP3 decoder")
/// .audio()
/// .unwrap();
///
/// let supported = codec.supported_formats();
/// assert!(supported.supports(Sample::F32(Type::Planar)));
/// ```
pub fn supports(self, t: T) -> bool {
match self {
Supported::All => true,
Supported::Specific(mut iter) => iter.any(|elem| elem == t),
}
}
}
#[cfg(feature = "ffmpeg_7_1")]
fn supported<WrapperType, AVType, CodecType, I>(
codec: Codec<CodecType>,
ctx: Option<&Context>,
cfg: AVCodecConfig,
) -> Result<Supported<I>, Error>
where
I: TerminatedPtrIter<AVType, WrapperType>,
AVType: Into<WrapperType>,
{
let mut out_ptr: *const libc::c_void = std::ptr::null();
unsafe {
let avctx = ctx.map_or(std::ptr::null(), |ctx| ctx.as_ptr());
let ret = avcodec_get_supported_config(
avctx,
codec.as_ptr(),
cfg,
0, // flags: unused as of 7.1, set to zero
&mut out_ptr,
std::ptr::null_mut(), // out_num_configs: optional, we don't support it currently
);
if ret < 0 {
return Err(Error::from(ret));
}
match NonNull::new(out_ptr as *mut _) {
// non-nullptr -> Specific list of values is supported.
Some(ptr) => Ok(Supported::Specific(I::from_ptr(ptr))),
// nullptr -> Everything is supported
None => Ok(Supported::All),
}
}
}
/// Pointer-based iterator, stepped through via pointer arithmetic and ended
/// when a dereferenced pointer equals a terminator value.
pub(crate) trait TerminatedPtrIter<AVType, WrapperType>:
Sized + Iterator<Item = WrapperType>
{
/// Create a new iterator from a non-null pointer to any value in the iteration.
///
/// # Safety
///
/// `ptr` and all following pointers must be dereferenceable until the terminator is reached.
unsafe fn from_ptr(ptr: NonNull<AVType>) -> Self;
/// Create a new iterator from a pointer to any value in the iteration.
///
/// Returns `None` if `ptr` is null. See also [`from_ptr`][TerminatedPtrIter::from_ptr].
///
/// # Safety
///
/// See [`from_ptr`][TerminatedPtrIter::from_ptr].
unsafe fn from_raw(ptr: *const AVType) -> Option<Self> {
unsafe { NonNull::new(ptr as *mut _).map(|ptr| Self::from_ptr(ptr)) }
}
}
macro_rules! impl_config_iter {
(
$fn_name:ident,
$codec_cfg:expr,
$iter:ident,
$ty:ty,
$av_ty:ty,
$terminator:expr
) => {
impl_config_iter_fn!($fn_name, $iter, $codec_cfg);
impl_config_iter_struct!($iter, $av_ty);
impl_config_iter_traits!($iter, $ty, $av_ty, $terminator);
};
}
macro_rules! impl_config_iter_struct {
($iter:ident, $av_ty:ty) => {
#[derive(Debug, Clone)]
pub struct $iter<'a> {
next: std::ptr::NonNull<$av_ty>,
_marker: std::marker::PhantomData<&'a $av_ty>,
}
};
}
macro_rules! impl_config_iter_fn {
($fn_name:ident, $iter:ident, $codec_cfg:expr) => {
/// Low-level function interacting with the FFmpeg API via
/// `avcodec_get_supported_config()`. Consider using one of the convenience methods
/// on the codecs or codec contexts instead.
#[cfg(feature = "ffmpeg_7_1")]
pub fn $fn_name<T>(
codec: Codec<T>,
ctx: Option<&Context>,
) -> Result<Supported<$iter>, Error> {
supported(codec, ctx, $codec_cfg)
}
};
}
macro_rules! impl_config_iter_traits {
($iter:ident, $ty:ty, $av_ty:ty, $terminator:expr) => {
impl<'a> TerminatedPtrIter<$av_ty, $ty> for $iter<'a> {
unsafe fn from_ptr(ptr: std::ptr::NonNull<$av_ty>) -> Self {
Self {
next: ptr,
_marker: std::marker::PhantomData,
}
}
}
// We make sure that this is true by not incrementing self.ptr after the
// terminator has been reached.
impl<'a> std::iter::FusedIterator for $iter<'a> {}
// TODO: Maybe add ExactSizeIterator? This would require using the out_num_configs
// parameter and storing it inside $iter. Not sure it's too important unless
// many people want to use .collect() or something else that benefits from
// ExactSizeIterator.
impl<'a> Iterator for $iter<'a> {
type Item = $ty;
fn next(&mut self) -> Option<Self::Item> {
// SAFETY: The FFmpeg API guarantees that the pointer is safe to deref and
// increment until the terminator is reached.
unsafe {
let curr = self.next.as_ptr();
if *curr == $terminator {
return None;
}
// TODO: Replace with the following if MSRV >= 1.80:
// self.next = NonNull::from(self.next).add(1).as_ref();
self.next = std::ptr::NonNull::new_unchecked(curr.add(1));
Some((*curr).into())
}
}
}
};
}
impl_config_iter!(
supported_pixel_formats,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_PIX_FORMAT,
PixelFormatIter,
crate::format::Pixel,
crate::ffi::AVPixelFormat,
crate::ffi::AVPixelFormat::AV_PIX_FMT_NONE
);
impl_config_iter!(
supported_frame_rates,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_FRAME_RATE,
FrameRateIter,
crate::Rational,
crate::ffi::AVRational,
crate::ffi::AVRational { num: 0, den: 0 }
);
impl_config_iter!(
supported_sample_rates,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_RATE,
SampleRateIter,
libc::c_int,
libc::c_int,
0 as libc::c_int
);
impl_config_iter!(
supported_sample_formats,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_FORMAT,
SampleFormatIter,
crate::format::Sample,
crate::ffi::AVSampleFormat,
crate::ffi::AVSampleFormat::AV_SAMPLE_FMT_NONE
);
#[cfg(feature = "ffmpeg_7_1")]
impl_config_iter!(
supported_color_ranges,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_RANGE,
ColorRangeIter,
crate::color::Range,
crate::ffi::AVColorRange,
crate::ffi::AVColorRange::AVCOL_RANGE_UNSPECIFIED
);
#[cfg(feature = "ffmpeg_7_1")]
impl_config_iter!(
supported_color_spaces,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_SPACE,
ColorSpaceIter,
crate::color::Space,
crate::ffi::AVColorSpace,
crate::ffi::AVColorSpace::AVCOL_SPC_UNSPECIFIED
);
#[cfg(test)]
#[cfg(feature = "ffmpeg_7_1")]
mod test {
use super::*;
use crate::codec::{decoder, encoder, Compliance, Id};
use crate::color::Range;
use crate::format::Pixel;
use crate::Rational;
// These tests can fail if the FFmpeg build does not contain the required de/encoder.
// TODO: Check if tests can be hidden behind feature flags.
#[test]
fn audio_decoder() {
let codec = decoder::find(Id::MP3).expect("can find mp3 decoder");
// Audio decoder does not have color ranges
assert!(supported_color_ranges(codec, None).is_err());
let format_iter = match supported_sample_formats(codec, None) {
Ok(Supported::Specific(f)) => f,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for format in format_iter {
println!("format: {format:#?}");
}
}
#[test]
fn audio_encoder() {
let codec = encoder::find(Id::OPUS).expect("can find opus encoder");
// looks like every codec returns Supported::All for color space.
// might change in a future FFmpeg release
assert!(matches!(
supported_color_spaces(codec, None),
Ok(Supported::All)
));
let format_iter = match supported_sample_formats(codec, None) {
Ok(Supported::Specific(f)) => f,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for format in format_iter {
println!("format: {format:#?}");
}
}
#[test]
fn video_decoder() {
let codec = decoder::find(Id::H264).expect("can find H264 decoder");
assert!(supported_sample_rates(codec, None).is_err());
assert!(matches!(
supported_color_spaces(codec, None),
Ok(Supported::All)
));
}
#[test]
fn video_encoder() {
let codec = encoder::find(Id::VP9).expect("can find VP9 encoder");
let color_ranges = match supported_color_ranges(codec, None) {
Ok(Supported::Specific(c)) => c,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for range in color_ranges {
println!("{range:#?}");
}
assert!(matches!(
supported_pixel_formats(codec, None),
Ok(Supported::Specific(_))
));
assert!(matches!(
supported_frame_rates(codec, None),
Ok(Supported::All)
));
}
#[test]
fn supports() {
let codec = encoder::find(Id::FFV1).expect("can find FFV1 encoder");
assert!(supported_color_ranges(codec, None)
.expect("can check color range support")
.supports(Range::MPEG));
assert!(!supported_pixel_formats(codec, None)
.expect("can check color range support")
.supports(Pixel::GRAY16));
assert!(supported_frame_rates(codec, None)
.expect("can check frame rate support")
.supports(Rational(123, 456)));
supported_sample_formats(codec, None)
.expect_err("can NOT check sample format support (video codec)");
}
#[test]
fn with_context() {
let codec = encoder::find(Id::MJPEG).expect("can find MJPEG encoder");
let mut ctx = unsafe {
let avctx = crate::ffi::avcodec_alloc_context3(codec.as_ptr());
crate::codec::Context::wrap(avctx, None)
};
ctx.compliance(Compliance::Strict);
assert!(!supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
.expect("can check color range support")
.supports(Range::MPEG));
ctx.compliance(Compliance::Unofficial);
// Note that we check for NOT supported above, and YES supported here
// MJPEG encoder only supports MPEG color range if compliance is
// Unofficial or lower (less strict)
assert!(supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
.expect("can check color range support")
.supports(Range::MPEG));
}
}

View File

@ -62,7 +62,13 @@ impl Context {
}
pub fn codec(&self) -> Option<Codec> {
unsafe { Codec::from_raw((*self.as_ptr()).codec) }
unsafe {
if (*self.as_ptr()).codec.is_null() {
None
} else {
Some(Codec::wrap((*self.as_ptr()).codec as *mut _))
}
}
}
pub fn medium(&self) -> media::Type {

View File

@ -18,7 +18,7 @@ impl Decoder {
}
}
pub fn open_as<T, D: traits::Decoder<T>>(mut self, codec: D) -> Result<Opened, Error> {
pub fn open_as<D: traits::Decoder>(mut self, codec: D) -> Result<Opened, Error> {
unsafe {
if let Some(codec) = codec.decoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -31,7 +31,7 @@ impl Decoder {
}
}
pub fn open_as_with<T, D: traits::Decoder<T>>(
pub fn open_as_with<D: traits::Decoder>(
mut self,
codec: D,
options: Dictionary,

View File

@ -34,16 +34,25 @@ pub fn new() -> Decoder {
pub fn find(id: Id) -> Option<Codec> {
unsafe {
let ptr = avcodec_find_decoder(id.into());
Codec::from_raw(ptr)
let ptr = avcodec_find_decoder(id.into()) as *mut AVCodec;
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
pub fn find_by_name(name: &str) -> Option<Codec> {
unsafe {
let name = CString::new(name).unwrap();
let ptr = avcodec_find_decoder_by_name(name.as_ptr());
let ptr = avcodec_find_decoder_by_name(name.as_ptr()) as *mut AVCodec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}

View File

@ -1,158 +0,0 @@
use std::ffi::CStr;
use std::ptr::NonNull;
use std::str::from_utf8_unchecked;
use crate::ffi::*;
use crate::media;
use super::profile::ProfileIter;
use super::{CodecProperties, Id};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodecDescriptor {
ptr: NonNull<AVCodecDescriptor>,
}
impl CodecDescriptor {
pub unsafe fn from_raw(ptr: *const AVCodecDescriptor) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
pub fn as_ptr(self) -> *const AVCodecDescriptor {
self.ptr.as_ptr()
}
pub fn id(self) -> Id {
unsafe { (*self.as_ptr()).id.into() }
}
pub fn kind(self) -> media::Type {
unsafe { (*self.as_ptr()).type_.into() }
}
pub fn name(self) -> &'static str {
unsafe { from_utf8_unchecked(CStr::from_ptr((*self.as_ptr()).name).to_bytes()) }
}
pub fn description(self) -> Option<&'static str> {
unsafe {
let long_name = (*self.as_ptr()).long_name;
if long_name.is_null() {
None
} else {
Some(from_utf8_unchecked(CStr::from_ptr(long_name).to_bytes()))
}
}
}
pub fn props(self) -> CodecProperties {
unsafe { CodecProperties::from_bits_truncate((*self.as_ptr()).props) }
}
pub fn mime_types(self) -> Option<MimeTypeIter> {
unsafe { MimeTypeIter::from_raw((*self.as_ptr()).mime_types) }
}
pub fn profiles(self) -> Option<ProfileIter> {
unsafe {
if (*self.as_ptr()).profiles.is_null() {
None
} else {
Some(ProfileIter::new(self.id(), (*self.as_ptr()).profiles))
}
}
}
}
pub struct CodecDescriptorIter {
ptr: *const AVCodecDescriptor,
}
impl CodecDescriptorIter {
pub fn new() -> Self {
Self {
ptr: std::ptr::null(),
}
}
}
impl Default for CodecDescriptorIter {
fn default() -> Self {
Self::new()
}
}
impl Iterator for CodecDescriptorIter {
type Item = CodecDescriptor;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = avcodec_descriptor_next(self.ptr);
if let Some(desc) = CodecDescriptor::from_raw(next) {
self.ptr = next;
Some(desc)
} else {
None
}
}
}
}
pub struct MimeTypeIter {
ptr: NonNull<*const libc::c_char>,
}
impl MimeTypeIter {
pub unsafe fn from_raw(ptr: *const *const libc::c_char) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
}
impl Iterator for MimeTypeIter {
type Item = &'static str;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = *self.ptr.as_ptr();
if next.is_null() {
return None;
}
self.ptr = NonNull::new_unchecked(self.ptr.as_ptr().add(1));
Some(from_utf8_unchecked(CStr::from_ptr(next).to_bytes()))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::decoder::find;
#[test]
fn descriptor() {
let targa = find(Id::TARGA).expect("can find targa decoder");
let desc = targa.descriptor().expect("targa has descriptor");
assert_eq!(desc.id(), Id::TARGA);
assert_eq!(desc.kind(), media::Type::Video);
assert_eq!(desc.name(), "targa");
// --enable-small will remove all `long_name`s. So this can either be null/None
// or the correct description
assert!(matches!(
desc.description(),
None | Some("Truevision Targa image")
));
let props = desc.props();
assert!(
props.contains(CodecProperties::INTRA_ONLY)
&& props.contains(CodecProperties::LOSSLESS)
);
let mut mime_types = desc.mime_types().expect("has mime types");
assert_eq!(mime_types.next(), Some("image/x-targa"));
assert_eq!(mime_types.next(), Some("image/x-tga"));
assert_eq!(mime_types.next(), None);
}
}

View File

@ -30,7 +30,7 @@ impl Audio {
}
}
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> {
pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe {
if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -57,7 +57,7 @@ impl Audio {
}
}
pub fn open_as_with<T, E: traits::Encoder<T>>(
pub fn open_as_with<E: traits::Encoder>(
mut self,
codec: E,
options: Dictionary,

View File

@ -37,15 +37,25 @@ pub fn new() -> Encoder {
pub fn find(id: Id) -> Option<Codec> {
unsafe {
let ptr = avcodec_find_encoder(id.into());
Codec::from_raw(ptr)
let ptr = avcodec_find_encoder(id.into()) as *mut AVCodec;
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
pub fn find_by_name(name: &str) -> Option<Codec> {
unsafe {
let name = CString::new(name).unwrap();
let ptr = avcodec_find_encoder_by_name(name.as_ptr());
Codec::from_raw(ptr)
let ptr = avcodec_find_encoder_by_name(name.as_ptr()) as *mut AVCodec;
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}

View File

@ -20,7 +20,7 @@ impl Subtitle {
}
}
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> {
pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe {
if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -33,7 +33,7 @@ impl Subtitle {
}
}
pub fn open_as_with<T, E: traits::Encoder<T>>(
pub fn open_as_with<E: traits::Encoder>(
mut self,
codec: E,
options: Dictionary,

View File

@ -27,7 +27,7 @@ impl Video {
}
#[inline]
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> {
pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe {
if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -56,7 +56,7 @@ impl Video {
}
#[inline]
pub fn open_as_with<T, E: traits::Encoder<T>>(
pub fn open_as_with<E: traits::Encoder>(
mut self,
codec: E,
options: Dictionary,

View File

@ -11,13 +11,8 @@ pub mod subtitle;
#[cfg(not(feature = "ffmpeg_5_0"))]
pub mod picture;
pub mod descriptor;
pub use self::descriptor::CodecDescriptor;
pub mod discard;
pub mod config;
pub mod context;
pub use self::context::Context;
@ -25,11 +20,16 @@ pub mod capabilities;
pub use self::capabilities::Capabilities;
pub mod codec;
pub use self::codec::{Audio, Codec, Video};
pub mod parameters;
pub use self::parameters::Parameters;
pub mod video;
pub use self::video::Video;
pub mod audio;
pub use self::audio::Audio;
pub mod audio_service;
pub mod field_order;
@ -42,9 +42,6 @@ pub use self::debug::Debug;
pub mod profile;
pub use self::profile::Profile;
pub mod props;
pub use self::props::CodecProperties;
pub mod threading;
pub mod decoder;

View File

@ -387,31 +387,3 @@ impl From<Profile> for c_int {
}
}
}
pub struct ProfileIter {
id: Id,
ptr: *const AVProfile,
}
impl ProfileIter {
pub fn new(id: Id, ptr: *const AVProfile) -> Self {
ProfileIter { id, ptr }
}
}
impl Iterator for ProfileIter {
type Item = Profile;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if (*self.ptr).profile == FF_PROFILE_UNKNOWN {
return None;
}
let profile = Profile::from((self.id, (*self.ptr).profile));
self.ptr = self.ptr.offset(1);
Some(profile)
}
}
}

View File

@ -1,17 +0,0 @@
use crate::ffi::*;
use libc::c_int;
bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct CodecProperties: c_int {
const INTRA_ONLY = AV_CODEC_PROP_INTRA_ONLY;
const LOSSY = AV_CODEC_PROP_LOSSY;
const LOSSLESS = AV_CODEC_PROP_LOSSLESS;
const REORDER = AV_CODEC_PROP_REORDER;
#[cfg(feature = "ffmpeg_6_1")]
const FIELDS = AV_CODEC_PROP_FIELDS;
const BITMAP_SUB = AV_CODEC_PROP_BITMAP_SUB;
const TEXT_SUB = AV_CODEC_PROP_TEXT_SUB;
}
}

View File

@ -1,26 +1,25 @@
use super::codec::UnknownType;
use super::{decoder, encoder};
use crate::codec::Id;
use crate::codec::{Audio, Id, Video};
use crate::Codec;
pub trait Decoder<T> {
fn decoder(self) -> Option<Codec<T>>;
pub trait Decoder {
fn decoder(self) -> Option<Codec>;
}
impl<'a> Decoder<UnknownType> for &'a str {
fn decoder(self) -> Option<Codec<UnknownType>> {
impl<'a> Decoder for &'a str {
fn decoder(self) -> Option<Codec> {
decoder::find_by_name(self)
}
}
impl Decoder<UnknownType> for Id {
fn decoder(self) -> Option<Codec<UnknownType>> {
impl Decoder for Id {
fn decoder(self) -> Option<Codec> {
decoder::find(self)
}
}
impl<T> Decoder<T> for Codec<T> {
fn decoder(self) -> Option<Codec<T>> {
impl Decoder for Codec {
fn decoder(self) -> Option<Codec> {
if self.is_decoder() {
Some(self)
} else {
@ -29,30 +28,50 @@ impl<T> Decoder<T> for Codec<T> {
}
}
impl<T> Decoder<T> for Option<Codec<T>> {
fn decoder(self) -> Option<Codec<T>> {
impl Decoder for Option<Codec> {
fn decoder(self) -> Option<Codec> {
self.and_then(|c| c.decoder())
}
}
pub trait Encoder<T> {
fn encoder(self) -> Option<Codec<T>>;
impl Decoder for Audio {
fn decoder(self) -> Option<Codec> {
if self.is_decoder() {
Some(*self)
} else {
None
}
}
}
impl<'a> Encoder<UnknownType> for &'a str {
fn encoder(self) -> Option<Codec<UnknownType>> {
impl Decoder for Video {
fn decoder(self) -> Option<Codec> {
if self.is_decoder() {
Some(*self)
} else {
None
}
}
}
pub trait Encoder {
fn encoder(self) -> Option<Codec>;
}
impl<'a> Encoder for &'a str {
fn encoder(self) -> Option<Codec> {
encoder::find_by_name(self)
}
}
impl Encoder<UnknownType> for Id {
fn encoder(self) -> Option<Codec<UnknownType>> {
impl Encoder for Id {
fn encoder(self) -> Option<Codec> {
encoder::find(self)
}
}
impl<T> Encoder<T> for Codec<T> {
fn encoder(self) -> Option<Codec<T>> {
impl Encoder for Codec {
fn encoder(self) -> Option<Codec> {
if self.is_encoder() {
Some(self)
} else {
@ -61,8 +80,28 @@ impl<T> Encoder<T> for Codec<T> {
}
}
impl<T> Encoder<T> for Option<Codec<T>> {
fn encoder(self) -> Option<Codec<T>> {
impl Encoder for Option<Codec> {
fn encoder(self) -> Option<Codec> {
self.and_then(|c| c.encoder())
}
}
impl Encoder for Audio {
fn encoder(self) -> Option<Codec> {
if self.is_encoder() {
Some(*self)
} else {
None
}
}
}
impl Encoder for Video {
fn encoder(self) -> Option<Codec> {
if self.is_encoder() {
Some(*self)
} else {
None
}
}
}

100
src/codec/video.rs Normal file
View File

@ -0,0 +1,100 @@
use std::ops::Deref;
use super::codec::Codec;
use crate::ffi::*;
use crate::{format, Rational};
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Video {
codec: Codec,
}
impl Video {
pub unsafe fn new(codec: Codec) -> Video {
Video { codec }
}
}
impl Video {
pub fn rates(&self) -> Option<RateIter> {
unsafe {
if (*self.codec.as_ptr()).supported_framerates.is_null() {
None
} else {
Some(RateIter::new((*self.codec.as_ptr()).supported_framerates))
}
}
}
pub fn formats(&self) -> Option<FormatIter> {
unsafe {
if (*self.codec.as_ptr()).pix_fmts.is_null() {
None
} else {
Some(FormatIter::new((*self.codec.as_ptr()).pix_fmts))
}
}
}
}
impl Deref for Video {
type Target = Codec;
fn deref(&self) -> &Self::Target {
&self.codec
}
}
pub struct RateIter {
ptr: *const AVRational,
}
impl RateIter {
pub fn new(ptr: *const AVRational) -> Self {
RateIter { ptr }
}
}
impl Iterator for RateIter {
type Item = Rational;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if (*self.ptr).num == 0 && (*self.ptr).den == 0 {
return None;
}
let rate = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(rate)
}
}
}
pub struct FormatIter {
ptr: *const AVPixelFormat,
}
impl FormatIter {
pub fn new(ptr: *const AVPixelFormat) -> Self {
FormatIter { ptr }
}
}
impl Iterator for FormatIter {
type Item = format::Pixel;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == AVPixelFormat::AV_PIX_FMT_NONE {
return None;
}
let format = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(format)
}
}
}

View File

@ -3,60 +3,50 @@ use std::ptr;
use crate::ffi::*;
use crate::format;
pub struct AudioIter(*const AVInputFormat);
pub struct AudioIter(*mut AVInputFormat);
impl Iterator for AudioIter {
type Item = format::Input;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
let inner = self.0;
let ptr = av_input_audio_device_next(self.0) as *mut AVInputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_input_audio_device_next(inner);
if let Some(input) = format::Input::from_raw(ptr) {
self.0 = ptr;
Some(input)
} else {
if ptr.is_null() && !self.0.is_null() {
None
} else {
self.0 = ptr;
Some(format::Input::wrap(ptr))
}
}
}
}
pub fn audio() -> AudioIter {
AudioIter(ptr::null())
AudioIter(ptr::null_mut())
}
pub struct VideoIter(*const AVInputFormat);
pub struct VideoIter(*mut AVInputFormat);
impl Iterator for VideoIter {
type Item = format::Input;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
let inner = self.0;
let ptr = av_input_video_device_next(self.0) as *mut AVInputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_input_video_device_next(inner);
if let Some(input) = format::Input::from_raw(ptr) {
self.0 = ptr;
Some(input)
} else {
if ptr.is_null() && !self.0.is_null() {
None
} else {
self.0 = ptr;
Some(format::Input::wrap(ptr))
}
}
}
}
pub fn video() -> VideoIter {
VideoIter(ptr::null())
VideoIter(ptr::null_mut())
}

View File

@ -3,26 +3,21 @@ use std::ptr;
use crate::ffi::*;
use crate::format;
pub struct AudioIter(*const AVOutputFormat);
pub struct AudioIter(*mut AVOutputFormat);
impl Iterator for AudioIter {
type Item = format::Output;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
let inner = self.0;
let ptr = av_output_audio_device_next(self.0) as *mut AVOutputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_output_audio_device_next(inner);
if let Some(output) = format::Output::from_raw(ptr) {
self.0 = ptr;
Some(output)
} else {
if ptr.is_null() && !self.0.is_null() {
None
} else {
self.0 = ptr as *mut AVOutputFormat;
Some(format::Output::wrap(ptr))
}
}
}
@ -32,26 +27,21 @@ pub fn audio() -> AudioIter {
AudioIter(ptr::null_mut())
}
pub struct VideoIter(*const AVOutputFormat);
pub struct VideoIter(*mut AVOutputFormat);
impl Iterator for VideoIter {
type Item = format::Output;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
let inner = self.0;
let ptr = av_output_video_device_next(self.0) as *mut AVOutputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_output_video_device_next(inner);
if let Some(output) = format::Output::from_raw(ptr) {
self.0 = ptr;
Some(output)
} else {
if ptr.is_null() && !self.0.is_null() {
None
} else {
self.0 = ptr as *mut AVOutputFormat;
Some(format::Output::wrap(ptr))
}
}
}

View File

@ -1,10 +1,11 @@
use std::ffi::CString;
use std::mem;
use std::ops::{Bound, Deref, DerefMut, RangeBounds};
use std::ops::{Deref, DerefMut};
use super::common::Context;
use super::destructor;
use crate::ffi::*;
use crate::util::range::Range;
#[cfg(not(feature = "ffmpeg_5_0"))]
use crate::Codec;
use crate::{format, Error, Packet, Stream};
@ -35,14 +36,19 @@ impl Input {
impl Input {
pub fn format(&self) -> format::Input {
unsafe { format::Input::from_raw((*self.as_ptr()).iformat).expect("iformat is non-null") }
unsafe { format::Input::wrap((*self.as_ptr()).iformat as *mut AVInputFormat) }
}
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn video_codec(&self) -> Option<Codec> {
unsafe {
let ptr = (*self.as_ptr()).video_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
@ -50,7 +56,12 @@ impl Input {
pub fn audio_codec(&self) -> Option<Codec> {
unsafe {
let ptr = (*self.as_ptr()).audio_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
@ -58,7 +69,12 @@ impl Input {
pub fn subtitle_codec(&self) -> Option<Codec> {
unsafe {
let ptr = (*self.as_ptr()).subtitle_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
@ -66,7 +82,12 @@ impl Input {
pub fn data_codec(&self) -> Option<Codec> {
unsafe {
let ptr = (*self.as_ptr()).data_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
}
}
@ -96,21 +117,16 @@ impl Input {
}
}
pub fn seek<R: RangeBounds<i64>>(&mut self, ts: i64, range: R) -> Result<(), Error> {
pub fn seek<R: Range<i64>>(&mut self, ts: i64, range: R) -> Result<(), Error> {
unsafe {
let start = match range.start_bound().cloned() {
Bound::Included(i) => i,
Bound::Excluded(i) => i.saturating_add(1),
Bound::Unbounded => i64::MIN,
};
let end = match range.end_bound().cloned() {
Bound::Included(i) => i,
Bound::Excluded(i) => i.saturating_sub(1),
Bound::Unbounded => i64::MAX,
};
match avformat_seek_file(self.as_mut_ptr(), -1, start, ts, end, 0) {
match avformat_seek_file(
self.as_mut_ptr(),
-1,
range.start().cloned().unwrap_or(i64::MIN),
ts,
range.end().cloned().unwrap_or(i64::MAX),
0,
) {
s if s >= 0 => Ok(()),
e => Err(Error::from(e)),
}

View File

@ -35,7 +35,7 @@ impl Output {
impl Output {
pub fn format(&self) -> format::Output {
unsafe { format::Output::from_raw((*self.as_ptr()).oformat).expect("oformat is non-null") }
unsafe { format::Output::wrap((*self.as_ptr()).oformat as *mut AVOutputFormat) }
}
pub fn write_header(&mut self) -> Result<(), Error> {
@ -68,7 +68,7 @@ impl Output {
}
}
pub fn add_stream<T, E: traits::Encoder<T>>(&mut self, codec: E) -> Result<StreamMut, Error> {
pub fn add_stream<E: traits::Encoder>(&mut self, codec: E) -> Result<StreamMut, Error> {
unsafe {
let codec = codec.encoder();
let codec = codec.map_or(ptr::null(), |c| c.as_ptr());

View File

@ -1,37 +1,34 @@
use std::ptr::NonNull;
use crate::ffi::*;
use crate::utils;
use super::Flags;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Input {
ptr: NonNull<AVInputFormat>,
ptr: *mut AVInputFormat,
}
impl Input {
pub unsafe fn from_raw(ptr: *const AVInputFormat) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
pub unsafe fn wrap(ptr: *mut AVInputFormat) -> Self {
Input { ptr }
}
pub fn as_ptr(self) -> *const AVInputFormat {
self.ptr.as_ptr()
pub unsafe fn as_ptr(&self) -> *const AVInputFormat {
self.ptr as *const _
}
pub fn name(self) -> &'static str {
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVInputFormat {
self.ptr
}
}
impl Input {
pub fn name(&self) -> &str {
unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) }
}
pub fn description(self) -> &'static str {
pub fn description(&self) -> &str {
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
}
pub fn flags(self) -> Flags {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
pub fn extensions(self) -> Vec<&'static str> {
pub fn extensions(&self) -> Vec<&str> {
unsafe {
let ptr = (*self.as_ptr()).extensions;
@ -43,7 +40,7 @@ impl Input {
}
}
pub fn mime_types(self) -> Vec<&'static str> {
pub fn mime_types(&self) -> Vec<&str> {
unsafe {
let ptr = (*self.as_ptr()).mime_type;

View File

@ -26,7 +26,11 @@ impl Iterator for DemuxerIter {
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = av_demuxer_iterate(&mut self.ptr);
Input::from_raw(next)
if next.is_null() {
None
} else {
Some(Input::wrap(next as _))
}
}
}
}
@ -53,7 +57,11 @@ impl Iterator for MuxerIter {
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = av_muxer_iterate(&mut self.ptr);
Output::from_raw(next)
if next.is_null() {
None
} else {
Some(Output::wrap(next as _))
}
}
}
}

View File

@ -1,39 +1,40 @@
use std::path::Path;
use std::ffi::CString;
use std::ptr::{self, NonNull};
use std::ptr;
use super::Flags;
use crate::ffi::*;
use crate::{codec, media, utils};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Output {
ptr: NonNull<AVOutputFormat>,
ptr: *mut AVOutputFormat,
}
impl Output {
pub unsafe fn from_raw(ptr: *const AVOutputFormat) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
pub unsafe fn wrap(ptr: *mut AVOutputFormat) -> Self {
Output { ptr }
}
pub fn as_ptr(self) -> *const AVOutputFormat {
self.ptr.as_ptr()
pub unsafe fn as_ptr(&self) -> *const AVOutputFormat {
self.ptr as *const _
}
pub fn name(self) -> &'static str {
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVOutputFormat {
self.ptr
}
}
impl Output {
pub fn name(&self) -> &str {
unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) }
}
pub fn description(self) -> &'static str {
pub fn description(&self) -> &str {
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
}
pub fn flags(self) -> Flags {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
pub fn extensions(self) -> Vec<&'static str> {
pub fn extensions(&self) -> Vec<&str> {
unsafe {
let ptr = (*self.as_ptr()).extensions;
@ -45,7 +46,7 @@ impl Output {
}
}
pub fn mime_types(self) -> Vec<&'static str> {
pub fn mime_types(&self) -> Vec<&str> {
unsafe {
let ptr = (*self.as_ptr()).mime_type;
@ -57,9 +58,9 @@ impl Output {
}
}
pub fn codec<P: AsRef<Path>>(self, path: P, kind: media::Type) -> codec::Id {
pub fn codec<P: AsRef<Path>>(&self, path: &P, kind: media::Type) -> codec::Id {
// XXX: use to_cstring when stable
let path = CString::new(path.as_ref().to_str().unwrap()).unwrap();
let path = CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap();
unsafe {
codec::Id::from(av_guess_codec(
@ -71,4 +72,8 @@ impl Output {
))
}
}
pub fn flags(&self) -> Flags {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
}

View File

@ -23,6 +23,27 @@ use crate::ffi::*;
use crate::utils;
use crate::{Dictionary, Error};
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_all() {
unsafe {
av_register_all();
}
}
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_input(mut format: Input) {
unsafe {
av_register_input_format(format.as_mut_ptr());
}
}
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_output(mut format: Output) {
unsafe {
av_register_output_format(format.as_mut_ptr());
}
}
pub fn version() -> u32 {
unsafe { avformat_version() }
}
@ -36,11 +57,11 @@ pub fn license() -> &'static str {
}
// XXX: use to_cstring when stable
fn from_path<P: AsRef<Path>>(path: P) -> CString {
fn from_path<P: AsRef<Path>>(path: &P) -> CString {
CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
}
pub fn input<P: AsRef<Path>>(path: P) -> Result<context::Input, Error> {
pub fn input<P: AsRef<Path>>(path: &P) -> Result<context::Input, Error> {
unsafe {
let mut ps = ptr::null_mut();
let path = from_path(path);
@ -60,7 +81,7 @@ pub fn input<P: AsRef<Path>>(path: P) -> Result<context::Input, Error> {
}
pub fn input_with_dictionary<P: AsRef<Path>>(
path: P,
path: &P,
options: Dictionary,
) -> Result<context::Input, Error> {
unsafe {
@ -85,7 +106,10 @@ pub fn input_with_dictionary<P: AsRef<Path>>(
}
}
pub fn input_with_interrupt<P: AsRef<Path>, F>(path: P, closure: F) -> Result<context::Input, Error>
pub fn input_with_interrupt<P: AsRef<Path>, F>(
path: &P,
closure: F,
) -> Result<context::Input, Error>
where
F: FnMut() -> bool,
{
@ -108,7 +132,7 @@ where
}
}
pub fn output<P: AsRef<Path>>(path: P) -> Result<context::Output, Error> {
pub fn output<P: AsRef<Path>>(path: &P) -> Result<context::Output, Error> {
unsafe {
let mut ps = ptr::null_mut();
let path = from_path(path);
@ -124,7 +148,10 @@ pub fn output<P: AsRef<Path>>(path: P) -> Result<context::Output, Error> {
}
}
pub fn output_with<P: AsRef<Path>>(path: P, options: Dictionary) -> Result<context::Output, Error> {
pub fn output_with<P: AsRef<Path>>(
path: &P,
options: Dictionary,
) -> Result<context::Output, Error> {
unsafe {
let mut ps = ptr::null_mut();
let path = from_path(path);
@ -153,7 +180,7 @@ pub fn output_with<P: AsRef<Path>>(path: P, options: Dictionary) -> Result<conte
}
}
pub fn output_as<P: AsRef<Path>>(path: P, format: &str) -> Result<context::Output, Error> {
pub fn output_as<P: AsRef<Path>>(path: &P, format: &str) -> Result<context::Output, Error> {
unsafe {
let mut ps = ptr::null_mut();
let path = from_path(path);
@ -176,7 +203,7 @@ pub fn output_as<P: AsRef<Path>>(path: P, format: &str) -> Result<context::Outpu
}
pub fn output_as_with<P: AsRef<Path>>(
path: P,
path: &P,
format: &str,
options: Dictionary,
) -> Result<context::Output, Error> {

View File

@ -83,6 +83,11 @@ fn init_error() {
util::error::register_all();
}
#[cfg(all(feature = "format", not(feature = "ffmpeg_5_0")))]
fn init_format() {
format::register_all();
}
#[cfg(not(feature = "format"))]
fn init_format() {}
@ -104,6 +109,8 @@ fn init_filter() {}
pub fn init() -> Result<(), Error> {
init_error();
#[cfg(not(feature = "ffmpeg_5_0"))]
init_format();
init_device();
#[cfg(not(feature = "ffmpeg_5_0"))]
init_filter();

View File

@ -12,6 +12,7 @@ pub mod mathematics;
pub mod media;
pub mod option;
pub mod picture;
pub mod range;
pub mod rational;
pub mod time;

35
src/util/range.rs Normal file
View File

@ -0,0 +1,35 @@
use std::ops;
pub trait Range<T> {
fn start(&self) -> Option<&T> {
None
}
fn end(&self) -> Option<&T> {
None
}
}
impl<T> Range<T> for ops::Range<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
impl<T> Range<T> for ops::RangeTo<T> {
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
impl<T> Range<T> for ops::RangeFrom<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
}
impl<T> Range<T> for ops::RangeFull {}