1 // Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
2 //               2018 François Laignel <fengalin@free.fr>
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9 
10 use gst::glib;
11 use gst::prelude::*;
12 use gst::subclass::prelude::*;
13 use gst::{gst_debug, gst_error, gst_info};
14 use gst_base::prelude::*;
15 use gst_base::subclass::prelude::*;
16 
17 use std::fs::File;
18 use std::io::{Read, Seek, SeekFrom};
19 use std::sync::Mutex;
20 
21 use url::Url;
22 
23 use crate::file_location::FileLocation;
24 
25 const DEFAULT_LOCATION: Option<FileLocation> = None;
26 
27 #[derive(Debug)]
28 struct Settings {
29     location: Option<FileLocation>,
30 }
31 
32 impl Default for Settings {
default() -> Self33     fn default() -> Self {
34         Settings {
35             location: DEFAULT_LOCATION,
36         }
37     }
38 }
39 
40 enum State {
41     Stopped,
42     Started { file: File, position: u64 },
43 }
44 
45 impl Default for State {
default() -> State46     fn default() -> State {
47         State::Stopped
48     }
49 }
50 
51 #[derive(Default)]
52 pub struct FileSrc {
53     settings: Mutex<Settings>,
54     state: Mutex<State>,
55 }
56 
57 use once_cell::sync::Lazy;
58 static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
59     gst::DebugCategory::new(
60         "rsfilesrc",
61         gst::DebugColorFlags::empty(),
62         Some("File Source"),
63     )
64 });
65 
66 impl FileSrc {
set_location( &self, element: &super::FileSrc, location: Option<FileLocation>, ) -> Result<(), glib::Error>67     fn set_location(
68         &self,
69         element: &super::FileSrc,
70         location: Option<FileLocation>,
71     ) -> Result<(), glib::Error> {
72         let state = self.state.lock().unwrap();
73         if let State::Started { .. } = *state {
74             return Err(glib::Error::new(
75                 gst::URIError::BadState,
76                 "Changing the `location` property on a started `filesrc` is not supported",
77             ));
78         }
79 
80         let mut settings = self.settings.lock().unwrap();
81         settings.location = match location {
82             Some(location) => {
83                 if !location.exists() {
84                     return Err(glib::Error::new(
85                         gst::URIError::BadReference,
86                         format!("{} doesn't exist", location).as_str(),
87                     ));
88                 }
89 
90                 if !location.is_file() {
91                     return Err(glib::Error::new(
92                         gst::URIError::BadReference,
93                         format!("{} is not a file", location).as_str(),
94                     ));
95                 }
96 
97                 match settings.location {
98                     Some(ref location_cur) => {
99                         gst_info!(
100                             CAT,
101                             obj: element,
102                             "Changing `location` from {:?} to {}",
103                             location_cur,
104                             location,
105                         );
106                     }
107                     None => {
108                         gst_info!(CAT, obj: element, "Setting `location to {}", location,);
109                     }
110                 }
111                 Some(location)
112             }
113             None => {
114                 gst_info!(CAT, obj: element, "Resetting `location` to None",);
115                 None
116             }
117         };
118 
119         Ok(())
120     }
121 }
122 
123 #[glib::object_subclass]
124 impl ObjectSubclass for FileSrc {
125     const NAME: &'static str = "RsFileSrc";
126     type Type = super::FileSrc;
127     type ParentType = gst_base::BaseSrc;
128     type Interfaces = (gst::URIHandler,);
129 }
130 
131 impl ObjectImpl for FileSrc {
properties() -> &'static [glib::ParamSpec]132     fn properties() -> &'static [glib::ParamSpec] {
133         static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
134             vec![glib::ParamSpec::new_string(
135                 "location",
136                 "File Location",
137                 "Location of the file to read from",
138                 None,
139                 glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY,
140             )]
141         });
142 
143         PROPERTIES.as_ref()
144     }
145 
set_property( &self, obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec, )146     fn set_property(
147         &self,
148         obj: &Self::Type,
149         _id: usize,
150         value: &glib::Value,
151         pspec: &glib::ParamSpec,
152     ) {
153         match pspec.name() {
154             "location" => {
155                 let res = match value.get::<Option<String>>() {
156                     Ok(Some(location)) => FileLocation::try_from_path_str(location)
157                         .and_then(|file_location| self.set_location(obj, Some(file_location))),
158                     Ok(None) => self.set_location(obj, None),
159                     Err(_) => unreachable!("type checked upstream"),
160                 };
161 
162                 if let Err(err) = res {
163                     gst_error!(CAT, obj: obj, "Failed to set property `location`: {}", err);
164                 }
165             }
166             _ => unimplemented!(),
167         };
168     }
169 
property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value170     fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
171         match pspec.name() {
172             "location" => {
173                 let settings = self.settings.lock().unwrap();
174                 let location = settings
175                     .location
176                     .as_ref()
177                     .map(|location| location.to_string());
178 
179                 location.to_value()
180             }
181             _ => unimplemented!(),
182         }
183     }
184 
constructed(&self, obj: &Self::Type)185     fn constructed(&self, obj: &Self::Type) {
186         self.parent_constructed(obj);
187 
188         obj.set_format(gst::Format::Bytes);
189     }
190 }
191 
192 impl ElementImpl for FileSrc {
metadata() -> Option<&'static gst::subclass::ElementMetadata>193     fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
194         static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
195             gst::subclass::ElementMetadata::new(
196                 "File Source",
197                 "Source/File",
198                 "Read stream from a file",
199                 "François Laignel <fengalin@free.fr>, Sebastian Dröge <sebastian@centricular.com>",
200             )
201         });
202 
203         Some(&*ELEMENT_METADATA)
204     }
205 
pad_templates() -> &'static [gst::PadTemplate]206     fn pad_templates() -> &'static [gst::PadTemplate] {
207         static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
208             let caps = gst::Caps::new_any();
209             let src_pad_template = gst::PadTemplate::new(
210                 "src",
211                 gst::PadDirection::Src,
212                 gst::PadPresence::Always,
213                 &caps,
214             )
215             .unwrap();
216 
217             vec![src_pad_template]
218         });
219 
220         PAD_TEMPLATES.as_ref()
221     }
222 }
223 
224 impl BaseSrcImpl for FileSrc {
is_seekable(&self, _src: &Self::Type) -> bool225     fn is_seekable(&self, _src: &Self::Type) -> bool {
226         true
227     }
228 
size(&self, _src: &Self::Type) -> Option<u64>229     fn size(&self, _src: &Self::Type) -> Option<u64> {
230         let state = self.state.lock().unwrap();
231         if let State::Started { ref file, .. } = *state {
232             file.metadata().ok().map(|m| m.len())
233         } else {
234             None
235         }
236     }
237 
start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage>238     fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> {
239         let mut state = self.state.lock().unwrap();
240         if let State::Started { .. } = *state {
241             unreachable!("FileSrc already started");
242         }
243 
244         let settings = self.settings.lock().unwrap();
245         let location = settings.location.as_ref().ok_or_else(|| {
246             gst::error_msg!(
247                 gst::ResourceError::Settings,
248                 ["File location is not defined"]
249             )
250         })?;
251 
252         let file = File::open(location).map_err(|err| {
253             gst::error_msg!(
254                 gst::ResourceError::OpenRead,
255                 [
256                     "Could not open file {} for reading: {}",
257                     location,
258                     err.to_string(),
259                 ]
260             )
261         })?;
262 
263         gst_debug!(CAT, obj: element, "Opened file {:?}", file);
264 
265         *state = State::Started { file, position: 0 };
266 
267         gst_info!(CAT, obj: element, "Started");
268 
269         Ok(())
270     }
271 
stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage>272     fn stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> {
273         let mut state = self.state.lock().unwrap();
274         if let State::Stopped = *state {
275             return Err(gst::error_msg!(
276                 gst::ResourceError::Settings,
277                 ["FileSrc not started"]
278             ));
279         }
280 
281         *state = State::Stopped;
282 
283         gst_info!(CAT, obj: element, "Stopped");
284 
285         Ok(())
286     }
287 
fill( &self, element: &Self::Type, offset: u64, _length: u32, buffer: &mut gst::BufferRef, ) -> Result<gst::FlowSuccess, gst::FlowError>288     fn fill(
289         &self,
290         element: &Self::Type,
291         offset: u64,
292         _length: u32,
293         buffer: &mut gst::BufferRef,
294     ) -> Result<gst::FlowSuccess, gst::FlowError> {
295         let mut state = self.state.lock().unwrap();
296 
297         let (file, position) = match *state {
298             State::Started {
299                 ref mut file,
300                 ref mut position,
301             } => (file, position),
302             State::Stopped => {
303                 gst::element_error!(element, gst::CoreError::Failed, ["Not started yet"]);
304                 return Err(gst::FlowError::Error);
305             }
306         };
307 
308         if *position != offset {
309             file.seek(SeekFrom::Start(offset)).map_err(|err| {
310                 gst::element_error!(
311                     element,
312                     gst::LibraryError::Failed,
313                     ["Failed to seek to {}: {}", offset, err.to_string()]
314                 );
315                 gst::FlowError::Error
316             })?;
317 
318             *position = offset;
319         }
320 
321         let size = {
322             let mut map = buffer.map_writable().map_err(|_| {
323                 gst::element_error!(element, gst::LibraryError::Failed, ["Failed to map buffer"]);
324                 gst::FlowError::Error
325             })?;
326 
327             file.read(map.as_mut()).map_err(|err| {
328                 gst::element_error!(
329                     element,
330                     gst::LibraryError::Failed,
331                     ["Failed to read at {}: {}", offset, err.to_string()]
332                 );
333                 gst::FlowError::Error
334             })?
335         };
336 
337         *position += size as u64;
338 
339         buffer.set_size(size);
340 
341         Ok(gst::FlowSuccess::Ok)
342     }
343 }
344 
345 impl URIHandlerImpl for FileSrc {
346     const URI_TYPE: gst::URIType = gst::URIType::Src;
347 
protocols() -> &'static [&'static str]348     fn protocols() -> &'static [&'static str] {
349         &["file"]
350     }
351 
uri(&self, _element: &Self::Type) -> Option<String>352     fn uri(&self, _element: &Self::Type) -> Option<String> {
353         let settings = self.settings.lock().unwrap();
354 
355         // Conversion to Url already checked while building the `FileLocation`
356         settings.location.as_ref().map(|location| {
357             Url::from_file_path(location)
358                 .expect("FileSrc::get_uri couldn't build `Url` from `location`")
359                 .into()
360         })
361     }
362 
set_uri(&self, element: &Self::Type, uri: &str) -> Result<(), glib::Error>363     fn set_uri(&self, element: &Self::Type, uri: &str) -> Result<(), glib::Error> {
364         // Special case for "file://" as this is used by some applications to test
365         // with `gst_element_make_from_uri` if there's an element that supports the URI protocol
366 
367         if uri != "file://" {
368             let file_location = FileLocation::try_from_uri_str(uri)?;
369             self.set_location(element, Some(file_location))
370         } else {
371             Ok(())
372         }
373     }
374 }
375