Compare commits
No commits in common. "bdc65868e76f241bb88e89c69464156c80bea51a" and "ef14631f71dd6b791c32505ea9ea4bf24a2ed757" have entirely different histories.
bdc65868e7
...
ef14631f71
61
.github/workflows/build.yml
vendored
61
.github/workflows/build.yml
vendored
@ -23,49 +23,30 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-test-lint-linux:
|
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
|
runs-on: ubuntu-22.04
|
||||||
|
container: jrottenberg/ffmpeg:${{ matrix.ffmpeg_version }}-ubuntu
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
ffmpeg:
|
ffmpeg_version:
|
||||||
- version: "4.2"
|
- "4.2"
|
||||||
file: "ffmpeg-4.2-linux-gcc.tar.xz"
|
- "4.3"
|
||||||
- version: "4.3"
|
- "4.4"
|
||||||
file: "ffmpeg-4.3-linux-gcc.tar.xz"
|
- "5.0"
|
||||||
- version: "4.4"
|
- "5.1"
|
||||||
file: "ffmpeg-4.4-linux-clang-default.tar.xz"
|
- "6.0"
|
||||||
lib_subdir: "amd64"
|
- "6.1"
|
||||||
- version: "5.1"
|
- "7.0"
|
||||||
file: "ffmpeg-5.1-linux-clang-default.tar.xz"
|
- "7.1"
|
||||||
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"
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
apt-get update
|
||||||
sudo apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends clang curl pkg-config
|
||||||
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"
|
|
||||||
- name: Install Rust stable with clippy and rustfmt
|
- name: Install Rust stable with clippy and rustfmt
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@ -74,7 +55,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prefix-key: "v3-rust"
|
prefix-key: "v3-rust"
|
||||||
# Only save cache for one FFmpeg version
|
# Only save cache for one FFmpeg version
|
||||||
save-if: ${{ matrix.ffmpeg.version == '7.1' }}
|
save-if: ${{ matrix.ffmpeg_version == '7.1' }}
|
||||||
|
|
||||||
- name: Check format
|
- name: Check format
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
@ -185,18 +166,14 @@ jobs:
|
|||||||
|
|
||||||
msrv:
|
msrv:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
container: jrottenberg/ffmpeg:7.0-ubuntu
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Download FFmpeg
|
- name: Install dependencies
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
mkdir ffmpeg-libs
|
apt-get update
|
||||||
curl -L "https://sourceforge.net/projects/avbuild/files/linux/ffmpeg-7.1-linux-clang-lite.tar.xz/download" \
|
apt-get install -y --no-install-recommends clang curl pkg-config
|
||||||
| 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"
|
|
||||||
# rust-version from Cargo.toml
|
# rust-version from Cargo.toml
|
||||||
- name: Install Rust 1.65.0
|
- name: Install Rust 1.65.0
|
||||||
uses: dtolnay/rust-toolchain@1.65.0
|
uses: dtolnay/rust-toolchain@1.65.0
|
||||||
|
@ -58,6 +58,7 @@ codec = ["ffmpeg-sys-the-third/avcodec"]
|
|||||||
device = ["ffmpeg-sys-the-third/avdevice", "format"]
|
device = ["ffmpeg-sys-the-third/avdevice", "format"]
|
||||||
filter = ["ffmpeg-sys-the-third/avfilter"]
|
filter = ["ffmpeg-sys-the-third/avfilter"]
|
||||||
format = ["ffmpeg-sys-the-third/avformat", "codec"]
|
format = ["ffmpeg-sys-the-third/avformat", "codec"]
|
||||||
|
resampling = ["ffmpeg-sys-the-third/avresample"]
|
||||||
postprocessing = ["ffmpeg-sys-the-third/postproc"]
|
postprocessing = ["ffmpeg-sys-the-third/postproc"]
|
||||||
software-resampling = ["ffmpeg-sys-the-third/swresample"]
|
software-resampling = ["ffmpeg-sys-the-third/swresample"]
|
||||||
software-scaling = ["ffmpeg-sys-the-third/swscale", "codec"]
|
software-scaling = ["ffmpeg-sys-the-third/swscale", "codec"]
|
||||||
|
23
README.md
23
README.md
@ -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.
|
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
|
**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.**
|
||||||
|
|
||||||
- [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.
|
|
||||||
|
|
||||||
## Minimum supported Rust version (MSRV)
|
## Minimum supported Rust version (MSRV)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::env;
|
|||||||
fn main() {
|
fn main() {
|
||||||
ffmpeg::init().unwrap();
|
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) => {
|
Ok(ictx) => {
|
||||||
println!("Nb chapters: {}", ictx.nb_chapters());
|
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() {
|
for chapter in ictx.chapters() {
|
||||||
let title = match chapter.metadata().get("title") {
|
let title = match chapter.metadata().get("title") {
|
||||||
|
@ -20,7 +20,7 @@ fn main() {
|
|||||||
println!("\t profiles: none");
|
println!("\t profiles: none");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(video) = codec.video() {
|
if let Ok(video) = codec.video() {
|
||||||
if let Some(rates) = video.rates() {
|
if let Some(rates) = video.rates() {
|
||||||
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
||||||
} else {
|
} 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() {
|
if let Some(rates) = audio.rates() {
|
||||||
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
||||||
} else {
|
} else {
|
||||||
@ -71,7 +71,7 @@ fn main() {
|
|||||||
println!("\t profiles: {:?}", profiles.collect::<Vec<_>>());
|
println!("\t profiles: {:?}", profiles.collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(video) = codec.video() {
|
if let Ok(video) = codec.video() {
|
||||||
if let Some(rates) = video.rates() {
|
if let Some(rates) = video.rates() {
|
||||||
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
||||||
} else {
|
} 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() {
|
if let Some(rates) = audio.rates() {
|
||||||
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
println!("\t rates: {:?}", rates.collect::<Vec<_>>());
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,7 +11,7 @@ use std::io::prelude::*;
|
|||||||
fn main() -> Result<(), ffmpeg::Error> {
|
fn main() -> Result<(), ffmpeg::Error> {
|
||||||
ffmpeg::init().unwrap();
|
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
|
let input = ictx
|
||||||
.streams()
|
.streams()
|
||||||
.best(Type::Video)
|
.best(Type::Video)
|
||||||
|
@ -5,7 +5,7 @@ use std::env;
|
|||||||
fn main() -> Result<(), ffmpeg::Error> {
|
fn main() -> Result<(), ffmpeg::Error> {
|
||||||
ffmpeg::init().unwrap();
|
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) => {
|
Ok(context) => {
|
||||||
for (k, v) in context.metadata().iter() {
|
for (k, v) in context.metadata().iter() {
|
||||||
println!("{k}: {v}");
|
println!("{k}: {v}");
|
||||||
|
@ -72,7 +72,7 @@ struct Transcoder {
|
|||||||
fn transcoder<P: AsRef<Path>>(
|
fn transcoder<P: AsRef<Path>>(
|
||||||
ictx: &mut format::context::Input,
|
ictx: &mut format::context::Input,
|
||||||
octx: &mut format::context::Output,
|
octx: &mut format::context::Output,
|
||||||
path: P,
|
path: &P,
|
||||||
filter_spec: &str,
|
filter_spec: &str,
|
||||||
) -> Result<Transcoder, ffmpeg::Error> {
|
) -> Result<Transcoder, ffmpeg::Error> {
|
||||||
let input = ictx
|
let input = ictx
|
||||||
@ -83,8 +83,7 @@ fn transcoder<P: AsRef<Path>>(
|
|||||||
let mut decoder = context.decoder().audio()?;
|
let mut decoder = context.decoder().audio()?;
|
||||||
let codec = ffmpeg::encoder::find(octx.format().codec(path, media::Type::Audio))
|
let codec = ffmpeg::encoder::find(octx.format().codec(path, media::Type::Audio))
|
||||||
.expect("failed to find encoder")
|
.expect("failed to find encoder")
|
||||||
.audio()
|
.audio()?;
|
||||||
.expect("encoder is not audio encoder");
|
|
||||||
let global = octx
|
let global = octx
|
||||||
.format()
|
.format()
|
||||||
.flags()
|
.flags()
|
||||||
|
@ -58,6 +58,7 @@ avcodec = []
|
|||||||
avdevice = ["avformat"]
|
avdevice = ["avformat"]
|
||||||
avfilter = []
|
avfilter = []
|
||||||
avformat = ["avcodec"]
|
avformat = ["avcodec"]
|
||||||
|
avresample = []
|
||||||
postproc = []
|
postproc = []
|
||||||
swresample = []
|
swresample = []
|
||||||
swscale = []
|
swscale = []
|
||||||
|
@ -20,7 +20,6 @@ In addition to feature flags declared in `Cargo.toml`, this crate performs vario
|
|||||||
- "ffmpeg_6_0"
|
- "ffmpeg_6_0"
|
||||||
- "ffmpeg_6_1"
|
- "ffmpeg_6_1"
|
||||||
- "ffmpeg_7_0"
|
- "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.
|
- `avcodec_version_greater_than_<x>_<y>`, e.g., `avcodec_version_greater_than_58_90`. The name should be self-explanatory.
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ struct Library {
|
|||||||
optional: bool,
|
optional: bool,
|
||||||
features: &'static [AVFeature],
|
features: &'static [AVFeature],
|
||||||
headers: &'static [AVHeader],
|
headers: &'static [AVHeader],
|
||||||
min_major_version: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Library {
|
impl Library {
|
||||||
@ -30,14 +29,12 @@ impl Library {
|
|||||||
name: &'static str,
|
name: &'static str,
|
||||||
features: &'static [AVFeature],
|
features: &'static [AVFeature],
|
||||||
headers: &'static [AVHeader],
|
headers: &'static [AVHeader],
|
||||||
min_version: u64,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
optional: false,
|
optional: false,
|
||||||
features,
|
features,
|
||||||
headers,
|
headers,
|
||||||
min_major_version: min_version,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,14 +42,12 @@ impl Library {
|
|||||||
name: &'static str,
|
name: &'static str,
|
||||||
features: &'static [AVFeature],
|
features: &'static [AVFeature],
|
||||||
headers: &'static [AVHeader],
|
headers: &'static [AVHeader],
|
||||||
min_version: u64,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
optional: true,
|
optional: true,
|
||||||
features,
|
features,
|
||||||
headers,
|
headers,
|
||||||
min_major_version: min_version,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,14 +61,15 @@ impl Library {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LIBRARIES: &[Library] = &[
|
static LIBRARIES: &[Library] = &[
|
||||||
Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS, 50),
|
Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS),
|
||||||
Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS, 50),
|
Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS),
|
||||||
Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS, 50),
|
Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS),
|
||||||
Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS, 50),
|
Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS),
|
||||||
Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS, 0),
|
Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS),
|
||||||
Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS, 0),
|
Library::optional("avresample", AVRESAMPLE_FEATURES, AVRESAMPLE_HEADERS),
|
||||||
Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS, 0),
|
Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS),
|
||||||
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS, 50),
|
Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS),
|
||||||
|
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -220,6 +216,8 @@ static AVFILTER_FEATURES: &[AVFeature] = &[
|
|||||||
AVFeature::new("LINK_PUBLIC"),
|
AVFeature::new("LINK_PUBLIC"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static AVRESAMPLE_FEATURES: &[AVFeature] = &[];
|
||||||
|
|
||||||
static SWSCALE_FEATURES: &[AVFeature] = &[];
|
static SWSCALE_FEATURES: &[AVFeature] = &[];
|
||||||
|
|
||||||
static SWRESAMPLE_FEATURES: &[AVFeature] = &[];
|
static SWRESAMPLE_FEATURES: &[AVFeature] = &[];
|
||||||
@ -304,6 +302,7 @@ static AVFILTER_HEADERS: &[AVHeader] = &[
|
|||||||
AVHeader::new("buffersrc.h"),
|
AVHeader::new("buffersrc.h"),
|
||||||
AVHeader::new("avfilter.h"),
|
AVHeader::new("avfilter.h"),
|
||||||
];
|
];
|
||||||
|
static AVRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("avresample.h")];
|
||||||
static SWSCALE_HEADERS: &[AVHeader] = &[AVHeader::new("swscale.h")];
|
static SWSCALE_HEADERS: &[AVHeader] = &[AVHeader::new("swscale.h")];
|
||||||
static SWRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("swresample.h")];
|
static SWRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("swresample.h")];
|
||||||
static POSTPROC_HEADERS: &[AVHeader] = &[AVHeader::new("postprocess.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
|
// the binary using ffmpeg-sys cannot be redistributed
|
||||||
configure.switch("BUILD_LICENSE_NONFREE", "nonfree");
|
configure.switch("BUILD_LICENSE_NONFREE", "nonfree");
|
||||||
|
|
||||||
|
let ffmpeg_major_version: u32 = get_major_version(ffmpeg_version);
|
||||||
|
|
||||||
// configure building libraries based on features
|
// 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);
|
configure.switch(&lib.name.to_uppercase(), lib.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,22 +779,22 @@ fn check_features(include_paths: &[PathBuf]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for lib in enabled_libraries() {
|
let version_check_info = [("avcodec", 57, 62, 0, 101)];
|
||||||
let ver = if let Some(v) = versions.get(&lib.name) {
|
for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in
|
||||||
v
|
&version_check_info
|
||||||
} else {
|
{
|
||||||
continue;
|
let libversion = *versions
|
||||||
};
|
.get(lib)
|
||||||
for major in lib.min_major_version..=(*ver).0 {
|
.expect("Unable to find the version for lib{lib}");
|
||||||
for minor in 0..=135 {
|
|
||||||
if *ver >= (major, minor) {
|
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!(
|
println!(
|
||||||
r#"cargo:rustc-cfg=feature="{}_version_greater_than_{major}_{minor}""#,
|
r#"cargo:rustc-cfg=feature="{lib}_version_greater_than_{version_major}_{version_minor}""#
|
||||||
lib.name,
|
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
r#"cargo:{}_version_greater_than_{major}_{minor}=true"#,
|
r#"cargo:{lib}_version_greater_than_{version_major}_{version_minor}=true"#
|
||||||
lib.name,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -966,6 +971,7 @@ fn main() {
|
|||||||
.allowlist_file(r#".*[/\\]libavformat[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libavformat[/\\].*"#)
|
||||||
.allowlist_file(r#".*[/\\]libavdevice[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libavdevice[/\\].*"#)
|
||||||
.allowlist_file(r#".*[/\\]libavfilter[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libavfilter[/\\].*"#)
|
||||||
|
.allowlist_file(r#".*[/\\]libavresample[/\\].*"#)
|
||||||
.allowlist_file(r#".*[/\\]libswscale[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libswscale[/\\].*"#)
|
||||||
.allowlist_file(r#".*[/\\]libswresample[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libswresample[/\\].*"#)
|
||||||
.allowlist_file(r#".*[/\\]libpostproc[/\\].*"#)
|
.allowlist_file(r#".*[/\\]libpostproc[/\\].*"#)
|
||||||
|
215
src/codec/audio.rs
Normal file
215
src/codec/audio.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +1,30 @@
|
|||||||
use std::marker::PhantomData;
|
use super::{Audio, Capabilities, Id, Profile, Video};
|
||||||
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 crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::{media, utils};
|
use crate::{media, utils, Error};
|
||||||
|
|
||||||
#[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>;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
pub struct Codec<Type = UnknownType> {
|
pub struct Codec {
|
||||||
ptr: NonNull<AVCodec>,
|
ptr: *mut AVCodec,
|
||||||
_marker: PhantomData<Type>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
unsafe impl Send for Codec {}
|
||||||
pub struct UnknownType;
|
unsafe impl Sync for Codec {}
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub struct AudioType;
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub struct VideoType;
|
|
||||||
|
|
||||||
unsafe impl<T> Send for Codec<T> {}
|
impl Codec {
|
||||||
unsafe impl<T> Sync for Codec<T> {}
|
pub unsafe fn wrap(ptr: *mut AVCodec) -> Self {
|
||||||
|
Codec { ptr }
|
||||||
|
}
|
||||||
|
|
||||||
impl Codec<UnknownType> {
|
pub unsafe fn as_ptr(&self) -> *const AVCodec {
|
||||||
/// Create a new reference to a codec from a raw pointer.
|
self.ptr as *const _
|
||||||
///
|
}
|
||||||
/// Returns `None` if `ptr` is null.
|
|
||||||
pub unsafe fn from_raw(ptr: *const AVCodec) -> Option<Self> {
|
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVCodec {
|
||||||
NonNull::new(ptr as *mut _).map(|ptr| Self {
|
self.ptr
|
||||||
ptr,
|
|
||||||
_marker: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Codec<T> {
|
impl Codec {
|
||||||
pub fn as_ptr(&self) -> *const AVCodec {
|
|
||||||
self.ptr.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_encoder(&self) -> bool {
|
pub fn is_encoder(&self) -> bool {
|
||||||
unsafe { av_codec_is_encoder(self.as_ptr()) != 0 }
|
unsafe { av_codec_is_encoder(self.as_ptr()) != 0 }
|
||||||
}
|
}
|
||||||
@ -81,14 +53,13 @@ impl<T> Codec<T> {
|
|||||||
self.medium() == media::Type::Video
|
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 {
|
if self.medium() == media::Type::Video {
|
||||||
Some(Codec {
|
Ok(Video::new(self))
|
||||||
ptr: self.ptr,
|
|
||||||
_marker: PhantomData,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(Error::InvalidData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,14 +67,13 @@ impl<T> Codec<T> {
|
|||||||
self.medium() == media::Type::Audio
|
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 {
|
if self.medium() == media::Type::Audio {
|
||||||
Some(Codec {
|
Ok(Audio::new(self))
|
||||||
ptr: self.ptr,
|
|
||||||
_marker: PhantomData,
|
|
||||||
})
|
|
||||||
} else {
|
} 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> {
|
impl Iterator for ProfileIter {
|
||||||
/// Checks if the given sample rate is supported by this audio codec.
|
type Item = Profile;
|
||||||
#[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;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = self.ptr.as_ptr();
|
if (*self.ptr).profile == FF_PROFILE_UNKNOWN {
|
||||||
if *ptr == 0 {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = ChannelLayoutMask::from_bits_truncate(*ptr);
|
let profile = Profile::from((self.id, (*self.ptr).profile));
|
||||||
self.ptr = NonNull::new_unchecked(ptr.add(1));
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -62,7 +62,13 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn codec(&self) -> Option<Codec> {
|
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 {
|
pub fn medium(&self) -> media::Type {
|
||||||
|
@ -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 {
|
unsafe {
|
||||||
if let Some(codec) = codec.decoder() {
|
if let Some(codec) = codec.decoder() {
|
||||||
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
|
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,
|
mut self,
|
||||||
codec: D,
|
codec: D,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
|
@ -34,16 +34,25 @@ pub fn new() -> Decoder {
|
|||||||
|
|
||||||
pub fn find(id: Id) -> Option<Codec> {
|
pub fn find(id: Id) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = avcodec_find_decoder(id.into());
|
let ptr = avcodec_find_decoder(id.into()) as *mut AVCodec;
|
||||||
Codec::from_raw(ptr)
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Codec::wrap(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_name(name: &str) -> Option<Codec> {
|
pub fn find_by_name(name: &str) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = CString::new(name).unwrap();
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
unsafe {
|
||||||
if let Some(codec) = codec.encoder() {
|
if let Some(codec) = codec.encoder() {
|
||||||
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
|
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,
|
mut self,
|
||||||
codec: E,
|
codec: E,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
|
@ -37,15 +37,25 @@ pub fn new() -> Encoder {
|
|||||||
|
|
||||||
pub fn find(id: Id) -> Option<Codec> {
|
pub fn find(id: Id) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = avcodec_find_encoder(id.into());
|
let ptr = avcodec_find_encoder(id.into()) as *mut AVCodec;
|
||||||
Codec::from_raw(ptr)
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Codec::wrap(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_name(name: &str) -> Option<Codec> {
|
pub fn find_by_name(name: &str) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = CString::new(name).unwrap();
|
let name = CString::new(name).unwrap();
|
||||||
let ptr = avcodec_find_encoder_by_name(name.as_ptr());
|
let ptr = avcodec_find_encoder_by_name(name.as_ptr()) as *mut AVCodec;
|
||||||
Codec::from_raw(ptr)
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Codec::wrap(ptr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
unsafe {
|
||||||
if let Some(codec) = codec.encoder() {
|
if let Some(codec) = codec.encoder() {
|
||||||
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
|
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,
|
mut self,
|
||||||
codec: E,
|
codec: E,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
|
@ -27,7 +27,7 @@ impl Video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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 {
|
unsafe {
|
||||||
if let Some(codec) = codec.encoder() {
|
if let Some(codec) = codec.encoder() {
|
||||||
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
|
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
|
||||||
@ -56,7 +56,7 @@ impl Video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn open_as_with<T, E: traits::Encoder<T>>(
|
pub fn open_as_with<E: traits::Encoder>(
|
||||||
mut self,
|
mut self,
|
||||||
codec: E,
|
codec: E,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
|
@ -11,13 +11,8 @@ pub mod subtitle;
|
|||||||
#[cfg(not(feature = "ffmpeg_5_0"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
pub mod picture;
|
pub mod picture;
|
||||||
|
|
||||||
pub mod descriptor;
|
|
||||||
pub use self::descriptor::CodecDescriptor;
|
|
||||||
|
|
||||||
pub mod discard;
|
pub mod discard;
|
||||||
|
|
||||||
pub mod config;
|
|
||||||
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub use self::context::Context;
|
pub use self::context::Context;
|
||||||
|
|
||||||
@ -25,11 +20,16 @@ pub mod capabilities;
|
|||||||
pub use self::capabilities::Capabilities;
|
pub use self::capabilities::Capabilities;
|
||||||
|
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
pub use self::codec::{Audio, Codec, Video};
|
|
||||||
|
|
||||||
pub mod parameters;
|
pub mod parameters;
|
||||||
pub use self::parameters::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 audio_service;
|
||||||
pub mod field_order;
|
pub mod field_order;
|
||||||
|
|
||||||
@ -42,9 +42,6 @@ pub use self::debug::Debug;
|
|||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub use self::profile::Profile;
|
pub use self::profile::Profile;
|
||||||
|
|
||||||
pub mod props;
|
|
||||||
pub use self::props::CodecProperties;
|
|
||||||
|
|
||||||
pub mod threading;
|
pub mod threading;
|
||||||
|
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,25 @@
|
|||||||
use super::codec::UnknownType;
|
|
||||||
use super::{decoder, encoder};
|
use super::{decoder, encoder};
|
||||||
use crate::codec::Id;
|
use crate::codec::{Audio, Id, Video};
|
||||||
use crate::Codec;
|
use crate::Codec;
|
||||||
|
|
||||||
pub trait Decoder<T> {
|
pub trait Decoder {
|
||||||
fn decoder(self) -> Option<Codec<T>>;
|
fn decoder(self) -> Option<Codec>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Decoder<UnknownType> for &'a str {
|
impl<'a> Decoder for &'a str {
|
||||||
fn decoder(self) -> Option<Codec<UnknownType>> {
|
fn decoder(self) -> Option<Codec> {
|
||||||
decoder::find_by_name(self)
|
decoder::find_by_name(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decoder<UnknownType> for Id {
|
impl Decoder for Id {
|
||||||
fn decoder(self) -> Option<Codec<UnknownType>> {
|
fn decoder(self) -> Option<Codec> {
|
||||||
decoder::find(self)
|
decoder::find(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Decoder<T> for Codec<T> {
|
impl Decoder for Codec {
|
||||||
fn decoder(self) -> Option<Codec<T>> {
|
fn decoder(self) -> Option<Codec> {
|
||||||
if self.is_decoder() {
|
if self.is_decoder() {
|
||||||
Some(self)
|
Some(self)
|
||||||
} else {
|
} else {
|
||||||
@ -29,30 +28,50 @@ impl<T> Decoder<T> for Codec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Decoder<T> for Option<Codec<T>> {
|
impl Decoder for Option<Codec> {
|
||||||
fn decoder(self) -> Option<Codec<T>> {
|
fn decoder(self) -> Option<Codec> {
|
||||||
self.and_then(|c| c.decoder())
|
self.and_then(|c| c.decoder())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Encoder<T> {
|
impl Decoder for Audio {
|
||||||
fn encoder(self) -> Option<Codec<T>>;
|
fn decoder(self) -> Option<Codec> {
|
||||||
|
if self.is_decoder() {
|
||||||
|
Some(*self)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Encoder<UnknownType> for &'a str {
|
impl Decoder for Video {
|
||||||
fn encoder(self) -> Option<Codec<UnknownType>> {
|
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)
|
encoder::find_by_name(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder<UnknownType> for Id {
|
impl Encoder for Id {
|
||||||
fn encoder(self) -> Option<Codec<UnknownType>> {
|
fn encoder(self) -> Option<Codec> {
|
||||||
encoder::find(self)
|
encoder::find(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Encoder<T> for Codec<T> {
|
impl Encoder for Codec {
|
||||||
fn encoder(self) -> Option<Codec<T>> {
|
fn encoder(self) -> Option<Codec> {
|
||||||
if self.is_encoder() {
|
if self.is_encoder() {
|
||||||
Some(self)
|
Some(self)
|
||||||
} else {
|
} else {
|
||||||
@ -61,8 +80,28 @@ impl<T> Encoder<T> for Codec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Encoder<T> for Option<Codec<T>> {
|
impl Encoder for Option<Codec> {
|
||||||
fn encoder(self) -> Option<Codec<T>> {
|
fn encoder(self) -> Option<Codec> {
|
||||||
self.and_then(|c| c.encoder())
|
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
100
src/codec/video.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,60 +3,50 @@ use std::ptr;
|
|||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::format;
|
use crate::format;
|
||||||
|
|
||||||
pub struct AudioIter(*const AVInputFormat);
|
pub struct AudioIter(*mut AVInputFormat);
|
||||||
|
|
||||||
impl Iterator for AudioIter {
|
impl Iterator for AudioIter {
|
||||||
type Item = format::Input;
|
type Item = format::Input;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
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
|
if ptr.is_null() && !self.0.is_null() {
|
||||||
#[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 {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
self.0 = ptr;
|
||||||
|
|
||||||
|
Some(format::Input::wrap(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn audio() -> AudioIter {
|
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 {
|
impl Iterator for VideoIter {
|
||||||
type Item = format::Input;
|
type Item = format::Input;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
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
|
if ptr.is_null() && !self.0.is_null() {
|
||||||
#[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 {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
self.0 = ptr;
|
||||||
|
|
||||||
|
Some(format::Input::wrap(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video() -> VideoIter {
|
pub fn video() -> VideoIter {
|
||||||
VideoIter(ptr::null())
|
VideoIter(ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
@ -3,26 +3,21 @@ use std::ptr;
|
|||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::format;
|
use crate::format;
|
||||||
|
|
||||||
pub struct AudioIter(*const AVOutputFormat);
|
pub struct AudioIter(*mut AVOutputFormat);
|
||||||
|
|
||||||
impl Iterator for AudioIter {
|
impl Iterator for AudioIter {
|
||||||
type Item = format::Output;
|
type Item = format::Output;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
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
|
if ptr.is_null() && !self.0.is_null() {
|
||||||
#[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 {
|
|
||||||
None
|
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())
|
AudioIter(ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VideoIter(*const AVOutputFormat);
|
pub struct VideoIter(*mut AVOutputFormat);
|
||||||
|
|
||||||
impl Iterator for VideoIter {
|
impl Iterator for VideoIter {
|
||||||
type Item = format::Output;
|
type Item = format::Output;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||||
unsafe {
|
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
|
if ptr.is_null() && !self.0.is_null() {
|
||||||
#[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 {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
self.0 = ptr as *mut AVOutputFormat;
|
||||||
|
|
||||||
|
Some(format::Output::wrap(ptr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Bound, Deref, DerefMut, RangeBounds};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use super::common::Context;
|
use super::common::Context;
|
||||||
use super::destructor;
|
use super::destructor;
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
|
use crate::util::range::Range;
|
||||||
#[cfg(not(feature = "ffmpeg_5_0"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
use crate::Codec;
|
use crate::Codec;
|
||||||
use crate::{format, Error, Packet, Stream};
|
use crate::{format, Error, Packet, Stream};
|
||||||
@ -35,14 +36,19 @@ impl Input {
|
|||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn format(&self) -> format::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"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
pub fn video_codec(&self) -> Option<Codec> {
|
pub fn video_codec(&self) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).video_codec;
|
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> {
|
pub fn audio_codec(&self) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).audio_codec;
|
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> {
|
pub fn subtitle_codec(&self) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).subtitle_codec;
|
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> {
|
pub fn data_codec(&self) -> Option<Codec> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).data_codec;
|
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 {
|
unsafe {
|
||||||
let start = match range.start_bound().cloned() {
|
match avformat_seek_file(
|
||||||
Bound::Included(i) => i,
|
self.as_mut_ptr(),
|
||||||
Bound::Excluded(i) => i.saturating_add(1),
|
-1,
|
||||||
Bound::Unbounded => i64::MIN,
|
range.start().cloned().unwrap_or(i64::MIN),
|
||||||
};
|
ts,
|
||||||
|
range.end().cloned().unwrap_or(i64::MAX),
|
||||||
let end = match range.end_bound().cloned() {
|
0,
|
||||||
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) {
|
|
||||||
s if s >= 0 => Ok(()),
|
s if s >= 0 => Ok(()),
|
||||||
e => Err(Error::from(e)),
|
e => Err(Error::from(e)),
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ impl Output {
|
|||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
pub fn format(&self) -> format::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> {
|
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 {
|
unsafe {
|
||||||
let codec = codec.encoder();
|
let codec = codec.encoder();
|
||||||
let codec = codec.map_or(ptr::null(), |c| c.as_ptr());
|
let codec = codec.map_or(ptr::null(), |c| c.as_ptr());
|
||||||
|
@ -1,37 +1,34 @@
|
|||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
use super::Flags;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
ptr: NonNull<AVInputFormat>,
|
ptr: *mut AVInputFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub unsafe fn from_raw(ptr: *const AVInputFormat) -> Option<Self> {
|
pub unsafe fn wrap(ptr: *mut AVInputFormat) -> Self {
|
||||||
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
|
Input { ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_ptr(self) -> *const AVInputFormat {
|
pub unsafe fn as_ptr(&self) -> *const AVInputFormat {
|
||||||
self.ptr.as_ptr()
|
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) }
|
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("") }
|
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags(self) -> Flags {
|
pub fn extensions(&self) -> Vec<&str> {
|
||||||
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extensions(self) -> Vec<&'static str> {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).extensions;
|
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 {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).mime_type;
|
let ptr = (*self.as_ptr()).mime_type;
|
||||||
|
|
||||||
|
@ -26,7 +26,11 @@ impl Iterator for DemuxerIter {
|
|||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let next = av_demuxer_iterate(&mut self.ptr);
|
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> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let next = av_muxer_iterate(&mut self.ptr);
|
let next = av_muxer_iterate(&mut self.ptr);
|
||||||
Output::from_raw(next)
|
if next.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Output::wrap(next as _))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,40 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::ptr::{self, NonNull};
|
use std::ptr;
|
||||||
|
|
||||||
use super::Flags;
|
use super::Flags;
|
||||||
use crate::ffi::*;
|
use crate::ffi::*;
|
||||||
use crate::{codec, media, utils};
|
use crate::{codec, media, utils};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
ptr: NonNull<AVOutputFormat>,
|
ptr: *mut AVOutputFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Output {
|
impl Output {
|
||||||
pub unsafe fn from_raw(ptr: *const AVOutputFormat) -> Option<Self> {
|
pub unsafe fn wrap(ptr: *mut AVOutputFormat) -> Self {
|
||||||
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
|
Output { ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_ptr(self) -> *const AVOutputFormat {
|
pub unsafe fn as_ptr(&self) -> *const AVOutputFormat {
|
||||||
self.ptr.as_ptr()
|
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) }
|
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("") }
|
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags(self) -> Flags {
|
pub fn extensions(&self) -> Vec<&str> {
|
||||||
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extensions(self) -> Vec<&'static str> {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).extensions;
|
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 {
|
unsafe {
|
||||||
let ptr = (*self.as_ptr()).mime_type;
|
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
|
// 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 {
|
unsafe {
|
||||||
codec::Id::from(av_guess_codec(
|
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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,27 @@ use crate::ffi::*;
|
|||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::{Dictionary, Error};
|
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 {
|
pub fn version() -> u32 {
|
||||||
unsafe { avformat_version() }
|
unsafe { avformat_version() }
|
||||||
}
|
}
|
||||||
@ -36,11 +57,11 @@ pub fn license() -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// XXX: use to_cstring when stable
|
// 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()
|
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 {
|
unsafe {
|
||||||
let mut ps = ptr::null_mut();
|
let mut ps = ptr::null_mut();
|
||||||
let path = from_path(path);
|
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>>(
|
pub fn input_with_dictionary<P: AsRef<Path>>(
|
||||||
path: P,
|
path: &P,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
) -> Result<context::Input, Error> {
|
) -> Result<context::Input, Error> {
|
||||||
unsafe {
|
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
|
where
|
||||||
F: FnMut() -> bool,
|
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 {
|
unsafe {
|
||||||
let mut ps = ptr::null_mut();
|
let mut ps = ptr::null_mut();
|
||||||
let path = from_path(path);
|
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 {
|
unsafe {
|
||||||
let mut ps = ptr::null_mut();
|
let mut ps = ptr::null_mut();
|
||||||
let path = from_path(path);
|
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 {
|
unsafe {
|
||||||
let mut ps = ptr::null_mut();
|
let mut ps = ptr::null_mut();
|
||||||
let path = from_path(path);
|
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>>(
|
pub fn output_as_with<P: AsRef<Path>>(
|
||||||
path: P,
|
path: &P,
|
||||||
format: &str,
|
format: &str,
|
||||||
options: Dictionary,
|
options: Dictionary,
|
||||||
) -> Result<context::Output, Error> {
|
) -> Result<context::Output, Error> {
|
||||||
|
@ -83,6 +83,11 @@ fn init_error() {
|
|||||||
util::error::register_all();
|
util::error::register_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "format", not(feature = "ffmpeg_5_0")))]
|
||||||
|
fn init_format() {
|
||||||
|
format::register_all();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "format"))]
|
#[cfg(not(feature = "format"))]
|
||||||
fn init_format() {}
|
fn init_format() {}
|
||||||
|
|
||||||
@ -104,6 +109,8 @@ fn init_filter() {}
|
|||||||
|
|
||||||
pub fn init() -> Result<(), Error> {
|
pub fn init() -> Result<(), Error> {
|
||||||
init_error();
|
init_error();
|
||||||
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
|
init_format();
|
||||||
init_device();
|
init_device();
|
||||||
#[cfg(not(feature = "ffmpeg_5_0"))]
|
#[cfg(not(feature = "ffmpeg_5_0"))]
|
||||||
init_filter();
|
init_filter();
|
||||||
|
@ -12,6 +12,7 @@ pub mod mathematics;
|
|||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod option;
|
pub mod option;
|
||||||
pub mod picture;
|
pub mod picture;
|
||||||
|
pub mod range;
|
||||||
pub mod rational;
|
pub mod rational;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
||||||
|
35
src/util/range.rs
Normal file
35
src/util/range.rs
Normal 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 {}
|
Loading…
x
Reference in New Issue
Block a user