• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

qubit-ltd / rs-mime / 25725229317

12 May 2026 09:16AM UTC coverage: 99.41% (-0.1%) from 99.51%
25725229317

push

github

Haixing-Hu
chore(ci): add submodule update helper script

- Add `update-submodule.sh` to standardize root-level submodule sync/update
  commands for CI and local maintenance workflows.
- Expand `Cargo.toml` package exclusion list to avoid CI helper scripts and
  GitHub workflow files from being included in package artifacts.

2021 of 2033 relevant lines covered (99.41%)

5226.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.51
/src/classifier/ffprobe_command_media_stream_classifier.rs
1
/*******************************************************************************
2
 *
3
 *    Copyright (c) 2026 Haixing Hu.
4
 *
5
 *    SPDX-License-Identifier: Apache-2.0
6
 *
7
 *    Licensed under the Apache License, Version 2.0.
8
 *
9
 ******************************************************************************/
10
//! FFprobe-backed media stream classifier.
11

12
use std::path::Path;
13

14
use qubit_command::CommandRunner;
15
use qubit_command::{
16
    Command,
17
    CommandError,
18
};
19

20
use crate::{
21
    FileBasedMediaStreamClassifier,
22
    MediaStreamType,
23
    MimeResult,
24
};
25

26
/// Media stream classifier backed by the `ffprobe` command.
27
#[derive(Debug, Clone)]
28
pub struct FfprobeCommandMediaStreamClassifier {
29
    /// The working directory used to execute FFprobe.
30
    working_directory: Option<String>,
31
    /// The command runner used to execute FFprobe.
32
    command_runner: CommandRunner,
33
}
34

