Skip to main content

ez_ffmpeg/core/context/output/
attachment.rs

1use super::Output;
2
3impl Output {
4    /// Embeds a file as a container **attachment** stream (FFmpeg `-attach`),
5    /// e.g. a `.ttf`/`.otf` font for Matroska subtitle rendering, or cover art.
6    ///
7    /// The MIME type is guessed from the file extension
8    /// (`.ttf` → `application/x-truetype-font`,
9    /// `.otf` → `application/vnd.ms-opentype`, otherwise
10    /// `application/octet-stream`). Use
11    /// [`add_attachment_with_mimetype`](Self::add_attachment_with_mimetype) to
12    /// set it explicitly.
13    ///
14    /// # Behavior & limitations
15    /// - **Local files only.** The path is read with `std::fs` at build time,
16    ///   so protocol URLs (`http:`, `pipe:`, …) are **not** supported here
17    ///   (unlike some FFmpeg inputs). A missing, unreadable, empty, or oversized
18    ///   file surfaces as an `Err` from
19    ///   [`FfmpegContext`](crate::FfmpegContext) build — never a panic. The
20    ///   setter itself does no I/O and never fails.
21    /// - **Muxer support.** Attachments are supported only by **Matroska/WebM**
22    ///   (`.mkv`/`.webm`). MP4, MOV, MPEG-TS and other muxers do not accept a
23    ///   generic attachment stream and will reject the job at header-write time
24    ///   (surfacing as an `Err` from the running job, not a panic).
25    /// - Must be used **alongside at least one mapped/encoded stream**: an
26    ///   output whose only stream is an attachment is not supported.
27    ///
28    /// # Example
29    /// ```rust,ignore
30    /// let output = Output::from("output.mkv")
31    ///     .add_stream_map("0:v")
32    ///     .add_attachment("assets/DejaVuSans.ttf");
33    /// ```
34    pub fn add_attachment(mut self, path: impl Into<std::path::PathBuf>) -> Self {
35        self.attachments.push(AttachmentSpec {
36            path: path.into(),
37            mimetype: None,
38        });
39        self
40    }
41
42    /// Same as [`add_attachment`](Self::add_attachment) but with an explicit
43    /// MIME type (e.g. `"application/x-truetype-font"`, `"image/png"`) instead
44    /// of guessing from the extension.
45    ///
46    /// An empty `mimetype` is rejected at build time (Matroska requires a
47    /// non-empty mimetype tag for every attachment).
48    ///
49    /// # Example
50    /// ```rust,ignore
51    /// let output = Output::from("output.mkv")
52    ///     .add_stream_map("0:v")
53    ///     .add_attachment_with_mimetype("cover.png", "image/png");
54    /// ```
55    pub fn add_attachment_with_mimetype(
56        mut self,
57        path: impl Into<std::path::PathBuf>,
58        mimetype: impl Into<String>,
59    ) -> Self {
60        self.attachments.push(AttachmentSpec {
61            path: path.into(),
62            mimetype: Some(mimetype.into()),
63        });
64        self
65    }
66}
67
68/// One `-attach` request. The file is **not** read here; I/O is deferred to
69/// output build time so the [`Output`] setters stay infallible.
70///
71/// Created via [`Output::add_attachment`] /
72/// [`Output::add_attachment_with_mimetype`].
73#[derive(Debug, Clone)]
74pub(crate) struct AttachmentSpec {
75    /// Path to the file whose bytes become the attachment payload (local file).
76    pub(crate) path: std::path::PathBuf,
77    /// Explicit MIME type override. `None` ⇒ guess from the file extension.
78    pub(crate) mimetype: Option<String>,
79}