1#![warn(missing_docs)]
71#![warn(clippy::all)]
72#![warn(clippy::pedantic)]
73#![allow(clippy::module_name_repetitions)]
74#![allow(clippy::must_use_candidate)]
75
76pub mod builder;
77pub mod display;
78pub mod playback;
79pub mod types;
80
81pub use builder::{FFplayBuilder, FFplayProcess};
83pub use display::DisplayOptions;
84pub use playback::{PlaybackOptions, SyncType};
85pub use types::{
86 HwAccelOptions, KeyBinding, MouseAction, PlaybackState, ShowMode, VisualizationType,
87 VulkanOptions, WindowState,
88};
89
90pub use ffmpeg_common::{
92 get_version, Capabilities, Duration, Error, LogLevel, MediaPath, Result, StreamSpecifier,
93 StreamType, Version,
94};
95
96pub mod prelude {
98 pub use crate::{
99 FFplayBuilder, ShowMode, SyncType,
100 display::presets as display_presets,
101 playback::presets as playback_presets,
102 };
103 pub use ffmpeg_common::{Duration, MediaPath, Result, StreamSpecifier};
104}
105
106pub async fn play(path: impl Into<MediaPath>) -> Result<FFplayProcess> {
108 FFplayBuilder::play(path).spawn().await
109}
110
111pub async fn play_fullscreen(path: impl Into<MediaPath>) -> Result<FFplayProcess> {
113 FFplayBuilder::play_fullscreen(path).spawn().await
114}
115
116pub async fn play_audio(path: impl Into<MediaPath>) -> Result<FFplayProcess> {
118 FFplayBuilder::play_audio(path).spawn().await
119}
120
121pub async fn capabilities() -> Result<Capabilities> {
123 Capabilities::detect("ffplay").await
124}
125
126pub async fn is_available() -> bool {
128 ffmpeg_common::process::find_executable("ffplay").is_ok()
129}
130
131pub async fn version() -> Result<Version> {
133 get_version("ffplay").await
134}
135
136pub mod scenarios {
138 use super::*;
139 use crate::prelude::*;
140
141 pub fn stream_low_latency(url: impl Into<MediaPath>) -> FFplayBuilder {
143 FFplayBuilder::play(url)
144 .framedrop(true)
145 .infbuf(true)
146 .sync(SyncType::External)
147 .fast(true)
148 }
149
150 pub fn with_hw_accel(path: impl Into<MediaPath>) -> FFplayBuilder {
152 let mut builder = FFplayBuilder::play(path);
153
154 #[cfg(target_os = "linux")]
155 {
156 builder = builder.raw_args(["-hwaccel", "vaapi"]);
157 }
158 #[cfg(target_os = "macos")]
159 {
160 builder = builder.raw_args(["-hwaccel", "videotoolbox"]);
161 }
162 #[cfg(target_os = "windows")]
163 {
164 builder = builder.raw_args(["-hwaccel", "d3d11va"]);
165 }
166
167 builder
168 }
169
170 pub async fn video_wall(
172 paths: Vec<impl Into<MediaPath>>,
173 grid_width: u32,
174 grid_height: u32,
175 window_width: u32,
176 window_height: u32,
177 ) -> Result<Vec<FFplayProcess>> {
178 let mut players = Vec::new();
179
180 for (i, path) in paths.into_iter().enumerate() {
181 let row = i as u32 / grid_width;
182 let col = i as u32 % grid_width;
183
184 let x = col * window_width;
185 let y = row * window_height;
186
187 let player = FFplayBuilder::play(path)
188 .size(window_width, window_height)
189 .window_position(x as i32, y as i32)
190 .borderless(true)
191 .no_audio(i > 0) .spawn()
193 .await?;
194
195 players.push(player);
196 }
197
198 Ok(players)
199 }
200
201 pub fn with_subtitles(
203 video: impl Into<MediaPath>,
204 subtitle_file: impl Into<String>,
205 ) -> FFplayBuilder {
206 FFplayBuilder::play(video)
207 .video_filter(format!("subtitles={}", subtitle_file.into()))
208 }
209
210 pub fn with_aspect_ratio(path: impl Into<MediaPath>, ratio: &str) -> FFplayBuilder {
212 FFplayBuilder::play(path)
213 .video_filter(format!("setdar={}", ratio))
214 }
215
216 pub fn deinterlaced(path: impl Into<MediaPath>) -> FFplayBuilder {
218 FFplayBuilder::play(path)
219 .video_filter("yadif")
220 }
221
222 pub fn benchmark(path: impl Into<MediaPath>) -> FFplayBuilder {
224 FFplayBuilder::play(path)
225 .no_display(true)
226 .autoexit(true)
227 .raw_args(["-benchmark"])
228 }
229
230 pub fn animated_wallpaper(path: impl Into<MediaPath>) -> FFplayBuilder {
232 FFplayBuilder::play(path)
233 .fullscreen(true)
234 .loop_count(-1)
235 .no_audio(true)
236 .exitonkeydown(false)
237 .exitonmousedown(false)
238 }
239}
240
241pub mod utils {
243 use super::*;
244
245 pub fn calculate_window_size(
247 video_width: u32,
248 video_height: u32,
249 max_width: u32,
250 max_height: u32,
251 ) -> (u32, u32) {
252 let video_aspect = video_width as f64 / video_height as f64;
253 let max_aspect = max_width as f64 / max_height as f64;
254
255 if video_aspect > max_aspect {
256 let height = (max_width as f64 / video_aspect) as u32;
258 (max_width, height)
259 } else {
260 let width = (max_height as f64 * video_aspect) as u32;
262 (width, max_height)
263 }
264 }
265
266 pub fn pip_filter(
268 main_video: &str,
269 pip_video: &str,
270 pip_scale: f32,
271 pip_position: &str,
272 ) -> String {
273 format!(
274 "[0:v][1:v]scale=iw*{}:ih*{}[pip];[0:v][pip]overlay={}",
275 pip_scale, pip_scale, pip_position
276 )
277 }
278
279 pub fn side_by_side_filter() -> &'static str {
281 "[0:v]pad=iw*2:ih[bg];[bg][1:v]overlay=W/2:0"
282 }
283
284 pub fn get_help_text() -> String {
286 let bindings = types::get_key_bindings();
287 let mut help = String::from("FFplay Key Bindings:\n\n");
288
289 for (key, desc) in bindings {
290 help.push_str(&format!("{:<20} {}\n", key, desc));
291 }
292
293 help
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::prelude::*;
301
302 #[test]
303 fn test_basic_play() {
304 let builder = FFplayBuilder::play("test.mp4");
305 let command = builder.command().unwrap();
306 assert!(command.contains("ffplay"));
307 assert!(command.contains("test.mp4"));
308 }
309
310 #[test]
311 fn test_scenarios() {
312 let low_latency = scenarios::stream_low_latency("rtmp://example.com/live");
313 let args = low_latency.build_args().unwrap();
314 assert!(args.contains(&"-framedrop".to_string()));
315 assert!(args.contains(&"-infbuf".to_string()));
316 assert!(args.contains(&"-sync".to_string()));
317 assert!(args.contains(&"ext".to_string()));
318
319 let deinterlaced = scenarios::deinterlaced("interlaced.mp4");
320 let args = deinterlaced.build_args().unwrap();
321 assert!(args.contains(&"-vf".to_string()));
322 assert!(args.contains(&"yadif".to_string()));
323 }
324
325 #[test]
326 fn test_utils() {
327 let (w, h) = utils::calculate_window_size(1920, 1080, 1280, 720);
329 assert_eq!(w, 1280);
330 assert_eq!(h, 720);
331
332 let (w, h) = utils::calculate_window_size(1080, 1920, 1280, 720);
333 assert_eq!(w, 405);
334 assert_eq!(h, 720);
335
336 let help = utils::get_help_text();
338 assert!(help.contains("FFplay Key Bindings"));
339 assert!(help.contains("Quit"));
340 }
341}