35
impl FfprobeCommandMediaStreamClassifier {
36
    /// FFprobe executable name.
37
    pub const COMMAND: &'static str = "ffprobe";
38
    /// FFprobe stream name for video streams.
39
    pub const VIDEO_STREAM: &'static str = "video";
40
    /// FFprobe stream name for audio streams.
41
    pub const AUDIO_STREAM: &'static str = "audio";
42

43
    /// Creates a FFprobe-backed classifier.
44
    ///
45
    /// # Returns
46
    /// A classifier using the current process working directory.
47
    pub fn new() -> Self {
52✔
48
        Self {
52✔
49
            working_directory: None,
52✔
50
            command_runner: Self::default_command_runner(),
52✔
51
        }
52✔
52
    }
52✔
53

54
    /// Gets the command runner used by this classifier.
55
    ///
56
    /// # Returns
57
    /// Runner used for `ffprobe` command executions.
58
    pub fn command_runner(&self) -> &CommandRunner {
5✔
59
        &self.command_runner
5✔
60
    }
5✔
61

62
    /// Replaces the command runner used by this classifier.
63
    ///
64
    /// # Parameters
65
    /// - `command_runner`: New runner configuration.
66
    pub fn set_command_runner(&mut self, command_runner: CommandRunner) {
1✔
67
        self.command_runner = command_runner;
1✔
68
    }
1✔
69

70
    /// Replaces the command runner and returns the updated classifier.
71
    ///
72
    /// # Parameters
73
    /// - `command_runner`: New runner configuration.
74
    ///
75
    /// # Returns
76
    /// The updated classifier.
77
    pub fn with_command_runner(mut self, command_runner: CommandRunner) -> Self {
3✔
78
        self.command_runner = command_runner;
3✔
79
        self
3✔
80
    }
3✔
81

82
    /// Sets the working directory used to execute FFprobe.
83
    ///
84
    /// # Parameters
85
    /// - `working_directory`: Optional working directory path.
86
    pub fn set_working_directory(&mut self, working_directory: Option<String>) {
1✔
87
        self.working_directory = working_directory;
1✔
88
    }
1✔
89

90
    /// Gets the configured working directory.
91
    ///
92
    /// # Returns
93
    /// Stored working directory, or `None`.
94
    pub fn working_directory(&self) -> Option<&str> {
1✔
95
        self.working_directory.as_deref()
1✔
96
    }
1✔
97

98
    /// Classifies FFprobe `codec_type` output.
99
    ///
100
    /// # Parameters
101
    /// - `output`: Lines printed by `ffprobe -show_entries stream=codec_type`.
102
    ///
103
    /// # Returns
104
    /// Media stream classification.
105
    pub fn classify_stream_listing(output: &str) -> MediaStreamType {
7✔
106
        let has_video = output.lines().any(|line| line.trim() == Self::VIDEO_STREAM);
7✔
107
        let has_audio = output.lines().any(|line| line.trim() == Self::AUDIO_STREAM);
11✔
108
        match (has_video, has_audio) {
7✔
109
            (true, true) => MediaStreamType::VideoWithAudio,
4✔
110
            (true, false) => MediaStreamType::VideoOnly,
1✔
111
            (false, true) => MediaStreamType::AudioOnly,
1✔
112
            (false, false) => MediaStreamType::None,
1✔
113
        }
114
    }
7✔
115

116
    /// Checks whether the `ffprobe` command is available.
117
    ///
118
    /// Availability is checked by executing `ffprobe -version` with the default
119
    /// quiet command runner. The result only describes whether the command can
120
    /// be started successfully; a particular media file may still be unreadable
121
    /// or unsupported.
122
    ///
123
    /// # Returns
124
    /// `true` when `ffprobe -version` executes successfully.
125
    pub fn is_available() -> bool {
1✔
126
        Self::default_command_runner()
1✔
127
            .run(Command::new(Self::COMMAND).arg("-version"))
1✔
128
            .is_ok()
1✔
129
    }
1✔
130

131
    /// Executes FFprobe for one local file.
132
    ///
133
    /// # Parameters
134
    /// - `path`: Local file path.
135
    ///
136
    /// # Returns
137
    /// Media stream classification. Non-zero FFprobe status is treated as
138
    /// [`MediaStreamType::None`] because stream refinement is best-effort.
139
    ///
140
    /// # Errors
141
    /// Returns [`MimeError::Command`](crate::MimeError::Command) when process
142
    /// execution itself fails.
143
    fn classify_with_ffprobe(&self, path: &Path) -> MimeResult<MediaStreamType> {
4✔
144
        let mut command = Self::command_for_path(path);
4✔
145
        if let Some(working_directory) = &self.working_directory {
4✔
146
            command = command.working_directory(working_directory);
3✔
147
        }
3✔
148
        match self.command_runner.run(command) {
4✔
149
            Ok(output) => {
3✔
150
                let stdout = output.stdout_lossy_text();
3✔
151
                Ok(Self::classify_stream_listing(&stdout))
3✔
152
            }
153
            Err(CommandError::UnexpectedExit { .. }) => Ok(MediaStreamType::None),
1✔
154
            Err(error) => Err(error.into()),
×
155
        }
156
    }
4✔
157

158
    /// Creates the default command runner for FFprobe classification.
159
    ///
160
    /// # Returns
161
    /// Runner used by the default classifier.
162
    fn default_command_runner() -> CommandRunner {
53✔
163
        CommandRunner::new().disable_logging(true)
53✔
164
    }
53✔
165

166
    /// Builds the structured `ffprobe` command for one path.
167
    ///
168
    /// # Parameters
169
    /// - `path`: Local file path passed as an argument without shell parsing.
170
    ///
171
    /// # Returns
172
    /// Structured command description.
173
    fn command_for_path(path: &Path) -> Command {
4✔
174
        Command::new(Self::COMMAND)
4✔
175
            .arg("-v")
4✔
176
            .arg("error")
4✔
177
            .arg("-show_entries")
4✔
178
            .arg("stream=codec_type")
4✔
179
            .arg("-of")
4✔
180
            .arg("csv=p=0")
4✔
181
            .arg_os(path)
4✔
182
    }
4✔
183
}
184

185
impl Default for FfprobeCommandMediaStreamClassifier {
186
    /// Creates the default classifier.
187
    fn default() -> Self {
1✔
188
        Self::new()
1✔
189
    }
1✔
190
}
191

192
impl FileBasedMediaStreamClassifier for FfprobeCommandMediaStreamClassifier {
193
    /// Classifies a readable local media file using FFprobe.
194
    fn classify_by_local_file(&self, file: &Path) -> MimeResult<MediaStreamType> {
4✔
195
        self.classify_with_ffprobe(file)
4✔
196
    }
4✔
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc