scuffle_ffmpeg/lib.rs
1//! A crate designed to provide a simple interface to the native ffmpeg c-bindings.
2#![cfg_attr(feature = "docs", doc = "\n\nSee the [changelog][changelog] for a full release history.")]
3#![cfg_attr(feature = "docs", doc = "## Feature flags")]
4#![cfg_attr(feature = "docs", doc = document_features::document_features!())]
5//! ## Why do we need this?
6//!
7//! This crate aims to provide a simple-safe interface to the native ffmpeg c-bindings.
8//!
9//! Currently this crate only supports the latest versions of ffmpeg (7.x.x).
10//!
11//! ## How is this different from other ffmpeg crates?
12//!
13//! The other main ffmpeg crate is [ffmpeg-next](https://github.com/zmwangx/rust-ffmpeg).
14//!
15//! This crate adds a few features and has a safer API. Notably it adds the ability to provide an in-memory decode / encode buffer.
16//!
17//! ## Examples
18//!
19//! ### Decoding a audio/video file
20//!
21//! ```rust
22//! # use std::path::PathBuf;
23//! # use scuffle_ffmpeg::AVMediaType;
24//! # fn file_path(item: &str) -> PathBuf {
25//! # if let Some(env) = std::env::var_os("ASSETS_DIR") {
26//! # PathBuf::from(env).join(item)
27//! # } else {
28//! # PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../../assets/{item}"))
29//! # }
30//! # }
31//! # fn test_fn() -> Result<(), Box<dyn std::error::Error>> {
32//! # let path = file_path("avc_aac.mp4");
33//! // 1. Store the input of the file from the path `path`
34//! // this can be any seekable io stream (std::io::Read + std::io::Seek)
35//! // if you don't have seek, you can just use Input::new(std::io::Read) (no seeking support)
36//! let mut input = scuffle_ffmpeg::io::Input::seekable(std::fs::File::open(path)?)?;
37//! // 2. Get the streams from the input
38//! let streams = input.streams();
39//!
40//! dbg!(&streams);
41//!
42//! // 3. Find the best audio & video streams.
43//! let best_video_stream = streams.best(AVMediaType::Video).expect("no video stream found");
44//! let best_audio_stream = streams.best(AVMediaType::Audio).expect("no audio stream found");
45//!
46//! dbg!(&best_video_stream);
47//! dbg!(&best_audio_stream);
48//!
49//! // 4. Create a decoder for each stream
50//! let mut video_decoder = scuffle_ffmpeg::decoder::Decoder::new(&best_video_stream)?
51//! .video()
52//! .expect("not an video decoder");
53//! let mut audio_decoder = scuffle_ffmpeg::decoder::Decoder::new(&best_audio_stream)?
54//! .audio()
55//! .expect("not an audio decoder");
56//!
57//! dbg!(&video_decoder);
58//! dbg!(&audio_decoder);
59//!
60//! // 5. Get the stream index of the video and audio streams.
61//! let video_stream_index = best_video_stream.index();
62//! let audio_stream_index = best_audio_stream.index();
63//!
64//! // 6. Iterate over the packets in the input.
65//! for packet in input.packets() {
66//! let packet = packet?;
67//! // 7. Send the packet to the respective decoder.
68//! // 8. Receive the frame from the decoder.
69//! if packet.stream_index() == video_stream_index {
70//! video_decoder.send_packet(&packet)?;
71//! while let Some(frame) = video_decoder.receive_frame()? {
72//! dbg!(&frame);
73//! }
74//! } else if packet.stream_index() == audio_stream_index {
75//! audio_decoder.send_packet(&packet)?;
76//! while let Some(frame) = audio_decoder.receive_frame()? {
77//! dbg!(&frame);
78//! }
79//! }
80//! }
81//!
82//! // 9. Send the EOF to the decoders.
83//! video_decoder.send_eof()?;
84//! audio_decoder.send_eof()?;
85//!
86//! // 10. Receive the remaining frames from the decoders.
87//! while let Some(frame) = video_decoder.receive_frame()? {
88//! dbg!(&frame);
89//! }
90//!
91//! while let Some(frame) = audio_decoder.receive_frame()? {
92//! dbg!(&frame);
93//! }
94//! # Ok(())
95//! # }
96//! # test_fn().expect("failed to run test");
97//! ```
98//!
99//! ### Re-encoding a audio/video file
100//!
101//! ```rust
102//! # use std::path::PathBuf;
103//! # use scuffle_ffmpeg::{AVMediaType, AVCodecID};
104//! # use scuffle_ffmpeg::encoder::{AudioEncoderSettings, VideoEncoderSettings};
105//! # use scuffle_ffmpeg::io::OutputOptions;
106//! # use scuffle_ffmpeg::frame::AudioChannelLayout;
107//! # fn file_path(item: &str) -> PathBuf {
108//! # if let Some(env) = std::env::var_os("ASSETS_DIR") {
109//! # PathBuf::from(env).join(item)
110//! # } else {
111//! # PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../../assets/{item}"))
112//! # }
113//! # }
114//! # fn test_fn() -> Result<(), Box<dyn std::error::Error>> {
115//! # let path = file_path("avc_aac.mp4");
116//! // 1. Create an input for reading. In this case we open it from a std::fs::File, however
117//! // it can be from any seekable io stream (std::io::Read + std::io::Seek) for example a std::io::Cursor.
118//! // It can also be a non-seekable stream in that case you can use Input::new(std::io::Read)
119//! let input = scuffle_ffmpeg::io::Input::seekable(std::fs::File::open(path)?)?;
120//!
121//! // 2. Get the streams from the input.
122//! let streams = input.streams();
123//!
124//! // 3. Find the best audio & video streams.
125//! let best_video_stream = streams.best(AVMediaType::Video).expect("no video stream found");
126//! let best_audio_stream = streams.best(AVMediaType::Audio).expect("no audio stream found");
127//!
128//! // 4. Create a decoder for each stream
129//! let mut video_decoder = scuffle_ffmpeg::decoder::Decoder::new(&best_video_stream)?
130//! .video()
131//! .expect("not an video decoder");
132//! let mut audio_decoder = scuffle_ffmpeg::decoder::Decoder::new(&best_audio_stream)?
133//! .audio()
134//! .expect("not an audio decoder");
135//!
136//! // 5. Create an output for writing. In this case we use a std::io::Cursor,
137//! // however it can be any seekable io stream (std::io::Read + std::io::Seek)
138//! // for example a std::io::Cursor. It can also be a non-seekable stream
139//! // in that case you can use Output::new(std::io::Read)
140//! let mut output = scuffle_ffmpeg::io::Output::seekable(
141//! std::io::Cursor::new(Vec::new()),
142//! OutputOptions::builder().format_name("mp4")?.build(),
143//! )?;
144//!
145//! // 6. Find encoders for the streams by name or codec
146//! let h264 = scuffle_ffmpeg::codec::EncoderCodec::new(AVCodecID::H264)
147//! .expect("no h264 encoder found");
148//! let aac = scuffle_ffmpeg::codec::EncoderCodec::new(AVCodecID::Aac)
149//! .expect("no aac encoder found");
150//!
151//! // 7. Setup the settings for each encoder
152//! let video_settings = VideoEncoderSettings::builder()
153//! .width(video_decoder.width())
154//! .height(video_decoder.height())
155//! .frame_rate(video_decoder.frame_rate())
156//! .pixel_format(video_decoder.pixel_format())
157//! .build();
158//!
159//! let audio_settings = AudioEncoderSettings::builder()
160//! .sample_rate(audio_decoder.sample_rate())
161//! .ch_layout(AudioChannelLayout::new(
162//! audio_decoder.channels()
163//! ).expect("invalid channel layout"))
164//! .sample_fmt(audio_decoder.sample_format())
165//! .build();
166//!
167//! // 8. Initialize the encoders
168//! let mut video_encoder = scuffle_ffmpeg::encoder::Encoder::new(
169//! h264,
170//! &mut output,
171//! best_video_stream.time_base(),
172//! best_video_stream.time_base(),
173//! video_settings,
174//! ).expect("not an video encoder");
175//! let mut audio_encoder = scuffle_ffmpeg::encoder::Encoder::new(
176//! aac,
177//! &mut output,
178//! best_audio_stream.time_base(),
179//! best_audio_stream.time_base(),
180//! audio_settings,
181//! ).expect("not an audio encoder");
182//!
183//! // 9. Write the header to the output.
184//! output.write_header()?;
185//!
186//! loop {
187//! let mut audio_done = false;
188//! let mut video_done = false;
189//!
190//! // 10. Receive the frame from the decoders.
191//! // 11. Send the frame to the encoders.
192//! // 12. Receive the packet from the encoders.
193//! // 13. Write the packet to the output.
194//!
195//! if let Some(frame) = audio_decoder.receive_frame()? {
196//! audio_encoder.send_frame(&frame)?;
197//! while let Some(packet) = audio_encoder.receive_packet()? {
198//! output.write_packet(&packet)?;
199//! }
200//! } else {
201//! audio_done = true;
202//! }
203//!
204//! if let Some(frame) = video_decoder.receive_frame()? {
205//! video_encoder.send_frame(&frame)?;
206//! while let Some(packet) = video_encoder.receive_packet()? {
207//! output.write_packet(&packet)?;
208//! }
209//! } else {
210//! video_done = true;
211//! }
212//!
213//! // 14. Break the loop if both the audio and video are done.
214//! if audio_done && video_done {
215//! break;
216//! }
217//! }
218//!
219//! // 15. Send the EOF to the decoders.
220//! video_decoder.send_eof()?;
221//! audio_decoder.send_eof()?;
222//!
223//! // 16. Receive the remaining packets from the encoders.
224//! while let Some(packet) = video_encoder.receive_packet()? {
225//! output.write_packet(&packet)?;
226//! }
227//!
228//! while let Some(packet) = audio_encoder.receive_packet()? {
229//! output.write_packet(&packet)?;
230//! }
231//!
232//! // 17. Write the trailer to the output.
233//! output.write_trailer()?;
234//!
235//! // 18. Do something with the output data (write to disk, upload to s3, etc).
236//! let output_data = output.into_inner();
237//! # drop(output_data);
238//! # Ok(())
239//! # }
240//! # test_fn().expect("failed to run test");
241//! ```
242//!
243//! ## License
244//!
245//! This project is licensed under the MIT or Apache-2.0 license.
246//! You can choose between one of them if you use this work.
247//!
248//! `SPDX-License-Identifier: MIT OR Apache-2.0`
249#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
250#![cfg_attr(docsrs, feature(doc_auto_cfg))]
251#![deny(missing_docs)]
252#![deny(unreachable_pub)]
253#![deny(clippy::undocumented_unsafe_blocks)]
254#![deny(clippy::multiple_unsafe_ops_per_block)]
255
256/// Codec specific functionality.
257pub mod codec;
258/// Constants.
259pub mod consts;
260/// Decoder specific functionality.
261pub mod decoder;
262/// Dictionary specific functionality.
263pub mod dict;
264/// Encoder specific functionality.
265pub mod encoder;
266/// Error handling.
267pub mod error;
268/// Filter graph specific functionality.
269pub mod filter_graph;
270/// Frame specific functionality.
271pub mod frame;
272/// Input/Output specific functionality.
273pub mod io;
274/// Logging specific functionality.
275pub mod log;
276/// Packet specific functionality.
277pub mod packet;
278/// Rational number specific functionality.
279pub mod rational;
280/// [`frame::AudioFrame`] resampling and format conversion.
281pub mod resampler;
282/// Scaler specific functionality.
283pub mod scaler;
284/// Stream specific functionality.
285pub mod stream;
286/// Utility functionality.
287pub mod utils;
288
289#[cfg(test)]
290use std::path::PathBuf;
291
292pub use rusty_ffmpeg::ffi;
293
294mod smart_object;
295
296mod enums;
297
298pub use enums::*;
299
300/// Changelogs generated by [scuffle_changelog]
301#[cfg(feature = "docs")]
302#[scuffle_changelog::changelog]
303pub mod changelog {}
304
305#[cfg(test)]
306fn file_path(item: &str) -> PathBuf {
307 if let Some(env) = std::env::var_os("ASSETS_DIR") {
308 PathBuf::from(env).join(item)
309 } else {
310 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!("../../assets/{item}"))
311 }
312}