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}