1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! Frame & sample export for AI/CV — decode a video into packed RGB frames
//! (and audio into f32 PCM) in one pass, correctly.
//!
//! # Experimental (0.14)
//!
//! This module is **experimental**. Its API shape may be reshaped in 0.15;
//! within the 0.14.x line, patch releases will not break it. Correctness
//! defects (deadlocks, over-delivery, dropped errors, wrong strides, wrong
//! color) are **not** waived by this banner — they are release blockers.
//!
//! # What it does
//!
//! [`FrameExtractor`] runs a single decode → (optional resize) → RGB conversion
//! pass over one input and hands you owned, tightly packed [`VideoFrame`]s:
//!
//! ```no_run
//! use ez_ffmpeg::frame_export::{FrameExtractor, PixelLayout, Sampling};
//!
//! # fn main() -> Result<(), ez_ffmpeg::error::Error> {
//! // One frame per second, 224x224 RGB, for a CV pipeline.
//! for frame in FrameExtractor::new("input.mp4")
//! .sampling(Sampling::EverySec(1.0))
//! .width(224)
//! .height(224)
//! .pixel(PixelLayout::Rgb24)
//! .frames()?
//! {
//! let frame = frame?;
//! // frame.as_bytes(): width*height*3 tightly packed bytes, top-down.
//! let _ = (frame.width(), frame.height(), frame.pts_us());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Color correctness (the wedge)
//!
//! [`ColorPolicy::Tagged`] (the default) honors the frame's own colorspace tags
//! when converting YUV → RGB, so BT.709 (HD) content is not silently decoded as
//! BT.601. [`ColorPolicy::TaggedOrResolutionGuess`] additionally fills in
//! UNTAGGED frames with a per-frame resolution guess (height ≥ 720 → BT.709,
//! else BT.601) without ever overriding real tags. [`ColorPolicy::Force`] pins
//! a specific matrix/range for all frames.
//!
//! # Audio (PCM) export
//!
//! [`SampleExtractor`] is the audio sibling: it decodes one input's audio into
//! owned, interleaved `f32` PCM — the buffer layout whisper-rs / candle / ort
//! consume. Defaults preserve the source rate and channel layout (models that
//! expect a fixed shape need explicit normalization):
//! [`SampleExtractor::sample_rate`] and [`SampleExtractor::channels`] opt into
//! resample / channel conversion, and [`SampleExtractor::for_whisper`] presets
//! the 16 kHz mono that speech models expect.
//!
//! ```no_run
//! use ez_ffmpeg::frame_export::SampleExtractor;
//!
//! # fn main() -> Result<(), ez_ffmpeg::error::Error> {
//! // 16 kHz mono f32, ready to hand to an ASR model.
//! let pcm: Vec<f32> = SampleExtractor::for_whisper("input.mp4").collect_samples()?;
//! # let _ = pcm;
//! # Ok(())
//! # }
//! ```
//!
//! # Threading & teardown
//!
//! A run drives the normal scheduler (demux → decode → input frame pipeline →
//! filtergraph → null output) with the export sink mounted on the output frame
//! pipeline. Every run carries a dedicated input-pipeline thread (the
//! per-frame HDR guard / color stamp, plus the `UniformN` sampler when used)
//! between the decoder and the filtergraph, connected by a small bounded
//! channel hop. The returned [`FrameIter`] is `Send` (consume it on a worker
//! thread) and fused (exactly one terminal error, then `None` forever).
//! Dropping it early aborts the run cleanly — teardown drops the receiver
//! before aborting the scheduler, which may block until in-flight FFmpeg calls
//! return.
//!
//! # Non-goals (v1)
//!
//! Random access by index/timestamp, planar / >8-bit output, HDR tone mapping
//! (HDR input is a typed error — declared HDR fails at open time from stream
//! parameters, and decoded frames are re-checked at runtime, so a mid-stream
//! splice to HDR surfaces the same typed error instead of wrong colors; on
//! inputs with multiple video streams, set `video_stream_index` to keep that
//! runtime guard on the exported stream), GPU-frame export without download,
//! and Python bindings are out of scope.
pub use FrameExportError;
pub use VideoFrame;
pub use FrameIter;
pub use ;
pub use FrameExtractor;
// --- Audio (PCM) sample export ---
pub use SampleExtractor;
pub use SampleIter;
pub use Channels;
pub use AudioChunk;