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