vtome/lib.rs
1//! Show an image or a video on a specific monitor — or on a specific
2//! quadrilateral of one — without FFmpeg and without a codec anyone charges
3//! for.
4//!
5//! ```text
6//! file.mp4 ──demux──▶ H.264 ──platform decode──▶ ┌─────────┐ ──▶ GPU ──▶ corner-pinned quad
7//! │ Frame │ on monitor 2
8//! file.webm ─demux──▶ AV1 ──────software────────▶ │ (YUV + │
9//! │ colour)│ ──▶ H.264 (OS) or AV1 out (import)
10//! image.png ──────────────image─────────────────▶└─────────┘
11//! ```
12//!
13//! # What this is for
14//!
15//! Putting a picture where you want it: on the second monitor, in a rectangle
16//! of the third, or into four arbitrary corners because the projector is not
17//! square to the wall. [`geometry::Quad`] is that last one, and it is
18//! perspective-correct — see its documentation for why the obvious
19//! implementation leaves a crease down the diagonal.
20//!
21//! Audio is deliberately absent. `atome` is the audio engine; every clip here
22//! follows one engine timeline, which with [`Audio::atome`](crate::Audio) *is*
23//! atome's output clock (see [`clock`]) — vtome never opens a device.
24//!
25//! # Nothing patent-pooled is ever bundled
26//!
27//! vtome reads H.264 and AV1, and writes them: H.264 by default, but only
28//! ever through the operating system's own codec — VideoToolbox today — whose
29//! vendor already holds the licence; AV1 through the bundled, royalty-free
30//! `rav1e`, wherever the OS has no H.264 encoder or when asked. HEVC and VP9
31//! are out of scope. [`import`](crate::import()) writes a proxy at the output
32//! path first and the full-quality file through a queue — see
33//! `planning/TODO.md` §14 and §15.
34//!
35//! # Everything heavy is optional
36//!
37//! A build that only decodes frames and hands them to somebody else's renderer
38//! must not compile a windowing library, a GPU abstraction, or a C toolchain.
39//!
40//! | Feature | What it adds |
41//! |---|---|
42//! | `demux` | MP4 and Matroska/WebM parsing (pure Rust) |
43//! | `image` | Still images |
44//! | `render` | GPU presentation via wgpu — a surface, not a window |
45//! | `window` | `render` plus winit, so vtome opens its own windows. Desktop only: builds nothing on iOS and Android |
46//! | `embed` | `render` against a surface someone else owns — the Tauri path |
47//! | `decode-*` | One decoder backend each |
48//! | `encode-platform`, `encode-av1`, `mux` | H.264 through the OS into MP4, AV1 through rav1e into WebM |
49//! | `transcode` (= `import`) | [`transcode`] and the [`import`](crate::import()) queue; rav1e always comes with it |
50//! | `atome`, `split-audio` | Video following atome's clock; a film's audio to FLAC on import |
51//!
52//! # A first look
53//!
54//! ```no_run
55//! use vtome::{identify, Placement, MonitorSelector};
56//!
57//! // What is this file, really? The extension is a claim; the bytes are not.
58//! let container = identify::identify_path("clip.mp4")?;
59//! println!("{container}");
60//!
61//! // Second monitor, top-left quarter of it.
62//! let placement = Placement::new(MonitorSelector::Index(1))
63//! .area(vtome::geometry::Rect::from_size(960.0, 540.0));
64//! # Ok::<(), vtome::Error>(())
65//! ```
66
67#![warn(missing_docs)]
68
69pub mod bitstream;
70pub mod clock;
71pub mod color;
72pub mod decode;
73mod error;
74
75/// A seekable byte source that can move to another thread — a file, a
76/// `Cursor`, an entry of an archive. What media is opened from when it is not
77/// a file of its own: [`open_media_reader`], [`Clip::video_reader`].
78pub trait ReadSeek: std::io::Read + std::io::Seek + Send + Sync {}
79
80impl<T: std::io::Read + std::io::Seek + Send + Sync> ReadSeek for T {}
81pub mod frame;
82pub mod geometry;
83pub mod identify;
84pub mod media;
85pub mod output;
86pub mod output_layer;
87pub mod placement;
88pub mod plugin;
89pub mod scale;
90
91// Writing: H.264 through the OS encoder, AV1 through the bundled rav1e.
92pub mod encode;
93
94#[cfg(all(feature = "encode-platform", target_vendor = "apple"))]
95pub mod encode_videotoolbox;
96
97#[cfg(feature = "encode-av1")]
98pub mod encode_av1;
99
100// Windows' own H.264 encoder and decoder.
101#[cfg(all(windows, any(feature = "decode-platform", feature = "encode-platform")))]
102pub mod media_foundation;
103
104// Android's own H.264 encoder and decoder.
105#[cfg(all(target_os = "android", any(feature = "decode-platform", feature = "encode-platform")))]
106pub mod media_codec;
107
108// Linux's — whatever its GStreamer has — loaded at run time.
109#[cfg(all(
110 unix,
111 not(target_vendor = "apple"),
112 not(target_os = "android"),
113 any(feature = "decode-platform", feature = "encode-platform")
114))]
115pub mod gstreamer;
116
117#[cfg(feature = "demux")]
118mod playback;
119
120#[cfg(feature = "demux")]
121pub mod demux;
122
123// Containers out: H.264 into MP4, AV1 into WebM.
124#[cfg(feature = "mux")]
125pub mod mux;
126
127// One file through decode, scale, encode, and mux.
128#[cfg(feature = "transcode")]
129pub mod transcode;
130
131// The import queue: a proxy at the output path now, the real file later.
132#[cfg(feature = "transcode")]
133pub mod import;
134
135// A source is a file opened through a demuxer, and the compositor and player
136// are built from sources, so all three need one.
137#[cfg(feature = "demux")]
138pub mod compositor;
139#[cfg(feature = "demux")]
140pub mod player;
141#[cfg(feature = "demux")]
142pub mod video_source;
143
144#[cfg(feature = "image")]
145pub mod still;
146
147#[cfg(feature = "render")]
148pub mod render;
149
150// vtome's own windows. Desktop only: a phone has one window, and it is the
151// application's (see the `window` feature in Cargo.toml).
152#[cfg(all(feature = "window", not(any(target_os = "ios", target_os = "android"))))]
153pub mod window;
154
155// A clip's soundtrack through atome, on the clock its pictures follow.
156#[cfg(all(feature = "atome", feature = "render", feature = "demux", feature = "image", any(all(feature = "window", not(any(target_os = "ios", target_os = "android"))), feature = "tauri")))]
157mod audio;
158
159// The engine an application drives: outputs per monitor, clips added and
160// stopped from any thread. Two hosts share it — vtome's own windows, and a
161// Tauri application's.
162#[cfg(all(
163 feature = "render",
164 feature = "demux",
165 feature = "image",
166 any(all(feature = "window", not(any(target_os = "ios", target_os = "android"))), feature = "tauri")
167))]
168mod engine;
169
170#[cfg(all(feature = "window", not(any(target_os = "ios", target_os = "android")), feature = "demux", feature = "image"))]
171mod standalone;
172
173#[cfg(feature = "tauri")]
174mod tauri_host;
175
176#[cfg(feature = "tauri")]
177mod tauri_native;
178
179#[cfg(all(feature = "decode-platform", target_vendor = "apple"))]
180pub mod decode_videotoolbox;
181
182#[cfg(feature = "decode-av1")]
183pub mod decode_av1;
184
185pub use clock::{Clock, Follower, MasterClock, Monotonic, Pacing, SharedClock};
186pub use color::ColorSpace;
187#[cfg(feature = "demux")]
188pub use compositor::{Compositor, CompositorStats};
189pub use decode::{Decoder, Hardware};
190pub use encode::Encoder;
191pub use error::{Error, Result};
192pub use frame::{Frame, FramePool, PixelFormat};
193pub use geometry::{Fit, Point, Quad, Rect};
194pub use identify::{identify_bytes, identify_path, Container, Encoding};
195pub use media::{MediaInfo, Packet, TrackInfo, TrackKind};
196pub use output::Output;
197pub use output_layer::OutputLayer;
198pub use placement::{Monitor, MonitorSelector, Placement, ResolvedPlacement};
199pub use plugin::Plugin;
200#[cfg(feature = "demux")]
201pub use player::Player;
202#[cfg(feature = "demux")]
203pub use video_source::{cache_frames, FrameCache, VideoSource};
204
205#[cfg(feature = "demux")]
206pub use demux::{open as open_media, open_reader as open_media_reader, Demuxer};
207
208#[cfg(feature = "image")]
209pub use still::load_image;
210
211#[cfg(feature = "transcode")]
212pub use import::{
213 import, import_progress, import_queue, import_with, ImportOptions, ImportQueue, JobId,
214 JobProgress, QueueConfig, Stage,
215};
216
217#[cfg(all(
218 feature = "render",
219 feature = "demux",
220 feature = "image",
221 any(all(feature = "window", not(any(target_os = "ios", target_os = "android"))), feature = "tauri")
222))]
223pub use engine::{Audio, Clip, ClipEnded, ClipId, Controls, Hold, OutputRect};
224
225#[cfg(all(feature = "window", not(any(target_os = "ios", target_os = "android")), feature = "demux", feature = "image"))]
226pub use standalone::Vtome;
227
228#[cfg(feature = "tauri")]
229pub use tauri_host::{Started, TauriVtome};