ffmpeg_light/
thumbnail.rs

1//! Thumbnail generation helpers.
2
3use std::path::Path;
4
5use crate::command::{FfmpegBinaryPaths, FfmpegCommand};
6use crate::config::FfmpegLocator;
7use crate::error::Result;
8use crate::types::Time;
9
10/// Supported output formats.
11#[derive(Clone, Debug)]
12pub enum ThumbnailFormat {
13    /// Portable Network Graphics (png).
14    Png,
15    /// JPEG (jpg/jpeg).
16    Jpeg,
17}
18
19impl ThumbnailFormat {
20    fn extension(&self) -> &'static str {
21        match self {
22            ThumbnailFormat::Png => "png",
23            ThumbnailFormat::Jpeg => "jpg",
24        }
25    }
26
27    fn ffmpeg_args(&self) -> &'static [&'static str] {
28        match self {
29            ThumbnailFormat::Png => &["-f", "image2"],
30            ThumbnailFormat::Jpeg => &["-f", "mjpeg"],
31        }
32    }
33}
34
35/// Options for generating a thumbnail.
36#[derive(Clone, Debug)]
37pub struct ThumbnailOptions {
38    time: Time,
39    width: Option<u32>,
40    height: Option<u32>,
41    format: ThumbnailFormat,
42}
43
44impl ThumbnailOptions {
45    /// Create with the specified timestamp.
46    pub fn new(time: Time) -> Self {
47        Self {
48            time,
49            width: None,
50            height: None,
51            format: ThumbnailFormat::Png,
52        }
53    }
54
55    /// The timestamp at which the thumbnail will be captured.
56    pub fn time(&self) -> Time {
57        self.time
58    }
59
60    /// Set the output dimensions.
61    pub fn size(mut self, width: u32, height: u32) -> Self {
62        self.width = Some(width);
63        self.height = Some(height);
64        self
65    }
66
67    /// Current width/height configuration.
68    pub fn dimensions(&self) -> Option<(u32, u32)> {
69        match (self.width, self.height) {
70            (Some(w), Some(h)) => Some((w, h)),
71            _ => None,
72        }
73    }
74
75    /// Choose a specific output format.
76    pub fn format(mut self, format: ThumbnailFormat) -> Self {
77        self.format = format;
78        self
79    }
80
81    /// Output format getter.
82    pub fn output_format(&self) -> &ThumbnailFormat {
83        &self.format
84    }
85}
86
87/// Generate a thumbnail file.
88pub fn generate(
89    input: impl AsRef<Path>,
90    output: impl AsRef<Path>,
91    options: &ThumbnailOptions,
92) -> Result<()> {
93    let locator = FfmpegLocator::system()?;
94    generate_with_binaries(locator.binaries(), input, output, options)
95}
96
97/// Same as [`generate`] but reuses already-discovered binaries.
98pub fn generate_with_binaries(
99    binaries: &FfmpegBinaryPaths,
100    input: impl AsRef<Path>,
101    output: impl AsRef<Path>,
102    options: &ThumbnailOptions,
103) -> Result<()> {
104    if let Some(parent) = output.as_ref().parent() {
105        std::fs::create_dir_all(parent)?;
106    }
107
108    let mut cmd = FfmpegCommand::new(binaries.ffmpeg());
109    cmd.arg("-y");
110    cmd.arg("-ss").arg(options.time.to_ffmpeg_timestamp());
111    cmd.arg("-i").arg(input.as_ref());
112    cmd.arg("-vframes").arg("1");
113
114    if let (Some(width), Some(height)) = (options.width, options.height) {
115        cmd.arg("-vf").arg(format!("scale={width}:{height}"));
116    }
117
118    for arg in options.format.ffmpeg_args() {
119        cmd.arg(arg);
120    }
121
122    let mut output_path = output.as_ref().to_path_buf();
123    if output_path.extension().is_none() {
124        output_path.set_extension(options.format.extension());
125    }
126    cmd.arg(output_path);
127    cmd.run()
128}