falcon_ffmpeg/lib.rs
1//! falcon's fallback video decoder: FFmpeg, loaded at run time.
2//!
3//! macOS plays mp4, mov and m4v through AVFoundation; Matroska, WebM, AVI,
4//! WMV, FLV, Ogg, MPEG-TS and the rest go through FFmpeg instead, as Eagle
5//! falls back to its bundled player. falcon never links FFmpeg: the shared
6//! libraries are opened with `dlopen` when first needed (see [`ffi`]), so
7//! falcon builds and runs without them and only the fallback is missing.
8//! The libraries must be an LGPL build, decode and the atempo filter only,
9//! such as the one `scripts/build-ffmpeg.sh` makes; any GPL or nonfree build
10//! is refused.
11//!
12//! - [`probe`] reads a file's duration, size and tracks.
13//! - [`Grabber`] decodes single pictures, scaled, for previews; [`frame_at`]
14//! one picture at full size; [`thumbnail`] the import thumbnail.
15//! - [`Player`] plays a file: worker threads decode ahead, the owner takes
16//! each frame when it is due, and [`AudioOut`] carries the sound to the
17//! caller's audio output, which drives the clock.
18//! - [`Tempo`] changes the sound's speed and keeps its pitch, as `<video>`
19//! does by default in Eagle's main player.
20//! - [`AudioFile`] decodes a file's sound alone, for audio players.
21
22mod audio_file;
23mod decode;
24pub mod ffi;
25mod frame;
26mod media;
27mod player;
28pub mod sync;
29mod tempo;
30pub mod testing;
31
32pub use audio_file::AudioFile;
33pub use frame::Frame;
34pub use media::{Grabber, frame_at, probe, thumbnail};
35pub use player::{AudioOut, Player};
36pub use tempo::Tempo;
37
38/// What [`probe`] learns about a file.
39#[derive(Clone, Debug, PartialEq)]
40pub struct Info {
41 /// Seconds.
42 pub duration: f64,
43 /// The size the picture is shown at, after the pixel aspect ratio and
44 /// the stream's rotation.
45 pub width: u32,
46 pub height: u32,
47 /// Frames per second as the stream states it (0 if unknown).
48 pub fps: f32,
49 pub has_audio: bool,
50 /// Clockwise quarter turns the stream asks for (0 to 3).
51 pub turns: u8,
52}
53
54/// Why a file could not be opened or decoded.
55#[derive(Clone, Debug, PartialEq)]
56pub struct Error(pub String);
57
58impl std::fmt::Display for Error {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.write_str(&self.0)
61 }
62}
63
64impl std::error::Error for Error {}
65
66pub type Result<T> = std::result::Result<T, Error>;
67
68/// Whether FFmpeg can be used, and which: `Ok` with its version string and
69/// the folder it was loaded from.
70pub fn status() -> std::result::Result<(String, std::path::PathBuf), ffi::LoadError> {
71 ffi::api().map(|a| (a.version.clone(), a.dir.clone()))
72}
73
74/// Whether FFmpeg was found and can be used.
75pub fn available() -> bool {
76 ffi::api().is_ok()
77}
78
79/// File extensions macOS can't play that falcon hands to FFmpeg first:
80/// Eagle's video formats outside mp4, mov and m4v (and the 360, afx, vap
81/// and eva camera formats, which go to macOS first).
82pub const EXTENSIONS: [&str; 11] = ["mkv", "webm", "avi", "wmv", "flv", "f4v", "ts", "mts", "m2ts", "mpg", "3gp"];
83
84/// Whether files with extension `ext` (any case) go to FFmpeg first.
85pub fn handles(ext: &str) -> bool {
86 let ext = ext.to_ascii_lowercase();
87 EXTENSIONS.contains(&ext.as_str())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn fallback_formats() {
96 for ext in ["mkv", "WEBM", "avi", "wmv", "flv", "ts", "m2ts", "3gp"] {
97 assert!(handles(ext), "{ext}");
98 }
99 for ext in ["mp4", "mov", "m4v", "png", "", "ogv", "rm", "ogg"] {
100 assert!(!handles(ext), "{ext}");
101 }
102 }
103}