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}