av_decoders/lib.rs
1//! This crate providers ready-made decoders for use with the rust-av ecosystem.
2//! Each decoder will output structs from the `v_frame` crate.
3//!
4//! Only the y4m decoder is enabled by default.
5//! Others must be enabled via Cargo features, since they require external dependencies.
6
7#[cfg(feature = "vapoursynth")]
8use std::collections::HashMap;
9use std::fs::File;
10use std::io::{BufReader, Read, stdin};
11use std::path::Path;
12use v_frame::chroma::ChromaSubsampling;
13use v_frame::frame::Frame;
14use v_frame::pixel::Pixel;
15#[cfg(feature = "vapoursynth")]
16use vapoursynth::node::Node;
17#[cfg(feature = "vapoursynth")]
18use vapoursynth::prelude::Environment;
19
20mod error;
21mod helpers {
22 #[cfg(feature = "ffmpeg")]
23 pub(crate) mod ffmpeg;
24 #[cfg(feature = "ffms2")]
25 pub(crate) mod ffms2;
26 #[cfg(feature = "vapoursynth")]
27 pub(crate) mod vapoursynth;
28 pub(crate) mod y4m;
29}
30mod util;
31
32#[cfg(feature = "ffmpeg")]
33pub use crate::helpers::ffmpeg::FfmpegDecoder;
34#[cfg(feature = "ffms2")]
35pub use crate::helpers::ffms2::Ffms2Decoder;
36#[cfg(feature = "vapoursynth")]
37pub use crate::helpers::vapoursynth::ModifyNode;
38#[cfg(feature = "vapoursynth")]
39pub use crate::helpers::vapoursynth::VapoursynthDecoder;
40#[cfg(feature = "vapoursynth")]
41use crate::helpers::vapoursynth::{VariableName, VariableValue};
42pub use error::DecoderError;
43pub use num_rational::Rational32;
44pub use v_frame;
45pub use y4m::Decoder as Y4mDecoder;
46
47const Y4M_EXTENSIONS: &[&str] = &["y4m", "yuv"];
48
49// TODO: Get rid of these and make padding an optional parameter
50const SB_SIZE_LOG2: usize = 6;
51const SB_SIZE: usize = 1 << SB_SIZE_LOG2;
52const SUBPEL_FILTER_SIZE: usize = 8;
53const FRAME_MARGIN: usize = 16 + SUBPEL_FILTER_SIZE;
54const LUMA_PADDING: usize = SB_SIZE + FRAME_MARGIN;
55
56/// Video metadata and configuration details.
57///
58/// This struct contains essential information about a video stream that is needed
59/// for proper decoding and frame processing. All decoders will populate this
60/// information when initialized.
61#[derive(Debug, Clone, Copy)]
62pub struct VideoDetails {
63 /// The width of the video frame in pixels.
64 pub width: usize,
65 /// The height of the video frame in pixels.
66 pub height: usize,
67 /// The bit depth per color component (e.g., 8 for 8-bit, 10 for 10-bit video).
68 pub bit_depth: usize,
69 /// The chroma subsampling format used by the video.
70 ///
71 /// Common values include:
72 /// - `ChromaSubsampling::Yuv420` for 4:2:0 subsampling (most common)
73 /// - `ChromaSubsampling::Yuv422` for 4:2:2 subsampling
74 /// - `ChromaSubsampling::Yuv444` for 4:4:4 subsampling (no chroma subsampling)
75 /// - `ChromaSubsampling::Monochrome` for monochrome
76 pub chroma_sampling: ChromaSubsampling,
77 /// The frame rate of the video as a rational number (frames per second).
78 ///
79 /// Examples:
80 /// - `Rational32::new(30, 1)` for 30 fps
81 /// - `Rational32::new(24000, 1001)` for 23.976 fps (24000/1001)
82 /// - `Rational32::new(25, 1)` for 25 fps
83 pub frame_rate: Rational32,
84 /// The total number of frames in the video, if known.
85 pub total_frames: Option<usize>,
86}
87
88/// A set of possible configuration flags that are generic across all decoders.
89#[derive(Debug, Clone, Copy, Default)]
90pub struct DecoderConfig {
91 /// If `true`, the decoder will only fetch the luma planes from the video.
92 pub luma_only: bool,
93}
94
95#[cfg(test)]
96impl Default for VideoDetails {
97 #[inline]
98 fn default() -> Self {
99 VideoDetails {
100 width: 640,
101 height: 480,
102 bit_depth: 8,
103 chroma_sampling: ChromaSubsampling::Yuv420,
104 frame_rate: Rational32::new(30, 1),
105 total_frames: None,
106 }
107 }
108}
109
110/// A unified video decoder that can handle multiple video formats and sources.
111///
112/// The `Decoder` provides a consistent interface for decoding video frames from various
113/// sources and formats. It automatically selects the most appropriate decoding backend
114/// based on the input format and available features.
115///
116/// ## Supported Formats
117///
118/// - **Y4M files** (always available): Raw Y4M format files with `.y4m` or `.yuv` extensions
119/// - **General video files** (requires `ffmpeg` feature): Most common video formats via FFmpeg
120/// - **Advanced video processing** (requires `vapoursynth` feature): Enhanced format support via VapourSynth
121///
122/// ## Backend Priority
123///
124/// The decoder automatically selects backends in this order of preference:
125/// 1. **Y4M parser** - Used for Y4M files (fastest, lowest overhead)
126/// 2. **FFmpeg** - Used when available for faster decoding of a variety of formats
127/// 3. **VapourSynth** - Used as fallback when VapourSynth not available
128///
129/// ## Examples
130///
131/// ```no_run
132/// use av_decoders::Decoder;
133///
134/// // Decode from a file
135/// let mut decoder = Decoder::from_file("video.y4m")?;
136/// let details = decoder.get_video_details();
137/// println!("Video: {}x{} @ {} fps", details.width, details.height, details.frame_rate);
138///
139/// // Read frames
140/// while let Ok(frame) = decoder.read_video_frame::<u8>() {
141/// // Process the frame...
142/// }
143///
144/// // Decode from stdin
145/// let mut stdin_decoder = Decoder::from_stdin()?;
146/// let frame = stdin_decoder.read_video_frame::<u8>()?;
147/// # Ok::<(), av_decoders::DecoderError>(())
148/// ```
149pub struct Decoder {
150 decoder: DecoderImpl,
151 video_details: VideoDetails,
152 frames_read: usize,
153 config: DecoderConfig,
154}
155
156impl Decoder {
157 /// Creates a new decoder from a file path.
158 ///
159 /// This method automatically detects the input format and selects the most appropriate
160 /// decoder backend. It will prioritize Y4M files for performance, then Ffms2 for speed,
161 /// followed by Ffmpeg, and finally Vapoursynth.
162 ///
163 /// # Arguments
164 ///
165 /// * `input` - A path to the video file. Can be any type that implements `AsRef<Path>`,
166 /// such as `&str`, `String`, `Path`, or `PathBuf`.
167 ///
168 /// # Returns
169 ///
170 /// Returns a `Result` containing:
171 /// - `Ok(Decoder<BufReader<File>>)` - A successfully initialized decoder
172 /// - `Err(DecoderError)` - An error if the file cannot be read or decoded
173 ///
174 /// # Errors
175 ///
176 /// This method will return an error if:
177 /// - The file cannot be opened or read (`DecoderError::FileReadError`)
178 /// - No suitable decoder backend is available (`DecoderError::NoDecoder`)
179 /// - The file format is not supported or corrupted (`DecoderError::GenericDecodeError`)
180 /// - Required features are not enabled for the file format
181 ///
182 /// # Examples
183 ///
184 /// ```no_run
185 /// use av_decoders::Decoder;
186 /// use std::path::Path;
187 ///
188 /// // From string path
189 /// let decoder = Decoder::from_file("video.y4m")?;
190 ///
191 /// // From Path
192 /// let path = Path::new("video.mp4");
193 /// let decoder = Decoder::from_file(path)?;
194 ///
195 /// // From PathBuf
196 /// let pathbuf = std::env::current_dir()?.join("video.mkv");
197 /// let decoder = Decoder::from_file(pathbuf)?;
198 /// # Ok::<(), Box<dyn std::error::Error>>(())
199 /// ```
200 #[inline]
201 #[expect(clippy::allow_attributes)]
202 #[allow(
203 unreachable_code,
204 reason = "some branches are unreachable with some combinations of features"
205 )]
206 pub fn from_file<P: AsRef<Path>>(input: P) -> Result<Decoder, DecoderError> {
207 // A raw y4m parser is going to be the fastest with the least overhead,
208 // so we should use it if we have a y4m file.
209 let ext = input
210 .as_ref()
211 .extension()
212 .and_then(|ext| ext.to_str())
213 .map(|ext| ext.to_ascii_lowercase());
214 if let Some(ext) = ext.as_deref() {
215 if Y4M_EXTENSIONS.contains(&ext) {
216 let reader =
217 BufReader::new(File::open(input).map_err(|e| DecoderError::FileReadError {
218 cause: e.to_string(),
219 })?);
220 let decoder = DecoderImpl::Y4m(
221 y4m::decode(Box::new(reader) as Box<dyn Read>).map_err(|e| match e {
222 y4m::Error::EOF => DecoderError::EndOfFile,
223 _ => DecoderError::GenericDecodeError {
224 cause: e.to_string(),
225 },
226 })?,
227 );
228 let video_details = decoder.video_details()?;
229 return Ok(Decoder {
230 decoder,
231 video_details,
232 frames_read: 0,
233 config: DecoderConfig::default(),
234 });
235 }
236
237 #[cfg(feature = "vapoursynth")]
238 if ext == "vpy" {
239 // Decode vapoursynth script file input
240 let decoder =
241 DecoderImpl::Vapoursynth(VapoursynthDecoder::from_file(input, HashMap::new())?);
242 let video_details = decoder.video_details()?;
243 return Ok(Decoder {
244 decoder,
245 video_details,
246 frames_read: 0,
247 config: DecoderConfig::default(),
248 });
249 }
250 }
251
252 // Ffms2 is the fastest and most reliable, use it if available.
253 #[cfg(feature = "ffms2")]
254 {
255 let decoder = DecoderImpl::Ffms2(Ffms2Decoder::new(input)?);
256 let video_details = decoder.video_details()?;
257 return Ok(Decoder {
258 decoder,
259 video_details,
260 frames_read: 0,
261 config: DecoderConfig::default(),
262 });
263 }
264
265 // Ffmpeg is considerably faster at decoding, so we should prefer it over Vapoursynth
266 // for general use cases.
267 #[cfg(feature = "ffmpeg")]
268 {
269 let decoder = DecoderImpl::Ffmpeg(FfmpegDecoder::new(input)?);
270 let video_details = decoder.video_details()?;
271 return Ok(Decoder {
272 decoder,
273 video_details,
274 frames_read: 0,
275 config: DecoderConfig::default(),
276 });
277 }
278
279 #[cfg(feature = "vapoursynth")]
280 {
281 // Build a vapoursynth script and use that
282 use crate::util::escape_python_string;
283
284 let script = format!(
285 r#"
286import vapoursynth as vs
287core = vs.core
288clip = core.ffms2.Source("{}")
289clip.set_output()
290"#,
291 escape_python_string(
292 &std::path::absolute(input)
293 .map_err(|e| DecoderError::FileReadError {
294 cause: e.to_string()
295 })?
296 .to_string_lossy()
297 )
298 );
299 let decoder =
300 DecoderImpl::Vapoursynth(VapoursynthDecoder::from_script(&script, HashMap::new())?);
301 let video_details = decoder.video_details()?;
302 return Ok(Decoder {
303 decoder,
304 video_details,
305 frames_read: 0,
306 config: DecoderConfig::default(),
307 });
308 }
309
310 Err(DecoderError::NoDecoder)
311 }
312
313 /// Creates a new decoder from a VapourSynth script.
314 ///
315 /// This method allows you to create a decoder by providing a VapourSynth script directly
316 /// as a string, rather than reading from a file. This is useful for dynamic video processing
317 /// pipelines, custom filtering operations, or when you need to apply VapourSynth's advanced
318 /// video processing capabilities programmatically.
319 ///
320 /// VapourSynth scripts can include complex video processing operations, filters, and
321 /// transformations that are not available through simple file-based decoders. This method
322 /// provides access to the full power of the VapourSynth ecosystem.
323 ///
324 /// # Requirements
325 ///
326 /// This function is only available when the `vapoursynth` feature is enabled.
327 ///
328 /// # Arguments
329 ///
330 /// * `script` - A VapourSynth script as a string. The script should define a video node
331 /// that will be used as the source for decoding. The script must be valid VapourSynth
332 /// Python code that produces a video clip.
333 ///
334 /// * `variables` - Optional script variables as key-value pairs. These will be passed
335 /// to the VapourSynth environment and can be accessed within the script using
336 /// `vs.get_output()` or similar mechanisms. Pass `HashMap::new()` if no variables are needed.
337 ///
338 /// # Returns
339 ///
340 /// Returns a `Result` containing:
341 /// - `Ok(Decoder<BufReader<File>>)` - A successfully initialized decoder using the script
342 /// - `Err(DecoderError)` - An error if the script cannot be executed or produces invalid output
343 ///
344 /// # Errors
345 ///
346 /// This method will return an error if:
347 /// - The VapourSynth script contains syntax errors (`DecoderError::GenericDecodeError`)
348 /// - The script fails to execute or raises exceptions
349 /// - The script does not produce a valid video output
350 /// - Required VapourSynth plugins are not available
351 /// - The VapourSynth environment cannot be initialized
352 /// - The arguments cannot be set (`DecoderError::GenericDecodeError`)
353 ///
354 /// # Examples
355 ///
356 /// ```no_run
357 /// use av_decoders::Decoder;
358 /// use std::collections::HashMap;
359 ///
360 /// // Simple script that loads a video file
361 /// let script = r#"
362 /// import vapoursynth as vs
363 /// core = vs.core
364 /// clip = core.ffms2.Source("input.mkv")
365 /// clip.set_output()
366 /// "#;
367 ///
368 /// let decoder = Decoder::from_script(script, HashMap::new())?;
369 /// let details = decoder.get_video_details();
370 /// println!("Video: {}x{} @ {} fps", details.width, details.height, details.frame_rate);
371 ///
372 /// // Script with variables for dynamic processing
373 /// let script_with_args = r#"
374 /// import vapoursynth as vs
375 /// core = vs.core
376 ///
377 /// # Get variables passed from Rust
378 /// filename = vs.get_output().get("filename", "default.mkv")
379 /// resize_width = int(vs.get_output().get("width", "1920"))
380 ///
381 /// clip = core.ffms2.Source(filename)
382 /// clip = core.resize.Bicubic(clip, width=resize_width, height=clip.height * resize_width // clip.width)
383 /// clip.set_output()
384 /// "#;
385 ///
386 /// let mut variables = HashMap::new();
387 /// variables.insert("filename".to_string(), "video.mp4".to_string());
388 /// variables.insert("width".to_string(), "1280".to_string());
389 ///
390 /// let mut decoder = Decoder::from_script(script_with_args, variables)?;
391 ///
392 /// // Read frames from the processed video
393 /// while let Ok(frame) = decoder.read_video_frame::<u8>() {
394 /// // Process the filtered frame...
395 /// }
396 /// # Ok::<(), av_decoders::DecoderError>(())
397 /// ```
398 ///
399 /// ## Advanced Usage
400 ///
401 /// VapourSynth scripts can include complex filtering pipelines:
402 ///
403 /// ```no_run
404 /// # use av_decoders::Decoder;
405 /// use std::collections::HashMap;
406 ///
407 /// let advanced_script = r#"
408 /// import vapoursynth as vs
409 /// core = vs.core
410 ///
411 /// # Load source
412 /// clip = core.ffms2.Source("input.mkv")
413 ///
414 /// # Apply denoising
415 /// clip = core.bm3d.BM3D(clip, sigma=3.0)
416 ///
417 /// # Upscale using AI
418 /// clip = core.waifu2x.Waifu2x(clip, noise=1, scale=2)
419 ///
420 /// # Color correction
421 /// clip = core.std.Levels(clip, min_in=16, max_in=235, min_out=0, max_out=255)
422 ///
423 /// clip.set_output()
424 /// "#;
425 ///
426 /// let decoder = Decoder::from_script(advanced_script, HashMap::new())?;
427 /// # Ok::<(), av_decoders::DecoderError>(())
428 /// ```
429 #[inline]
430 #[cfg(feature = "vapoursynth")]
431 pub fn from_script(
432 script: &str,
433 variables: HashMap<VariableName, VariableValue>,
434 ) -> Result<Decoder, DecoderError> {
435 let dec = VapoursynthDecoder::from_script(script, variables)?;
436 let decoder = DecoderImpl::Vapoursynth(dec);
437 let video_details = decoder.video_details()?;
438 Ok(Decoder {
439 decoder,
440 video_details,
441 frames_read: 0,
442 config: DecoderConfig::default(),
443 })
444 }
445
446 /// Creates a new decoder that reads from standard input (stdin).
447 ///
448 /// This method is useful for processing video data in pipelines or when the video
449 /// data is being streamed. Currently, only Y4M format is supported for stdin input.
450 ///
451 /// # Returns
452 ///
453 /// Returns a `Result` containing:
454 /// - `Ok(Decoder<BufReader<Stdin>>)` - A successfully initialized decoder reading from stdin
455 /// - `Err(DecoderError)` - An error if stdin cannot be read or the data is not valid Y4M
456 ///
457 /// # Errors
458 ///
459 /// This method will return an error if:
460 /// - The input stream is not in Y4M format
461 /// - The Y4M header is malformed or missing (`DecoderError::GenericDecodeError`)
462 /// - End of file is reached immediately (`DecoderError::EndOfFile`)
463 ///
464 /// # Examples
465 ///
466 /// ```no_run
467 /// use av_decoders::Decoder;
468 ///
469 /// // Read Y4M data from stdin
470 /// let mut decoder = Decoder::from_stdin()?;
471 ///
472 /// // Process frames as they arrive
473 /// loop {
474 /// match decoder.read_video_frame::<u8>() {
475 /// Ok(frame) => {
476 /// // Process the frame
477 /// println!("Received frame: {}x{}", frame.y_plane.width(), frame.y_plane.height());
478 /// }
479 /// Err(av_decoders::DecoderError::EndOfFile) => break,
480 /// Err(e) => return Err(e),
481 /// }
482 /// }
483 /// # Ok::<(), av_decoders::DecoderError>(())
484 /// ```
485 ///
486 /// ## Command Line Usage
487 ///
488 /// This is commonly used with command-line pipelines:
489 /// ```bash
490 /// # Pipe Y4M data to your application
491 /// ffmpeg -i input.mp4 -f yuv4mpegpipe - | your_app
492 ///
493 /// # Or directly from Y4M files
494 /// cat video.y4m | your_app
495 /// ```
496 #[inline]
497 pub fn from_stdin() -> Result<Decoder, DecoderError> {
498 // We can only support y4m for this
499 let reader = BufReader::new(stdin());
500 let decoder = DecoderImpl::Y4m(y4m::decode(Box::new(reader) as Box<dyn Read>).map_err(
501 |e| match e {
502 y4m::Error::EOF => DecoderError::EndOfFile,
503 _ => DecoderError::GenericDecodeError {
504 cause: e.to_string(),
505 },
506 },
507 )?);
508 let video_details: VideoDetails = decoder.video_details()?;
509 Ok(Decoder {
510 decoder,
511 video_details,
512 frames_read: 0,
513 config: DecoderConfig::default(),
514 })
515 }
516
517 /// Creates a new decoder from an existing decoder implementation.
518 ///
519 /// This method provides a way to construct a `Decoder` from a specific `DecoderImpl`
520 /// variant when you need direct control over the decoder backend selection. This is
521 /// typically used for advanced use cases where you want to bypass the automatic
522 /// format detection and backend selection logic of the other constructor methods.
523 ///
524 /// The method will extract the video metadata from the provided decoder implementation
525 /// and create a fully initialized `Decoder` instance ready for frame reading.
526 ///
527 /// # Arguments
528 ///
529 /// * `decoder_impl` - A specific decoder implementation variant (`DecoderImpl`).
530 /// This can be one of:
531 /// - `DecoderImpl::Y4m` for Y4M format decoding
532 /// - `DecoderImpl::Vapoursynth` for VapourSynth-based decoding (requires `vapoursynth` feature)
533 /// - `DecoderImpl::Ffmpeg` for FFmpeg-based decoding (requires `ffmpeg` feature)
534 /// - `DecoderImpl::Ffms2` for FFMS2-based decoding (requires `ffms2` feature)
535 ///
536 /// # Returns
537 ///
538 /// Returns a `Result` containing:
539 /// - `Ok(Decoder)` - A successfully initialized decoder using the provided implementation
540 /// - `Err(DecoderError)` - An error if video details cannot be extracted from the implementation
541 ///
542 /// # Errors
543 ///
544 /// This method will return an error if:
545 /// - The decoder implementation is not properly initialized
546 /// - Video metadata cannot be extracted from the implementation (`DecoderError::GenericDecodeError`)
547 /// - The implementation is in an invalid state
548 ///
549 /// # Examples
550 ///
551 /// ```no_run
552 /// use av_decoders::{Decoder, DecoderImpl, Y4mDecoder};
553 /// use std::fs::File;
554 /// use std::io::{BufReader, Read};
555 ///
556 /// // Create a Y4M decoder implementation directly
557 /// let file = File::open("video.y4m")?;
558 /// let reader = BufReader::new(file);
559 /// let y4m_decoder = Y4mDecoder::new(Box::new(reader) as Box<dyn Read>)?;
560 /// let decoder_impl = DecoderImpl::Y4m(y4m_decoder);
561 ///
562 /// // Create a Decoder from the implementation
563 /// let decoder = Decoder::from_decoder_impl(decoder_impl)?;
564 /// let details = decoder.get_video_details();
565 /// println!("Video: {}x{}", details.width, details.height);
566 /// # Ok::<(), Box<dyn std::error::Error>>(())
567 /// ```
568 ///
569 /// ## Use Cases
570 ///
571 /// This method is particularly useful when:
572 /// - You need to pre-configure a specific decoder backend
573 /// - You want to bypass automatic format detection
574 /// - You're implementing custom decoder initialization logic
575 /// - You need to reuse or transfer decoder implementations between contexts
576 ///
577 /// ## Note
578 ///
579 /// This is an advanced method that exposes internal decoder implementation details.
580 /// In most cases, you should prefer using `from_file()`, `from_script()`, or
581 /// `from_stdin()` which provide safer, higher-level interfaces with automatic
582 /// format detection and backend selection.
583 #[inline]
584 pub fn from_decoder_impl(decoder_impl: DecoderImpl) -> Result<Decoder, DecoderError> {
585 let video_details = decoder_impl.video_details()?;
586 Ok(Decoder {
587 decoder: decoder_impl,
588 video_details,
589 frames_read: 0,
590 config: DecoderConfig::default(),
591 })
592 }
593
594 /// Returns the video metadata and configuration details.
595 ///
596 /// This method provides access to the essential video properties that were detected
597 /// during decoder initialization. The returned reference is valid for the lifetime
598 /// of the decoder and the values will not change during decoding.
599 ///
600 /// # Returns
601 ///
602 /// Returns a reference to `VideoDetails` containing:
603 /// - `width` and `height` - Frame dimensions in pixels
604 /// - `bit_depth` - Bits per color component (8, 10, 12, etc.)
605 /// - `chroma_sampling` - Color subsampling format (4:2:0, 4:2:2, 4:4:4)
606 /// - `frame_rate` - Frames per second as a rational number
607 ///
608 /// # Examples
609 ///
610 /// ```no_run
611 /// use av_decoders::Decoder;
612 /// use v_frame::chroma::ChromaSubsampling;
613 ///
614 /// let decoder = Decoder::from_file("video.y4m").unwrap();
615 /// let details = decoder.get_video_details();
616 ///
617 /// println!("Resolution: {}x{}", details.width, details.height);
618 /// println!("Bit depth: {} bits", details.bit_depth);
619 /// println!("Frame rate: {} fps", details.frame_rate);
620 ///
621 /// match details.chroma_sampling {
622 /// ChromaSubsampling::Yuv420 => println!("4:2:0 chroma subsampling"),
623 /// ChromaSubsampling::Yuv422 => println!("4:2:2 chroma subsampling"),
624 /// ChromaSubsampling::Yuv444 => println!("4:4:4 chroma subsampling"),
625 /// _ => println!("Other chroma subsampling"),
626 /// }
627 /// # Ok::<(), av_decoders::DecoderError>(())
628 /// ```
629 #[inline]
630 #[must_use]
631 pub fn get_video_details(&self) -> &VideoDetails {
632 &self.video_details
633 }
634
635 /// Sets the decoder to only fetch the luma planes from the video.
636 /// This may improve performance for applications that do not need chroma data.
637 #[inline]
638 pub fn set_luma_only(&mut self, enabled: bool) {
639 self.config.luma_only = enabled;
640 }
641
642 /// Reads and decodes the next video frame from the input.
643 ///
644 /// This method advances the decoder to the next frame and returns it as a `Frame<T>`
645 /// where `T` is the pixel type. The pixel type must be compatible with the video's
646 /// bit depth and the decoder backend being used.
647 ///
648 /// # Type Parameters
649 ///
650 /// * `T` - The pixel type to use for the decoded frame. Must implement the `Pixel` trait.
651 /// Types include:
652 /// - `u8` for 8-bit video
653 /// - `u16` for 10-bit to 16-bit video
654 ///
655 /// # Returns
656 ///
657 /// Returns a `Result` containing:
658 /// - `Ok(Frame<T>)` - The decoded video frame
659 /// - `Err(DecoderError)` - An error if the frame cannot be read or decoded
660 ///
661 /// # Errors
662 ///
663 /// This method will return an error if:
664 /// - End of file/stream is reached (`DecoderError::EndOfFile`)
665 /// - The frame data is corrupted or invalid (`DecoderError::GenericDecodeError`)
666 /// - There's an I/O error reading the input (`DecoderError::FileReadError`)
667 /// - The pixel type is incompatible with the video format
668 ///
669 /// # Examples
670 ///
671 /// ```no_run
672 /// use av_decoders::Decoder;
673 ///
674 /// let mut decoder = Decoder::from_file("video.y4m").unwrap();
675 /// let details = decoder.get_video_details();
676 ///
677 /// // Read video frames, dynamically detecting the pixel type
678 /// if details.bit_depth > 8 {
679 /// while let Ok(frame) = decoder.read_video_frame::<u16>() {
680 /// println!("Frame size: {}x{}",
681 /// frame.y_plane.width(),
682 /// frame.y_plane.height()
683 /// );
684 /// // Process frame data...
685 /// }
686 /// } else {
687 /// while let Ok(frame) = decoder.read_video_frame::<u8>() {
688 /// println!("Frame size: {}x{}",
689 /// frame.y_plane.width(),
690 /// frame.y_plane.height()
691 /// );
692 /// // Process frame data...
693 /// }
694 /// }
695 /// ```
696 ///
697 /// ## Performance Notes
698 ///
699 /// - Frames are decoded sequentially; seeking may not be supported by all backends
700 /// - Each frame contains uncompressed pixel values, which results in heavy memory usage;
701 /// avoid keeping frames in memory for longer than needed
702 #[inline]
703 pub fn read_video_frame<T: Pixel>(&mut self) -> Result<Frame<T>, DecoderError> {
704 let result = self.decoder.read_video_frame(
705 &self.video_details,
706 #[cfg(any(feature = "ffmpeg", feature = "vapoursynth", feature = "ffms2"))]
707 self.frames_read,
708 self.config.luma_only,
709 );
710 if result.is_ok() {
711 self.frames_read += 1;
712 }
713 result
714 }
715
716 /// Reads and decodes the specified video frame from the input.
717 ///
718 /// This method decodes the specified frame and returns it as a `Frame<T>`
719 /// where `T` is the pixel type. The pixel type must be compatible with the video's
720 /// bit depth and the decoder backend being used.
721 ///
722 /// # Type Parameters
723 ///
724 /// * `T` - The pixel type to use for the decoded frame. Must implement the `Pixel` trait.
725 /// Types include:
726 /// - `u8` for 8-bit video
727 /// - `u16` for 10-bit to 16-bit video
728 ///
729 /// # Returns
730 ///
731 /// Returns a `Result` containing:
732 /// - `Ok(Frame<T>)` - The decoded video frame
733 /// - `Err(DecoderError)` - An error if the frame cannot be read or decoded
734 ///
735 /// # Errors
736 ///
737 /// This method will return an error if:
738 /// - End of file/stream is reached (`DecoderError::EndOfFile`)
739 /// - The frame data is corrupted or invalid (`DecoderError::GenericDecodeError`)
740 /// - There's an I/O error reading the input (`DecoderError::FileReadError`)
741 /// - The pixel type is incompatible with the video format
742 /// - The decoder does not support seeking (`DecoderError::UnsupportedDecoder`)
743 ///
744 /// # Examples
745 ///
746 /// ```no_run
747 /// use av_decoders::Decoder;
748 /// use std::collections::HashMap;
749 ///
750 /// // Simple script that loads a video file
751 /// let script = r#"
752 /// import vapoursynth as vs
753 /// core = vs.core
754 ///
755 /// clip = core.ffms2.Source('input.mp4')
756 /// clip.set_output()
757 /// "#;
758 ///
759 /// let mut decoder = Decoder::from_script(script, HashMap::new()).unwrap();
760 /// let details = decoder.get_video_details();
761 ///
762 /// // Get the 42nd video frame, dynamically detecting the pixel type
763 /// if details.bit_depth > 8 {
764 /// while let Ok(frame) = decoder.get_video_frame::<u16>(42) {
765 /// println!("Frame size: {}x{}",
766 /// frame.y_plane.width(),
767 /// frame.y_plane.height()
768 /// );
769 /// // Process frame data...
770 /// }
771 /// } else {
772 /// while let Ok(frame) = decoder.get_video_frame::<u8>(42) {
773 /// println!("Frame size: {}x{}",
774 /// frame.y_plane.width(),
775 /// frame.y_plane.height()
776 /// );
777 /// // Process frame data...
778 /// }
779 /// }
780 /// ```
781 ///
782 /// ## Performance Notes
783 ///
784 /// - Getting a specific video frame may not be supported by all backends
785 /// - Each frame contains uncompressed pixel values, which results in heavy memory usage;
786 /// avoid keeping frames in memory for longer than needed
787 #[inline]
788 #[cfg(feature = "vapoursynth")]
789 pub fn get_video_frame<T: Pixel>(
790 &mut self,
791 frame_index: usize,
792 ) -> Result<Frame<T>, DecoderError> {
793 self.decoder.get_video_frame(
794 #[cfg(feature = "vapoursynth")]
795 &self.video_details,
796 #[cfg(feature = "vapoursynth")]
797 frame_index,
798 self.config.luma_only,
799 )
800 }
801
802 /// Returns a mutable reference to the VapourSynth environment.
803 ///
804 /// This method provides direct access to the VapourSynth environment when using
805 /// a VapourSynth-based decoder. The environment can be used for advanced
806 /// VapourSynth operations, plugin loading, or creating additional video nodes
807 /// and filters programmatically.
808 ///
809 /// This is particularly useful when you need to:
810 /// - Load additional VapourSynth plugins
811 /// - Create custom filtering pipelines
812 /// - Access VapourSynth's core functionality directly
813 /// - Integrate with existing VapourSynth workflows
814 ///
815 /// # Requirements
816 ///
817 /// This method is only available when:
818 /// - The `vapoursynth` feature is enabled
819 /// - The decoder is using the VapourSynth backend (created via `from_file()` with
820 /// VapourSynth available, or via `from_script()`)
821 ///
822 /// # Returns
823 ///
824 /// Returns a `Result` containing:
825 /// - `Ok(&mut Environment)` - A mutable reference to the VapourSynth environment
826 /// - `Err(DecoderError::UnsupportedDecoder)` - If the current decoder is not using VapourSynth
827 ///
828 /// # Errors
829 ///
830 /// This method will return an error if:
831 /// - The decoder was not initialized with VapourSynth (e.g., using Y4M or FFmpeg backend)
832 ///
833 /// # Examples
834 ///
835 /// ```no_run
836 /// use av_decoders::Decoder;
837 ///
838 /// let mut decoder = Decoder::from_file("video.mkv")?;
839 ///
840 /// // Access the VapourSynth environment for advanced operations
841 /// if let Ok(env) = decoder.get_vapoursynth_env() {
842 /// // Load additional plugins
843 /// // Note: This is a simplified example - actual VapourSynth API usage
844 /// // would require more specific vapoursynth crate methods
845 /// println!("VapourSynth environment available");
846 ///
847 /// // You can now use the environment for advanced VapourSynth operations
848 /// // such as loading plugins, creating nodes, etc.
849 /// }
850 /// # Ok::<(), av_decoders::DecoderError>(())
851 /// ```
852 #[inline]
853 #[cfg(feature = "vapoursynth")]
854 pub fn get_vapoursynth_env(&mut self) -> Result<&mut Environment, DecoderError> {
855 match self.decoder {
856 DecoderImpl::Vapoursynth(ref mut dec) => Ok(dec.get_env()),
857 _ => Err(DecoderError::UnsupportedDecoder),
858 }
859 }
860
861 /// Returns the VapourSynth video node representing the decoded video stream.
862 ///
863 /// This method provides access to the underlying VapourSynth `Node` that represents
864 /// the video source. The node can be used for advanced VapourSynth operations,
865 /// creating additional processing pipelines, or integrating with other VapourSynth
866 /// workflows and tools.
867 ///
868 /// VapourSynth nodes are the fundamental building blocks of video processing
869 /// pipelines and can be used to:
870 /// - Apply additional filters and transformations
871 /// - Create branched processing pipelines
872 /// - Extract frame metadata and properties
873 /// - Implement custom frame processing logic
874 /// - Interface with other VapourSynth-based applications
875 ///
876 /// # Requirements
877 ///
878 /// This method is only available when:
879 /// - The `vapoursynth` feature is enabled
880 /// - The decoder is using the VapourSynth backend (created via `from_file()` with
881 /// VapourSynth available, or via `from_script()`)
882 ///
883 /// # Returns
884 ///
885 /// Returns a `Result` containing:
886 /// - `Ok(Node)` - The VapourSynth node representing the video stream
887 /// - `Err(DecoderError::UnsupportedDecoder)` - If the current decoder is not using VapourSynth
888 ///
889 /// # Errors
890 ///
891 /// This method will return an error if:
892 /// - The decoder was not initialized with VapourSynth (e.g., using Y4M or FFmpeg backend)
893 ///
894 /// # Examples
895 ///
896 /// ```no_run
897 /// use av_decoders::Decoder;
898 ///
899 /// let decoder = Decoder::from_file("video.mkv")?;
900 ///
901 /// // Get the VapourSynth node for advanced processing
902 /// if let Ok(node) = decoder.get_vapoursynth_node() {
903 /// // You can now use this node for additional VapourSynth operations
904 /// // Note: This example shows the concept - actual usage would depend
905 /// // on specific VapourSynth operations you want to perform
906 ///
907 /// println!("Got VapourSynth node");
908 ///
909 /// // Example: You could apply additional filters to this node
910 /// // let filtered_node = apply_custom_filter(node);
911 ///
912 /// // Or use it to create a new processing pipeline
913 /// // let output_node = create_processing_pipeline(node);
914 /// }
915 /// # Ok::<(), av_decoders::DecoderError>(())
916 /// ```
917 ///
918 /// ## Advanced Usage
919 ///
920 /// ```no_run
921 /// # use av_decoders::Decoder;
922 /// # use std::collections::HashMap;
923 /// // Create a decoder from a script
924 /// let script = r#"
925 /// import vapoursynth as vs
926 /// core = vs.core
927 /// clip = core.ffms2.Source("input.mkv")
928 /// clip.set_output()
929 /// "#;
930 ///
931 /// let decoder = Decoder::from_script(script, HashMap::new())?;
932 ///
933 /// // Get the node and use it for further processing
934 /// let node = decoder.get_vapoursynth_node()?;
935 ///
936 /// // Now you can integrate this node into larger VapourSynth workflows
937 /// // or apply additional processing that wasn't included in the original script
938 /// # Ok::<(), av_decoders::DecoderError>(())
939 /// ```
940 #[inline]
941 #[cfg(feature = "vapoursynth")]
942 pub fn get_vapoursynth_node(&self) -> Result<Node<'_>, DecoderError> {
943 match self.decoder {
944 DecoderImpl::Vapoursynth(ref dec) => Ok(dec.get_output_node()),
945 _ => Err(DecoderError::UnsupportedDecoder),
946 }
947 }
948}
949
950/// Internal enum representing different decoder backend implementations.
951///
952/// This enum is used internally by the `Decoder` struct to store the specific
953/// decoder implementation being used. The appropriate variant is selected automatically
954/// based on the input format and available features during decoder initialization.
955///
956/// Each variant wraps a different decoder backend, allowing the unified `Decoder`
957/// interface to support multiple video formats and processing libraries.
958pub enum DecoderImpl {
959 /// Y4M format decoder using the built-in y4m parser.
960 ///
961 /// This variant provides fast, low-overhead decoding of Y4M (YUV4MPEG2) format files.
962 /// It's always available and is preferred for Y4M files due to its performance characteristics.
963 /// The decoder reads from any source implementing the `Read` trait.
964 Y4m(Y4mDecoder<Box<dyn Read>>),
965
966 /// VapourSynth-based decoder for advanced video processing.
967 ///
968 /// This variant uses the VapourSynth framework for video decoding and processing.
969 /// It provides the most accurate metadata extraction and supports complex video
970 /// processing pipelines. Only available when the `vapoursynth` feature is enabled.
971 #[cfg(feature = "vapoursynth")]
972 Vapoursynth(VapoursynthDecoder),
973
974 /// FFmpeg-based decoder for general video format support.
975 ///
976 /// This variant uses FFmpeg for broad video format compatibility, serving as a
977 /// fallback decoder for formats not handled by other backends. Only available
978 /// when the `ffmpeg` feature is enabled.
979 #[cfg(feature = "ffmpeg")]
980 Ffmpeg(FfmpegDecoder),
981
982 /// FFMS2-based decoder for general video format support.
983 ///
984 /// This variant uses FFMS2 for broad video format compatibility, serving as a
985 /// fallback decoder for formats not handled by other backends. Only available
986 /// when the `ffms2` feature is enabled.
987 #[cfg(feature = "ffms2")]
988 Ffms2(Ffms2Decoder),
989}
990
991impl DecoderImpl {
992 pub(crate) fn video_details(&self) -> Result<VideoDetails, DecoderError> {
993 match self {
994 Self::Y4m(dec) => Ok(helpers::y4m::get_video_details(dec)),
995 #[cfg(feature = "vapoursynth")]
996 Self::Vapoursynth(dec) => dec.get_video_details(),
997 #[cfg(feature = "ffmpeg")]
998 Self::Ffmpeg(dec) => Ok(dec.video_details),
999 #[cfg(feature = "ffms2")]
1000 Self::Ffms2(dec) => Ok(dec.video_details),
1001 }
1002 }
1003
1004 pub(crate) fn read_video_frame<T: Pixel>(
1005 &mut self,
1006 cfg: &VideoDetails,
1007 #[cfg(any(feature = "ffmpeg", feature = "vapoursynth", feature = "ffms2"))]
1008 frame_index: usize,
1009 luma_only: bool,
1010 ) -> Result<Frame<T>, DecoderError> {
1011 match self {
1012 Self::Y4m(dec) => {
1013 helpers::y4m::read_video_frame::<Box<dyn Read>, T>(dec, cfg, luma_only)
1014 }
1015 #[cfg(feature = "vapoursynth")]
1016 Self::Vapoursynth(dec) => dec.read_video_frame::<T>(cfg, frame_index, luma_only),
1017 #[cfg(feature = "ffmpeg")]
1018 Self::Ffmpeg(dec) => dec.read_video_frame::<T>(frame_index, luma_only),
1019 #[cfg(feature = "ffms2")]
1020 Self::Ffms2(dec) => dec.read_video_frame::<T>(frame_index, luma_only),
1021 }
1022 }
1023
1024 #[cfg(feature = "vapoursynth")]
1025 pub(crate) fn get_video_frame<T: Pixel>(
1026 &mut self,
1027 cfg: &VideoDetails,
1028 frame_index: usize,
1029 luma_only: bool,
1030 ) -> Result<Frame<T>, DecoderError> {
1031 match self {
1032 #[cfg(feature = "vapoursynth")]
1033 Self::Vapoursynth(dec) => dec.read_video_frame::<T>(cfg, frame_index, luma_only),
1034 _ => Err(DecoderError::UnsupportedDecoder),
1035 }
1036 }
1037}