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

nomalab / stainless-ffmpeg / #97

22 May 2025 10:16AM UTC coverage: 64.789% (-0.06%) from 64.852%
#97

push

1691 of 2610 relevant lines covered (64.79%)

0.99 hits per line

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

52.76
/src/format_context.rs
1
use crate::{
2
  audio_encoder::AudioEncoder, order::frame::FrameAddress, order::*, packet::Packet,
3
  subtitle_encoder::SubtitleEncoder, tools, video_encoder::VideoEncoder,
4
};
5
use ffmpeg_sys_next::*;
6
use std::{
7
  collections::{BTreeMap, HashMap},
8
  ffi::{c_void, CString},
9
  ptr::null_mut,
10
};
11

12
#[derive(Debug)]
13
pub struct FormatContext {
14
  pub filename: String,
15
  pub format_context: *mut AVFormatContext,
16
  streams: Vec<*mut AVStream>,
17
  frames: Vec<FrameAddress>,
18
  frame_index: usize,
19
}
20

21
impl FormatContext {
22
  pub fn new(filename: &str) -> Result<FormatContext, String> {
1✔
23
    Ok(FormatContext {
2✔
24
      filename: filename.to_string(),
1✔
25
      format_context: null_mut(),
26
      streams: vec![],
2✔
27
      frames: vec![],
2✔
28
      frame_index: 0,
29
    })
30
  }
31

32
  pub fn set_frames_addresses(&mut self, frames: &[FrameAddress]) {
×
33
    self.frames = frames.to_vec();
×
34
  }
35

36
  pub fn open_input(&mut self) -> Result<(), String> {
2✔
37
    unsafe {
38
      self.format_context = avformat_alloc_context();
2✔
39
      let filename = CString::new(self.filename.to_owned()).unwrap();
4✔
40
      if avformat_open_input(
2✔
41
        &mut self.format_context,
2✔
42
        filename.as_ptr(),
4✔
43
        null_mut(),
44
        null_mut(),
45
      ) < 0
46
      {
47
        return Err(format!("Unable to open input file {:?}", self.filename));
×
48
      }
49
      avformat_find_stream_info(self.format_context, null_mut());
4✔
50
    }
51
    Ok(())
1✔
52
  }
53

54
  pub fn close_input(&mut self) {
1✔
55
    unsafe {
56
      avformat_close_input(&mut self.format_context);
1✔
57
    }
58
  }
59

60
  pub fn open_output(
×
61
    &mut self,
62
    parameters: &HashMap<String, ParameterValue>,
63
  ) -> Result<(), String> {
64
    unsafe {
65
      let filename = CString::new(self.filename.to_owned()).unwrap();
×
66

67
      if avformat_alloc_output_context2(
×
68
        &mut self.format_context,
×
69
        null_mut(),
70
        null_mut(),
71
        filename.as_ptr(),
×
72
      ) < 0
73
      {
74
        return Err(format!("Unable to open output file {:?}", self.filename));
×
75
      }
76

77
      set_parameters(self.format_context as *mut c_void, parameters)?;
×
78
    }
79
    Ok(())
×
80
  }
81

82
  pub fn add_video_stream(&mut self, encoder: &VideoEncoder) -> Result<(), String> {
×
83
    unsafe {
84
      let av_stream = avformat_new_stream(self.format_context, null_mut());
×
85
      if av_stream.is_null() {
×
86
        return Err("Unable to create new stream".to_owned());
×
87
      }
88

89
      (*av_stream).id = ((*self.format_context).nb_streams - 1) as i32;
×
90
      (*av_stream).time_base = (*encoder.codec_context).time_base;
×
91
      avcodec_parameters_from_context((*av_stream).codecpar, encoder.codec_context);
×
92
      self.streams.push(av_stream);
×
93
    }
94
    Ok(())
×
95
  }
96

97
  pub fn add_audio_stream(&mut self, encoder: &AudioEncoder) -> Result<(), String> {
×
98
    unsafe {
99
      let av_stream = avformat_new_stream(self.format_context, null_mut());
×
100
      if av_stream.is_null() {
×
101
        return Err("Unable to create new stream".to_owned());
×
102
      }
103

104
      (*av_stream).id = ((*self.format_context).nb_streams - 1) as i32;
×
105
      (*av_stream).time_base = (*encoder.codec_context).time_base;
×
106
      avcodec_parameters_from_context((*av_stream).codecpar, encoder.codec_context);
×
107
      self.streams.push(av_stream);
×
108
    }
109
    Ok(())
×
110
  }
111

112
  pub fn add_subtitle_stream(&mut self, encoder: &SubtitleEncoder) -> Result<(), String> {
×
113
    unsafe {
114
      let av_stream = avformat_new_stream(self.format_context, null_mut());
×
115
      if av_stream.is_null() {
×
116
        return Err("Unable to create new stream".to_owned());
×
117
      }
118

119
      (*av_stream).id = ((*self.format_context).nb_streams - 1) as i32;
×
120
      (*av_stream).time_base = (*encoder.codec_context).time_base;
×
121
      avcodec_parameters_from_context((*av_stream).codecpar, encoder.codec_context);
×
122
      self.streams.push(av_stream);
×
123
    }
124
    Ok(())
×
125
  }
126

127
  /// # Safety
128
  /// The caller must ensure that `stream_index` is a valid index, i.e., is in [0, (*self.format_context).nb_streams].
129
  /// You can use the `get_nb_streams()` function to determine the valid range.
130
  pub unsafe fn get_stream(&self, stream_index: isize) -> *mut AVStream {
1✔
131
    *(*self.format_context).streams.offset(stream_index)
2✔
132
  }
133

134
  pub fn get_nb_streams(&self) -> u32 {
1✔
135
    if !self.frames.is_empty() {
1✔
136
      return 1;
×
137
    }
138
    unsafe { (*self.format_context).nb_streams }
2✔
139
  }
140

141
  pub fn get_format_name(&self) -> String {
1✔
142
    unsafe { tools::to_string((*(*self.format_context).iformat).name) }
1✔
143
  }
144

145
  pub fn get_format_long_name(&self) -> String {
1✔
146
    unsafe { tools::to_string((*(*self.format_context).iformat).long_name) }
1✔
147
  }
148

149
  pub fn get_program_count(&self) -> u32 {
1✔
150
    unsafe { (*self.format_context).nb_programs }
1✔
151
  }
152

153
  pub fn get_start_time(&self) -> Option<f32> {
1✔
154
    unsafe {
155
      if (*self.format_context).start_time == AV_NOPTS_VALUE {
2✔
156
        None
×
157
      } else {
158
        Some((*self.format_context).start_time as f32 / AV_TIME_BASE as f32)
2✔
159
      }
160
    }
161
  }
162

163
  pub fn get_duration(&self) -> Option<f64> {
1✔
164
    unsafe {
165
      if (*self.format_context).duration == AV_NOPTS_VALUE {
2✔
166
        None
×
167
      } else {
168
        Some((*self.format_context).duration as f64 / f64::from(AV_TIME_BASE))
2✔
169
      }
170
    }
171
  }
172

173
  pub fn get_bit_rate(&self) -> Option<i64> {
1✔
174
    unsafe {
175
      if (*self.format_context).bit_rate == AV_NOPTS_VALUE || (*self.format_context).bit_rate == 0 {
2✔
176
        None
×
177
      } else {
178
        Some((*self.format_context).bit_rate)
1✔
179
      }
180
    }
181
  }
182

183
  pub fn get_packet_size(&self) -> u32 {
1✔
184
    unsafe { (*self.format_context).packet_size }
1✔
185
  }
186

187
  /// # Safety
188
  /// The caller must ensure that `stream_index` is a valid index, i.e., is in [0, (*self.format_context).nb_streams].
189
  /// You can use the `get_nb_streams()` function to determine the valid range.
190
  pub unsafe fn get_stream_type(&self, stream_index: isize) -> AVMediaType {
1✔
191
    (*(**(*self.format_context).streams.offset(stream_index)).codecpar).codec_type
2✔
192
  }
193

194
  /// # Safety
195
  /// The caller must ensure that `stream_index` is a valid index, i.e., is in [0, (*self.format_context).nb_streams].
196
  /// You can use the `get_nb_streams()` function to determine the valid range.
197
  pub unsafe fn get_stream_type_name(&self, stream_index: isize) -> String {
1✔
198
    tools::to_string(av_get_media_type_string(self.get_stream_type(stream_index)))
1✔
199
  }
200

201
  /// # Safety
202
  /// The caller must ensure that `stream_index` is a valid index, i.e., is in [0, (*self.format_context).nb_streams].
203
  /// You can use the `get_nb_streams()` function to determine the valid range.
204
  pub unsafe fn get_codec_id(&self, stream_index: isize) -> AVCodecID {
1✔
205
    (*(**(*self.format_context).streams.offset(stream_index)).codecpar).codec_id
2✔
206
  }
207

208
  pub fn get_metadata(&self) -> BTreeMap<String, String> {
1✔
209
    unsafe {
210
      let mut tag = null_mut();
1✔
211
      let key = CString::new("").unwrap();
2✔
212
      let mut metadata = BTreeMap::new();
2✔
213

214
      loop {
215
        tag = av_dict_get(
1✔
216
          (*self.format_context).metadata,
1✔
217
          key.as_ptr(),
2✔
218
          tag,
1✔
219
          AV_DICT_IGNORE_SUFFIX,
220
        );
221
        if tag.is_null() {
1✔
222
          break;
223
        }
224
        let k = tools::to_string((*tag).key);
2✔
225
        let v = tools::to_string((*tag).value);
1✔
226
        metadata.insert(k.to_string(), v.to_string());
2✔
227
      }
228

229
      metadata
1✔
230
    }
231
  }
232

233
  pub fn next_packet(&mut self) -> Result<Packet, String> {
1✔
234
    if !self.frames.is_empty() {
1✔
235
      if self.frame_index >= self.frames.len() {
×
236
        return Err("End of data stream".to_string());
×
237
      }
238
      let frame = &self.frames[self.frame_index];
×
239
      unsafe {
240
        let filename = CString::new(self.filename.to_owned()).unwrap();
×
241
        let mut avio_context: *mut AVIOContext = null_mut();
×
242
        check_result!(avio_open(
×
243
          &mut avio_context,
244
          filename.as_ptr(),
245
          AVIO_FLAG_READ
246
        ));
247
        if avio_seek(avio_context, frame.offset as i64, 0) < 0 {
×
248
          println!("ERROR !");
×
249
        };
250

251
        let packet = av_packet_alloc();
×
252
        check_result!(av_new_packet(packet, frame.size as i32));
×
253
        check_result!(avio_read(avio_context, (*packet).data, (*packet).size));
×
254
        check_result!(avio_close(avio_context));
×
255

256
        self.frame_index += 1;
×
257

258
        return Ok(Packet { name: None, packet });
×
259
      }
260
    }
261

262
    unsafe {
263
      let mut packet = av_packet_alloc();
1✔
264
      av_init_packet(packet);
1✔
265

266
      if av_read_frame(self.format_context, packet) < 0 {
1✔
267
        av_packet_free(&mut packet);
1✔
268
        return Err("Unable to read next packet".to_string());
1✔
269
      }
270

271
      Ok(Packet { name: None, packet })
1✔
272
    }
273
  }
274
}
275

276
unsafe impl Send for FormatContext {}
277

278
impl From<*mut AVFormatContext> for FormatContext {
279
  fn from(format_context: *mut AVFormatContext) -> Self {
×
280
    FormatContext {
281
      filename: "virtual_source".to_string(),
×
282
      format_context,
283
      streams: vec![],
×
284
      frames: vec![],
×
285
      frame_index: 0,
286
    }
287
  }
288
}
289

290
impl Drop for FormatContext {
291
  fn drop(&mut self) {
1✔
292
    unsafe {
293
      if !self.format_context.is_null() {
1✔
294
        avformat_free_context(self.format_context);
1✔
295
      }
296
    }
297
  }
298
}
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