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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Error types for the `unbundle` crate.
//!
//! This module defines [`UnbundleError`], the unified error type returned by all
//! fallible operations in the crate. Errors carry rich context to aid debugging,
//! including file paths, frame numbers, and upstream error messages.
use std::{io::Error as IoError, path::PathBuf, time::Duration};
use ffmpeg_next::Error as FfmpegError;
use image::ImageError;
use thiserror::Error;
use crate::audio::AudioFormat;
/// The unified error type for all `unbundle` operations.
///
/// Every public method that can fail returns `Result<T, UnbundleError>`.
/// Variants carry enough context to diagnose the problem without needing
/// additional logging at the call site.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum UnbundleError {
/// The media file could not be opened.
#[error("Failed to open media file at {path}: {reason}")]
FileOpen {
/// Path that was passed to [`crate::MediaFile::open`].
path: PathBuf,
/// Underlying reason the open failed.
reason: String,
},
/// The media source could not be opened.
#[error("Failed to open media source '{input_source}': {reason}")]
SourceOpen {
/// Source string that was passed to [`crate::MediaFile::open_url`] or
/// [`crate::MediaFile::open_source`].
input_source: String,
/// Underlying reason the open failed.
reason: String,
},
/// The file does not contain a video stream.
#[error("No video stream found in file")]
NoVideoStream,
/// The file does not contain an audio stream.
#[error("No audio stream found in file")]
NoAudioStream,
/// A video frame could not be decoded.
#[error("Failed to decode video frame: {0}")]
VideoDecodeError(String),
/// Audio data could not be decoded.
#[error("Failed to decode audio: {0}")]
AudioDecodeError(String),
/// Audio data could not be encoded to the target format.
#[error("Failed to encode audio: {0}")]
AudioEncodeError(String),
/// The requested frame number exceeds the total frame count.
#[error("Frame {frame_number} is out of range (video has {total_frames} frames)")]
FrameOutOfRange {
/// The frame number that was requested.
frame_number: u64,
/// The total number of frames in the video.
total_frames: u64,
},
/// The requested timestamp exceeds the media duration.
#[error("Invalid timestamp: {0:?}")]
InvalidTimestamp(Duration),
/// A range's start value is greater than or equal to its end value.
#[error("Invalid range: start ({start:?}) must be less than end ({end:?})")]
InvalidRange {
/// The start of the range.
start: String,
/// The end of the range.
end: String,
},
/// An interval or step value of zero was provided.
#[error("Interval must be greater than zero")]
InvalidInterval,
/// The requested audio output format is not supported.
#[error("Unsupported audio format: {0}")]
UnsupportedAudioFormat(AudioFormat),
/// An error originating from the FFmpeg libraries.
#[error("FFmpeg error: {0}")]
FfmpegError(String),
/// An I/O error occurred while reading or writing files.
#[error("I/O error: {0}")]
IoError(#[from] IoError),
/// An error from the `image` crate during frame conversion.
#[error("Image processing error: {0}")]
ImageError(#[from] ImageError),
/// The operation was cancelled via a [`CancellationToken`](crate::CancellationToken).
#[error("Operation cancelled")]
Cancelled,
/// The file does not contain a subtitle stream.
#[error("No subtitle stream found in file")]
NoSubtitleStream,
/// Subtitle data could not be decoded.
#[error("Failed to decode subtitle: {0}")]
SubtitleDecodeError(String),
/// GIF encoding failed.
#[cfg(feature = "gif")]
#[error("GIF encoding error: {0}")]
GifEncodeError(String),
/// Video encoding failed (used by the video writer and transcoder).
#[error("Video encoding error: {0}")]
VideoEncodeError(String),
/// Transcoding failed.
#[cfg(feature = "transcode")]
#[error("Transcode error: {0}")]
TranscodeError(String),
/// Video writer failed.
#[cfg(feature = "encode")]
#[error("Video write error: {0}")]
VideoWriteError(String),
/// Waveform generation failed.
#[cfg(feature = "waveform")]
#[error("Waveform decode error: {0}")]
WaveformDecodeError(String),
/// Loudness analysis failed.
#[cfg(feature = "loudness")]
#[error("Loudness analysis error: {0}")]
LoudnessError(String),
/// The requested video track index is out of range.
#[error("Video track {track_index} is out of range (file has {track_count} video tracks)")]
VideoTrackOutOfRange {
/// Requested track index.
track_index: usize,
/// Number of available video tracks.
track_count: usize,
},
/// Raw stream copy (packet-level extraction) failed.
#[error("Stream copy error: {0}")]
StreamCopyError(String),
/// FFmpeg filter graph setup or processing failed.
#[error("Filter graph error: {0}")]
FilterGraphError(String),
}
impl From<FfmpegError> for UnbundleError {
fn from(error: FfmpegError) -> Self {
UnbundleError::FfmpegError(error.to_string())
}
}