use crate::constants::*; use serde_json::Value; use std::time::Duration; /// Information about an [`Input`] source. /// /// [`Input`]: crate::input::Input #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Metadata { /// The track of this stream. pub track: Option, /// The main artist of this stream. pub artist: Option, /// The date of creation of this stream. pub date: Option, /// The number of audio channels in this stream. /// /// Any number `>= 2` is treated as stereo. pub channels: Option, /// The YouTube channel of this stream. pub channel: Option, /// The time at which the first true sample is played back. /// /// This occurs as an artefact of coder delay. pub start_time: Option, /// The reported duration of this stream. pub duration: Option, /// The sample rate of this stream. pub sample_rate: Option, /// The source url of this stream. pub source_url: Option, /// The YouTube title of this stream. pub title: Option, /// The thumbnail url of this stream. pub thumbnail: Option, } impl Metadata { /// Extract metadata and details from the output of /// `ffprobe`. pub fn from_ffprobe_json(value: &Value) -> Self { let format = value.as_object().and_then(|m| m.get("format")); let duration = format .and_then(|m| m.get("duration")) .and_then(Value::as_str) .and_then(|v| v.parse::().ok()) .map(Duration::from_secs_f64); let start_time = format .and_then(|m| m.get("start_time")) .and_then(Value::as_str) .and_then(|v| v.parse::().ok().map(|t| t.max(0.0))) .map(Duration::from_secs_f64); let tags = format.and_then(|m| m.get("tags")); let track = tags .and_then(|m| m.get("title")) .and_then(Value::as_str) .map(str::to_string); let artist = tags .and_then(|m| m.get("artist")) .and_then(Value::as_str) .map(str::to_string); let date = tags .and_then(|m| m.get("date")) .and_then(Value::as_str) .map(str::to_string); let stream = value .as_object() .and_then(|m| m.get("streams")) .and_then(|v| v.as_array()) .and_then(|v| { v.iter() .find(|line| line.get("codec_type").and_then(Value::as_str) == Some("audio")) }); let channels = stream .and_then(|m| m.get("channels")) .and_then(Value::as_u64) .map(|v| v as u8); let sample_rate = stream .and_then(|m| m.get("sample_rate")) .and_then(Value::as_str) .and_then(|v| v.parse::().ok()) .map(|v| v as u32); Self { track, artist, date, channels, start_time, duration, sample_rate, ..Default::default() } } /// Use `youtube-dl`'s JSON output for metadata for an online resource. pub fn from_ytdl_output(value: Value) -> Self { let obj = value.as_object(); let track = obj .and_then(|m| m.get("track")) .and_then(Value::as_str) .map(str::to_string); let true_artist = obj .and_then(|m| m.get("artist")) .and_then(Value::as_str) .map(str::to_string); let artist = true_artist.or_else(|| { obj.and_then(|m| m.get("uploader")) .and_then(Value::as_str) .map(str::to_string) }); let r_date = obj .and_then(|m| m.get("release_date")) .and_then(Value::as_str) .map(str::to_string); let date = r_date.or_else(|| { obj.and_then(|m| m.get("upload_date")) .and_then(Value::as_str) .map(str::to_string) }); let channel = obj .and_then(|m| m.get("channel")) .and_then(Value::as_str) .map(str::to_string); let duration = obj .and_then(|m| m.get("duration")) .and_then(Value::as_f64) .map(Duration::from_secs_f64); let source_url = obj .and_then(|m| m.get("webpage_url")) .and_then(Value::as_str) .map(str::to_string); let title = obj .and_then(|m| m.get("title")) .and_then(Value::as_str) .map(str::to_string); let thumbnail = obj .and_then(|m| m.get("thumbnail")) .and_then(Value::as_str) .map(str::to_string); Self { track, artist, date, channels: Some(2), channel, duration, sample_rate: Some(SAMPLE_RATE_RAW as u32), source_url, title, thumbnail, ..Default::default() } } /// Move all fields from a `Metadata` object into a new one. pub fn take(&mut self) -> Self { Self { track: self.track.take(), artist: self.artist.take(), date: self.date.take(), channels: self.channels.take(), channel: self.channel.take(), start_time: self.start_time.take(), duration: self.duration.take(), sample_rate: self.sample_rate.take(), source_url: self.source_url.take(), title: self.title.take(), thumbnail: self.thumbnail.take(), } } }