From b02a54658d7ac2dab3e559d60fdafc5922bf9555 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Wed, 12 Jul 2023 21:15:39 -0600 Subject: [PATCH 01/24] Added playlist-rs module --- modules/demux/playlist-rs/Cargo.toml | 10 + modules/demux/playlist-rs/src/fmt_m3u.rs | 345 ++++++++++++++++++ modules/demux/playlist-rs/src/fmt_pls.rs | 136 +++++++ modules/demux/playlist-rs/src/fmt_qtl.rs | 198 ++++++++++ modules/demux/playlist-rs/src/fmt_sgimb.rs | 136 +++++++ modules/demux/playlist-rs/src/fmt_wms.rs | 86 +++++ modules/demux/playlist-rs/src/fmt_wpl.rs | 243 ++++++++++++ modules/demux/playlist-rs/src/fmt_xspf.rs | 324 ++++++++++++++++ modules/demux/playlist-rs/src/lib.rs | 186 ++++++++++ modules/demux/playlist-rs/src/m3u.rs | 144 ++++++++ modules/demux/playlist-rs/src/util_buf.rs | 62 ++++ .../demux/playlist-rs/src/util_input_item.rs | 37 ++ modules/demux/playlist-rs/src/util_xml.rs | 230 ++++++++++++ modules/demux/playlist-rs/todo.md | 4 + 14 files changed, 2141 insertions(+) create mode 100644 modules/demux/playlist-rs/Cargo.toml create mode 100644 modules/demux/playlist-rs/src/fmt_m3u.rs create mode 100644 modules/demux/playlist-rs/src/fmt_pls.rs create mode 100644 modules/demux/playlist-rs/src/fmt_qtl.rs create mode 100644 modules/demux/playlist-rs/src/fmt_sgimb.rs create mode 100644 modules/demux/playlist-rs/src/fmt_wms.rs create mode 100644 modules/demux/playlist-rs/src/fmt_wpl.rs create mode 100644 modules/demux/playlist-rs/src/fmt_xspf.rs create mode 100644 modules/demux/playlist-rs/src/lib.rs create mode 100644 modules/demux/playlist-rs/src/m3u.rs create mode 100644 modules/demux/playlist-rs/src/util_buf.rs create mode 100644 modules/demux/playlist-rs/src/util_input_item.rs create mode 100644 modules/demux/playlist-rs/src/util_xml.rs create mode 100644 modules/demux/playlist-rs/todo.md diff --git a/modules/demux/playlist-rs/Cargo.toml b/modules/demux/playlist-rs/Cargo.toml new file mode 100644 index 000000000000..8d6645b34bbd --- /dev/null +++ b/modules/demux/playlist-rs/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "playlist-rs" +version = "0.1.0" +license = "LGPL-2.1-or-later" +edition = "2021" + +[dependencies] +vlcrs-core = { workspace = true } +vlcrs-core-macros = { workspace = true } +xml-rs = "0.8.15" \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs new file mode 100644 index 000000000000..488468fcae2a --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -0,0 +1,345 @@ +//! Playlist_M3U Demux module + +use std::{ + collections::HashSet, + io::{BufRead, Read}, + rc::Rc, + str, +}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Seconds, Tick}, +}; + +use crate::{ + util_buf::{find_in_buf, read_float, read_to_end, skip_chars, starts_with_ignore_case}, + util_input_item::InputItemMeta, + InputContext, PlaylistFormat, +}; + +pub struct FormatM3U; + +impl PlaylistFormat for FormatM3U { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is m3u8"); + let peek = input.source.peek(1024)?; + let mut buf = peek.buf(); + if buf.len() < 8 { + debug!(input.logger, "Invalid buffer length {}", buf.len()); + return Err(CoreError::Unknown); + } + + /* UTF-8 Byte Order Mark */ + if &buf[..3] == &[0xEF, 0xBB, 0xBF] { + if buf.len() < 12 { + debug!(input.logger, "Invalid buffer length {}", buf.len()); + return Err(CoreError::Unknown); + } + // offset = 3; + buf = &buf[3..]; + } + + if !buf.starts_with("#EXTM3U".as_bytes()) + && !buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) + && !containsURL(buf)? + && !input.has_mime_type("application/mpegurl") + && !input.has_mime_type("application/x-mpegurl") + && !input.has_mime_type("audio/mpegurl") + && !input.has_mime_type("vnd.apple.mpegURL") + && !input.has_mime_type("audio/x-mpegurl") + && !input.has_extension(".m3u8") + && !input.has_extension(".m3u") + && !input.has_extension(".vlc") + { + return Err(CoreError::Unknown); + } + + debug!(input.logger, "Found a valid M3U playlist"); + + return Ok(true); + } + + fn parse( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + let mut all_buf = String::new(); + input + .source + .read_to_string(&mut all_buf) + .map_err(|_| CoreError::Unknown)?; + + let mut meta: InputItemMeta = Default::default(); + + for line in all_buf.lines() { + debug!(input.logger, "Line: {line}"); + + let line_buf = line.as_bytes(); + + let mut buf_i = 0; + let end = line_buf.len(); + + // Skip leading tabs and spaces + skip_chars(line_buf, &mut buf_i, " \t\n\r".as_bytes()); + + if buf_i >= end { + break; + } + + // Row has some content + + if line_buf[buf_i] as char == '#' { + // Parse extra info + + // Skip leading tabs and spaces + skip_chars(line_buf, &mut buf_i, " \t\n\r#".as_bytes()); + + if buf_i >= end { + break; + } + + // Row still has some content left + if starts_with_ignore_case(line_buf, buf_i, "EXTINF:") { + buf_i += 7; + let parsed_duration = read_float(line_buf, &mut buf_i)?; + if parsed_duration > 0.0 { + meta.duration = Tick::from_seconds(Seconds::from(parsed_duration)); + } + skip_chars(line_buf, &mut buf_i, " \t".as_bytes()); + if buf_i >= end { + break; + } + + if line_buf[buf_i] as char == ',' { + /* EXTINF:1,title*/ + /* EXTINF: -123.12 ,title*/ + (meta.artist, meta.name) = parse_EXTINF_title(line_buf, buf_i + 1)?; + } else if line_buf[buf_i].is_ascii_alphabetic() { + /*EXTINF: -1 tvg-foo="val" tvg-foo2="val",title + EXTINF: -1 tvg-foo="val,val2" ,title*/ + + match parse_EXTINF_iptv_diots_in_duration(&mut meta, line_buf, &mut buf_i) { + Ok(_) => { + if buf_i < end && line_buf[buf_i] as char == ',' { + buf_i += 1; + meta.name = read_to_end(line_buf, buf_i)?; + } + } + Err(_) => debug!(input.logger, "Parsing of IPTV diots failed"), + } + } + } else if starts_with_ignore_case(line_buf, buf_i, "EXTGRP:") { + meta.group = read_to_end(line_buf, buf_i + 7)?; + } else if starts_with_ignore_case(line_buf, buf_i, "EXTVLCOPT:") { + if let Some(vlc_option) = read_to_end(line_buf, buf_i + 10)? { + meta.options.push(vlc_option); + } + } else if starts_with_ignore_case(line_buf, buf_i, "EXTALBUMARTURL:") { + meta.album_art = read_to_end(line_buf, buf_i + 15)?; + } else if starts_with_ignore_case(line_buf, buf_i, "PLAYLIST:") { + if let Some(mut input_item) = input_item_node.get_item() { + input_item.set_title(read_to_end(line_buf, buf_i + 15)?); + } + } + } else if starts_with_ignore_case(line_buf, buf_i, "RTSPtext") { + // special case to handle QuickTime RTSPtext redirect files + } else { + let media_path = read_to_end(line_buf, buf_i)?.ok_or(CoreError::Unknown)?; + if meta.group.is_some() && meta.group_title.is_none() { + meta.group_title = meta.group.clone(); + } + let meta_mrl = input.process_mrl(media_path)?; + + let mut input_item = InputItem::new( + &meta_mrl, + match meta.name { + Some(name) => name, + None => media_path, + }, + )?; + + input_item.set_duration(meta.duration); + input_item.set_artist(meta.artist); + input_item.set_title(meta.name); + input_item.set_artwork_url(meta.album_art); + input_item.set_language(meta.language); + input_item.set_publisher(meta.group_title); + for option in meta.options.iter() { + // TODO: Pass No Flag (0) + input_item.add_option( + option, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_UNIQUE, + )?; + } + input_item_node.append_item(&input_item)?; + } + } + + Ok(()) + } + + fn get_extension(&self) -> &'static [&'static str] { + &[".m3u"] + } +} + +fn parse_EXTINF_title<'a>( + buf: &'a [u8], + buf_i: usize, +) -> Result<(Option<&'a str>, Option<&'a str>)> { + let (artist_end, name_start) = match find_in_buf(buf, buf_i.clone(), " - ".as_bytes()) { + Some(dash_i) => (dash_i, dash_i + 3), + None => match find_in_buf(buf, buf_i.clone(), ",".as_bytes()) { + Some(sep_i) => (sep_i, sep_i + 1), + None => (buf_i, buf_i), + }, + }; + + return Ok(( + if artist_end > buf_i { + Some(str::from_utf8(&buf[buf_i..artist_end]).map_err(|_| CoreError::Unknown)?) + } else { + None + }, + if name_start < buf.len() { + Some(str::from_utf8(&buf[name_start..]).map_err(|_| CoreError::Unknown)?) + } else { + None + }, + )); +} + +fn parse_EXTINF_iptv_diots_in_duration<'a>( + meta: &mut InputItemMeta<'a>, + buf: &'a [u8], + buf_i: &mut usize, +) -> Result<()> { + let mut escaped = false; + let mut key_start: Option<usize> = None; + let mut val_start: Option<usize> = None; + let mut eq_start: Option<usize> = None; + + while *buf_i < buf.len() { + match buf[*buf_i] as char { + ' ' | ',' => { + if !escaped && key_start.is_some() && val_start.is_some() { + let key_i = key_start.ok_or(CoreError::Unknown)?; + let val_i = val_start.ok_or(CoreError::Unknown)?; + let eq_i = eq_start.ok_or(CoreError::Unknown)?; + parse_EXTINF_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; + (key_start, val_start, eq_start) = (None, None, None); + } + + if !escaped && buf[*buf_i] as char == ',' { + return Ok(()); + } + } + '=' => { + if !escaped { + if eq_start.is_some() || key_start.is_none() || val_start.is_some() { + return Err(CoreError::Unknown); + } + eq_start = Some(*buf_i); + } + } + c => { + if c == '"' { + if !escaped && val_start.is_some() { + return Err(CoreError::Unknown); + } + if key_start.is_none() { + return Err(CoreError::Unknown); + } + escaped = !escaped; + } + + if eq_start.is_none() && key_start.is_none() { + key_start = Some(*buf_i); + } else if eq_start.is_some() && val_start.is_none() { + val_start = Some(*buf_i); + } + } + } + *buf_i = *buf_i + 1; + } + + if !escaped && key_start.is_some() && val_start.is_some() { + let key_i = key_start.ok_or(CoreError::Unknown)?; + let val_i = val_start.ok_or(CoreError::Unknown)?; + let eq_i = eq_start.ok_or(CoreError::Unknown)?; + parse_EXTINF_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; + } + + Ok(()) +} + +fn parse_EXTINF_iptv_diots<'a>( + meta: &mut InputItemMeta<'a>, + buf: &'a [u8], + mut key_start: usize, + mut key_end: usize, + mut val_start: usize, + mut val_end: usize, +) -> Result<()> { + // Remove quotes if present + if buf[val_start] as char == '"' { + if val_end - val_start < 2 || buf[val_end] as char != '"' { + return Err(CoreError::Unknown); + } + val_start += 1; + val_end -= 1; + } + let val = Some(str::from_utf8(&buf[val_start..=val_end]).map_err(|_| CoreError::Unknown)?); + + if starts_with_ignore_case(buf, key_start, "tvg-logo") { + meta.album_art = val; + } else if starts_with_ignore_case(buf, key_start, "tvg-name") { + meta.name = val; + } else if starts_with_ignore_case(buf, key_start, "tvg-language") { + meta.language = val; + } else if starts_with_ignore_case(buf, key_start, "tvg-id") { + meta.tvgid = val; + } else if starts_with_ignore_case(buf, key_start, "group-title") { + meta.group_title = val; + } + + Ok(()) +} + +/// Function to check if the first non-comment line contains url +fn containsURL(buf: &[u8]) -> Result<bool> { + for line in buf.lines() { + let l: String = line.map_err(|_| CoreError::Unknown)?; + if l.is_empty() || l.starts_with('#') { + continue; + } + match l.find("://") { + Some(3) => { + return Ok(l[..3].eq_ignore_ascii_case("mms") || l[..3].eq_ignore_ascii_case("ftp")) + } + Some(4) => { + return Ok(l[..4].eq_ignore_ascii_case("http") + || l[..4].eq_ignore_ascii_case("rtsp") + || l[..4].eq_ignore_ascii_case("ftps")) + } + Some(5) => { + return Ok( + l[..5].eq_ignore_ascii_case("https") || l[..5].eq_ignore_ascii_case("ftpes") + ) + } + _ => return Ok(false), + } + } + return Ok(false); +} diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs new file mode 100644 index 000000000000..6f95952a2d01 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -0,0 +1,136 @@ +use std::{io::Read, path::Path, rc::Rc}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{InputItem, self, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + url::Url, +}; + +use crate::{PlaylistFormat, InputContext}; + +pub struct FormatPLS; + +impl PlaylistFormat for FormatPLS { + fn can_open( + &mut self, + input: &mut InputContext, + ) -> Result<bool> { + debug!(input.logger, "Testing if file is PLS"); + let peek = input.source.peek(1024)?; + let mut buf = peek.buf(); + + if buf.len() < 10 { + return Err(CoreError::Unknown); + } + + let header = std::str::from_utf8(&buf[..10])?; + + if !header.eq_ignore_ascii_case("[playlist]") && !input.has_extension(".pls") { + debug!(input.logger, "Not a valid PLS playlist file"); + return Err(CoreError::Unknown); + } + + return Ok(true); + } + + fn parse<'a>( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode + ) -> Result<()> { + debug!(input.logger, "Read dir called"); + + // TODO: Temporary solution to get lines from stream + let mut buf: String = String::new(); + input.source + .read_to_string(&mut buf) + .map_err(|_| CoreError::Unknown)?; + + let mut i_item: i32 = -1; + let mut psz_mrl: Rc<String> = String::new().into(); + let mut psz_name: Rc<String> = String::new().into(); + + for line in buf.lines() { + debug!(input.logger, "Line: {line}"); + + if line == "[playlist]" { + continue; + } + + let (psz_key, psz_value) = match line.split_once('=') { + Some(parts) => parts, + None => continue, + }; + + match psz_key { + "version" => debug!(input.logger, "pls file version: {psz_value}"), + "numberofentries" => debug!(input.logger, "pls should have {psz_value} entries"), + _ => { + let (_, i_new_item_str) = + psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); + + if i_new_item_str.is_empty() { + debug!(input.logger, "couldn't find item number in key"); + continue; + } + + let i_new_item = i_new_item_str + .parse::<i32>() + .map_err(|_| CoreError::Unknown)?; + + if i_item == -1 { + i_item = i_new_item; + } else if i_item != i_new_item { + debug!(input.logger, "Adding item {psz_mrl}, {psz_name}"); + let input_item = InputItem::new(&psz_mrl, &psz_name)?; + input_item_node.append_item(&input_item)?; + i_item = i_new_item; + } + + let key_name = &psz_key[..psz_key.len() - i_new_item_str.len()]; + + debug!(input.logger, "Key: [{key_name}] Item : {i_item}"); + match key_name.to_ascii_lowercase().as_str() { + "file" => { + let abs_path = input.process_mrl(psz_value)?; + psz_mrl = Rc::new(abs_path); + } + "title" => { + psz_name = Rc::new(psz_value.to_owned()); + } + "length" => {} + _ => { + debug!(input.logger, "unknown key found in pls file: {key_name}"); + } + }; + } + }; + } + + // Add last item + if i_item != -1 { + let input_item = InputItem::new(&psz_mrl, &psz_name)?; + input_item_node.append_item(&input_item)?; + + debug!( + input.logger, + "Added last item {}, {}", + psz_mrl.to_string(), + psz_name.to_string() + ); + } + + Ok(()) + + } + +} diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs new file mode 100644 index 000000000000..74b21a9a0fb9 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -0,0 +1,198 @@ +use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str}; + +use vlcrs_core::{ + debug, error, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Seconds, Tick}, + warn, +}; +use xml::{ + attribute::OwnedAttribute, + name::{Name, OwnedName}, + reader::{Events, XmlEvent}, + EventReader, +}; + +use crate::{util_input_item::InputItemMeta, PlaylistFormat, util_xml::{XmlParser}, InputContext}; + +const ROOT_NODE_MAX_DEPTH: u32 = 2; +pub struct FormatQTL; + +pub struct DemuxerQTL<'a> { + source: &'a mut Stream, + es_out: EsOutBaked<'a>, + logger: &'a mut Logger, + path: Option<String>, + input_item: Option<&'a mut InputItem>, +} + +#[derive(Debug)] +enum QtlFullScreenType { + FullScreenNormal, + FullScreenDouble, + FullScreenHalf, + FullScreenCurrent, + FullScreenFull, +} + +#[derive(Debug)] +enum QtlLoopType { + LoopTrue, + LoopFalse, + LoopPalindrome, +} + +impl PlaylistFormat for FormatQTL { + fn can_open( + &mut self, + input: &mut InputContext + ) -> Result<bool> { + debug!(input.logger, "Testing if file is QTL"); + + if !input.has_extension(".qtl") { + return Ok(false); + } + + debug!(input.logger, "using QuickTime Media Link reader"); + + return Ok(true); + } + + fn parse<'a>( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode + ) -> Result<()> { + debug!(input.logger, "Starting QTL parser"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + let attributes = xml_parser.find_next_element("embed", ROOT_NODE_MAX_DEPTH)?; + + let mut autoplay: bool = false; + let mut controller: bool = false; + let mut fullscreen: Option<QtlFullScreenType> = None; + let mut href: Option<String> = None; + let mut kiosk_mode: bool = false; + let mut loop_type: QtlLoopType = QtlLoopType::LoopFalse; + let mut movie_id: i32 = -1; + let mut movie_name: Option<String> = None; + let mut play_every_frame: bool = false; + let mut qtnext: Option<String> = None; + let mut quit_when_done: bool = false; + let mut src: Option<String> = None; + let mut mime_type: Option<String> = None; + let mut volume: i32 = 100; + + for attr in attributes { + let attr_val = attr.value; + match attr.name.local_name.as_str() { + "autoplay" => autoplay = attr_val == "true", + "controller" => controller = attr_val == "false", + "fullscreen" => { + fullscreen = Some(match attr_val.as_str() { + "double" => QtlFullScreenType::FullScreenDouble, + "half" => QtlFullScreenType::FullScreenHalf, + "current" => QtlFullScreenType::FullScreenCurrent, + "full" => QtlFullScreenType::FullScreenFull, + _ => QtlFullScreenType::FullScreenNormal, + }) + } + "href" => href = Some(attr_val), + "kioskmode" => kiosk_mode = attr_val == "true", + "loop" => { + loop_type = match attr_val.as_str() { + "true" => QtlLoopType::LoopTrue, + "palindrome" => QtlLoopType::LoopPalindrome, + _ => QtlLoopType::LoopFalse, + } + } + "movieid" => movie_id = attr_val.parse().map_err(|_| CoreError::Unknown)?, + "moviename" => movie_name = Some(attr_val), + "playeveryframe" => play_every_frame = attr_val == "true", + "qtnext" => qtnext = Some(attr_val), + "quitwhendone" => quit_when_done = attr_val == "true", + "src" => src = Some(attr_val), + "mimetype" => mime_type = Some(attr_val), + "volume" => volume = attr_val.parse().map_err(|_| CoreError::Unknown)?, + attr_name => debug!( + input.logger, + "Attribute {attr_name} with value {attr_val} isn't valid" + ), + } + } + + debug!(input.logger, "autoplay: {autoplay:?} (unused by VLC)"); + debug!(input.logger, "controller: {controller:?} (unused by VLC)"); + debug!(input.logger, "fullscreen: {fullscreen:?} (unused by VLC)"); + debug!(input.logger, "href: {href:?}"); + debug!(input.logger, "kioskmode: {kiosk_mode:?} (unused by VLC)"); + debug!(input.logger, "loop: {loop_type:?} (unused by VLC)"); + debug!(input.logger, "movieid: {movie_id:?} (unused by VLC)"); + debug!(input.logger, "moviename: {movie_name:?}"); + debug!( + input.logger, + "playeverframe: {play_every_frame:?} (unused by VLC)" + ); + debug!(input.logger, "qtnext: {qtnext:?}"); + debug!( + input.logger, + "quitwhendone: {quit_when_done:?} (unused by VLC)" + ); + debug!(input.logger, "src: {src:?}"); + debug!(input.logger, "mimetype: {mime_type:?}"); + debug!(input.logger, "volume: {volume:?} (unused by VLC)"); + + if src.is_none() { + error!(input.logger, "Mandatory attribute 'src' not found"); + return Err(CoreError::Unknown); + } + + let input_uri = src.ok_or(CoreError::Unknown)?; + let mut input_item = if movie_name.is_some() { + let input_name = movie_name.ok_or(CoreError::Unknown)?; + InputItem::new(&input_uri, &input_name) + } else { + // TODO: NULL input name + InputItem::new(&input_uri, &input_uri) + }?; + + if let Some(href_val) = href { + input_item.add_info_str("QuickTime Media Link", "href", &href_val)?; + } + if let Some(mime_type_val) = mime_type { + input_item.add_info_str("QuickTime Media Link", "Mime", &mime_type_val)?; + } + + input_item_node.append_item(&input_item)?; + + if let Some(qtnext_val) = qtnext { + // TODO: Verify attribute value is unescaped + input_item_node.append_item(&InputItem::new(&qtnext_val, &qtnext_val)?)?; + } + + Ok(()) + } + + fn get_extension(&self) -> &'static [&'static str] { + &[".qtl"] + } +} + + diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs new file mode 100644 index 000000000000..b31b4ec653ce --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -0,0 +1,136 @@ +use std::{io::Read, path::Path, rc::Rc}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::InputItem, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + url::Url, +}; + +use crate::{has_extension, PlaylistFormat, process_mrl}; + +pub struct FormatSGIMB; + +impl PlaylistFormat for FormatSGIMB { + fn try_open( + &mut self, + path: &Option<&str>, + buf: &[u8], + es_out: &EsOut, + logger: &mut Logger, + _args: &ModuleArgs, + ) -> Result<bool> { + debug!(logger, "Testing if file is SGIMB"); + + // find_in_buf() + + let header = std::str::from_utf8(&buf[..10])?; + + if !header.eq_ignore_ascii_case("[playlist]") && !has_extension(path, ".SGIMB") { + debug!(logger, "Not a valid SGIMB playlist file"); + return Err(CoreError::Unknown); + } + + return Ok(true); + } + + + fn parse( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(self.logger, "Read dir called"); + + // TODO: Temporary solution to get lines from stream + let mut buf: String = String::new(); + self.source + .read_to_string(&mut buf) + .map_err(|_| CoreError::Unknown)?; + + let mut i_item: i32 = -1; + let mut psz_mrl: Rc<String> = String::new().into(); + let mut psz_name: Rc<String> = String::new().into(); + + for line in buf.lines() { + debug!(self.logger, "Line: {line}"); + + if line == "[playlist]" { + continue; + } + + let (psz_key, psz_value) = match line.split_once('=') { + Some(parts) => parts, + None => continue, + }; + + match psz_key { + "version" => debug!(self.logger, "SGIMB file version: {psz_value}"), + "numberofentries" => debug!(self.logger, "SGIMB should have {psz_value} entries"), + _ => { + let (_, i_new_item_str) = + psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); + + if i_new_item_str.is_empty() { + debug!(self.logger, "couldn't find item number in key"); + continue; + } + + let i_new_item = i_new_item_str + .parse::<i32>() + .map_err(|_| CoreError::Unknown)?; + + if i_item == -1 { + i_item = i_new_item; + } else if i_item != i_new_item { + debug!(self.logger, "Adding item {psz_mrl}, {psz_name}"); + let input = InputItem::new(&psz_mrl, &psz_name)?; + input_item_node.append_item(&input)?; + i_item = i_new_item; + } + + let key_name = &psz_key[..psz_key.len() - i_new_item_str.len()]; + + debug!(self.logger, "Key: [{key_name}] Item : {i_item}"); + match key_name.to_ascii_lowercase().as_str() { + "file" => { + let abs_path = process_mrl(self.path.as_deref(), psz_value)?; + psz_mrl = Rc::new(abs_path); + } + "title" => { + psz_name = Rc::new(psz_value.to_owned()); + } + "length" => {} + _ => { + debug!(self.logger, "unknown key found in SGIMB file: {key_name}"); + } + }; + } + }; + } + + // Add last item + if i_item != -1 { + let input = InputItem::new(&psz_mrl, &psz_name)?; + input_item_node.append_item(&input)?; + + debug!( + self.logger, + "Added last item {}, {}", + psz_mrl.to_string(), + psz_name.to_string() + ); + } + + Ok(()) + } + +} diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs new file mode 100644 index 000000000000..85c456b5c944 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -0,0 +1,86 @@ +use std::{io::Read, path::Path, rc::Rc}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + url::Url, + warn, +}; + +use crate::{util_buf::starts_with_ignore_case, PlaylistFormat, InputContext}; + +pub struct FormatWMS; + + +impl PlaylistFormat for FormatWMS { + fn can_open( + &mut self, + input: &mut InputContext + ) -> Result<bool> { + debug!(input.logger, "Testing if file is WMS"); + let peek = input.source.peek(1024)?; + let mut buf = peek.buf(); + + if buf.len() < 10 || !starts_with_ignore_case(buf, 0, "[Reference]") { + return Err(CoreError::Unknown); + } + + debug!(input.logger, "Found WMS metafile"); + + return Ok(true); + } + + fn parse<'a>( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading WMS metafile"); + + // TODO: Temporary solution to get lines from stream + let mut buf: String = String::new(); + input.source + .read_to_string(&mut buf) + .map_err(|_| CoreError::Unknown)?; + + for line in buf.lines() { + debug!(input.logger, "Line: {line}"); + + if line == "[Reference]" { + continue; + } + + if let Some((key, value_str)) = line.split_once('=') { + if value_str.is_empty() { + warn!(input.logger, "unexpected entry value \"{line}\""); + continue; + } + + if !key.starts_with("Ref") || key[3..].parse::<u32>().is_err() { + warn!(input.logger, "unexpected entry value \"{line}\""); + continue; + } + + let mut value = if value_str[..7].eq_ignore_ascii_case("http://") { + ("mmsh".to_owned() + &value_str[4..]) + } else { + value_str.to_owned() + }; + + let input_item = InputItem::new(&value, &value)?; + input_item_node.append_item(&input_item); + }; + } + + Ok(()) + } +} diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs new file mode 100644 index 000000000000..b8320ebf87fe --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -0,0 +1,243 @@ +use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str}; + +use vlcrs_core::{ + debug, error, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Seconds, Tick}, + warn, +}; +use xml::{ + attribute::OwnedAttribute, + name::{Name, OwnedName}, + reader::{Events, XmlEvent}, + EventReader, +}; + +use crate::{ + util_input_item::InputItemMeta, + util_xml::{parse_node, XmlParser}, + InputContext, PlaylistFormat, +}; + +const MAX_SEARCH_DEPTH: u32 = 100; + +pub struct FormatWPL; + +impl PlaylistFormat for FormatWPL { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is WPL"); + + if !input.has_extension(".wpl") && !input.has_extension(".zpl") { + return Ok(false); + } + + let peek = input.source.peek(2048)?; + let buf = peek.buf(); + // Should be an XML file & Root element should be smil + let parser = EventReader::new(buf); + + for e in parser { + if let XmlEvent::StartElement { name, .. } = e.map_err(|_| CoreError::Unknown)? { + if name.local_name.eq_ignore_ascii_case("smil") { + debug!(input.logger, "Found smil tag in WPL playlist"); + return Ok(true); + } else { + error!( + input.logger, + "Invalid WPL playlist. Root element should have been <smil>" + ); + return Ok(false); + } + }; + } + + return Ok(false); + } + + fn parse( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Starting WPL parser"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + xml_parser.find_next_element("smil", 1)?; + + parse_node!(&mut xml_parser, "smil", input_item_node, input_item, { + "head" => read_head, + "body" => read_body + }); + + Ok(()) + } + + fn get_extension(&self) -> &'static [&'static str] { + &[".wpl", ".zpl"] + } +} + +fn read_head( + parser: &mut XmlParser, + name: &str, + _attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + parse_node!(parser, name, ii_node, input_item, { + "meta" => parse_meta, + "title" => read_title + }); + // while let Some(e) = parser_iter.next() { + // let elem = e.map_err(|_| CoreError::Unknown)?; + // match elem { + // XmlEvent::StartElement { + // name, attributes, .. + // } => { + // if name.local_name.eq_ignore_ascii_case("meta") { + // if let Some(input_item_val) = input.input_item.as_deref_mut() { + // parse_meta(input.logger, &attributes, input_item_val)?; + // } else { + // warn!(input.logger, "Cannot update meta. Playlsit input item not found."); + // } + // } else if name.local_name.eq_ignore_ascii_case("title") { + // if let Some(input_item_val) = input.input_item.as_deref_mut() { + // read_title(input.logger, parser_iter, input_item_val)?; + // } else { + // warn!( + // input.logger, + // "Cannot update title. Playlsit input item not found." + // ); + // } + // } else { + // warn!( + // input.logger, + // "Skipping unknown tag: <{}> in <head>", name.local_name + // ); + // } + // } + // XmlEvent::EndElement { name, .. } => { + // if name.local_name.eq_ignore_ascii_case("head") { + // debug!(input.logger, "Closing head tag found"); + // break; + // } + // } + // _ => {} + // } + // } + + Ok(()) +} + +fn read_body( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + parser.find_next_element("seq", 1)?; + parse_node!(parser, "seq", ii_node, input_item, { + "media" => parse_media + }); + Ok(()) +} + +fn parse_meta( + parser: &mut XmlParser, + _name: &str, + attributes: &[OwnedAttribute], + _ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let mut option_name: Option<&str> = None; + let mut option_content: Option<&str> = None; + + for attr in attributes { + let attr_name = attr.name.local_name.as_str(); + let attr_value = attr.value.as_str(); + if option_name.is_none() && attr_name.eq_ignore_ascii_case("name") { + option_name = Some(attr_value); + } else if option_content.is_none() && attr_name.eq_ignore_ascii_case("content") { + option_content = Some(attr_value); + } else { + continue; + } + + if option_name.is_some() && option_content.is_some() { + break; + } + } + + if let (Some(name), Some(content)) = (option_name, option_content) { + if name.eq_ignore_ascii_case("TotalDuration") { + /* + TODO: input_item_AddInfo( p_input, _("Playlist"), _("Total duration"), + "%lld", atoll( psz_meta_content ) ); + */ + } else if name.eq_ignore_ascii_case("Author") { + input_item.set_publisher(Some(content)); + } else if name.eq_ignore_ascii_case("Rating") { + input_item.set_rating(Some(content)); + } else if name.eq_ignore_ascii_case("Genre") { + input_item.set_genre(Some(content)); + } else { + warn!(parser.logger, "Ignoring unknown meta-attribute {}", name); + } + } + + Ok(()) +} + +fn read_title( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let value = parser.parse_text_node(name)?; + if value.is_empty() { + return Err(CoreError::Unknown); + } + input_item.set_title(Some(&value)); + Ok(()) +} + +fn parse_media( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + for attr in attributes { + let attr_name = attr.name.local_name.as_str(); + let attr_value = attr.value.as_str(); + if attr_name.eq_ignore_ascii_case("src") && !attr_value.is_empty() { + debug!(parser.logger, "Adding playlist item : {}", attr_value); + let path = parser.process_mrl(attr_value)?; + // TODO: new InputItem with NULL name argument + let input_item = InputItem::new(&path, &path)?; + ii_node.append_item(&input_item)?; + } + } + Ok(()) +} diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs new file mode 100644 index 000000000000..29cb60a160fd --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -0,0 +1,324 @@ +use std::{any::Any, io::Read, path::Path, rc::Rc}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Miliseconds, Tick}, + url::Url, + warn, +}; +use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; + +use crate::{ + util_buf::starts_with_ignore_case, + util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, + util_xml::{parse_node, XmlParser}, + InputContext, PlaylistFormat, +}; + +pub struct FormatXSPF; + +impl PlaylistFormat for FormatXSPF { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is XSPF"); + + if !input.has_extension(".xspf") && !input.has_mime_type("application/xspf+xml") { + return Err(CoreError::Unknown); + } + + debug!(input.logger, "Found XSPF metafile"); + return Ok(true); + } + + fn parse<'a>( + &'a mut self, + mut input: &'a mut InputContext<'a>, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading XSPF metafile"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + xml_parser.find_next_element("playlist", 1)?; + + parse_node!(&mut xml_parser, "playlist", input_item_node, input_item, { + "title" => set_item_info, + "creator" => set_item_info, + "annotation" => set_item_info, + "image" => set_item_info, + "extension" => parse_extension_node, + "tracklist" => parse_tracklist_node + }); + + Ok(()) + } + + fn get_extension(&self) -> &'static [&'static str] { + &[".xspf"] + } +} + +fn parse_extension_node( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let application = parser.get_required_attrib(name, attributes, "application")?; + + if application != "http://www.videolan.org/vlc/playlist/0" { + debug!(parser.logger, "Skipping \"{application}\" extension tag"); + return parser.skip_element(name); + } + + parse_node!(parser, name, ii_node, input_item, { + "vlc:node" => parse_vlcnode_node, + "vlc:id" => parse_vlcid, + "vlc:option" => set_option + }); + + Ok(()) +} + +fn parse_tracklist_node( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + parse_node!(parser, name, ii_node, input_item, { + "track" => parse_track_node + }); + + Ok(()) +} + +fn parse_track_node( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + _input_item: &mut InputItem, +) -> Result<()> { + let mut new_ii = InputItem::new_empty()?; + // let mut new_ii_node = ii_node.append_item(&new_ii)?; + // let mut new_ii_pair = (ii_node, &mut Some(&mut new_ii)); + + parse_node!(parser, name, ii_node, &mut new_ii, { + "location" => parse_location, + // "identifier" => set_item_info, + "title" => set_item_info, + "creator" => set_item_info, + "annotation" => set_item_info, + "info" => set_item_info, + "image" => set_item_info, + "album" => set_item_info, + "trackNum" => set_item_info, + "duration" => set_item_info, + // "link" => set_item_info, + // "meta" => set_item_info, + "extension" => set_item_info + }); + + if new_ii.uri().is_none() { + new_ii.set_uri(Some(INPUT_ITEM_URI_NOP)); + } + + debug!( + parser.logger, + "Adding input_item with url={:?}", + new_ii.url() + ); + + // ii.set_duration(new_ii.duration()); + + ii_node.append_item(&new_ii)?; + + // TODO: Verify track tree + // match parser.track_id { + // Some(val) if val < i32::MAX && val > -1 => { + // if (val as usize) >= parser.track_list.len() { + // parser.track_list.resize_with(val as usize, || None); + // } + // if parser.track_list[val as usize].is_some() { + // warn!(parser.logger, "track ID {val} collision"); + // ii_pair.0.append_item(ii_pair.1)?; + // } + // }, + // _ => { + // // ii_node.append_node(&new_ii_node); + // }, + // } + + Ok(()) +} + +fn parse_vlcnode_node( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let title = parser.get_required_attrib(name, attributes, "title")?; + /* + input_item_NewExt( + "vlc://nop", psz_title, (0LL), ITEM_TYPE_DIRECTORY, ITEM_NET_UNKNOWN + ) + */ + // TODO: Uncomment + // if let Ok(mut new_ii) = InputItem::new_ext("vlc://nop", title, Tick::ZERO, input_item::Type::ITEM_TYPE_DIRECTORY, input_item::NetType::ITEM_NET_UNKNOWN) { + // if let Ok(mut new_ii_node) = ii_pair.0.append_item(&new_ii) { + // // ii_node = &mut new_ii_node; + // let mut new_ii_opt = Some(&mut new_ii); + // let mut new_ii_pair = (&mut new_ii_node, &mut new_ii_opt); + + // // TODO: Move Outside + // parse_node!(parser, name, &mut new_ii_pair, { + // "vlc:node" => parse_vlcnode_node, + // "vlc:item" => parse_extitem_node, + // "vlc:id" => parse_vlcid, + // "vlc:option" => set_option + // }); + + // return Ok(()) + // } + // }; + + // Otherwise parse with parent ii_pair + parse_node!(parser, name, ii_node, input_item, { + "vlc:node" => parse_vlcnode_node, + "vlc:item" => parse_extitem_node, + "vlc:id" => parse_vlcid, + "vlc:option" => set_option + }); + // parser.add_input_item(input_item)?; + + Ok(()) +} + +fn parse_vlcid( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + _ii_node: &mut InputItemNode, + _input_item: &mut InputItem, +) -> Result<()> { + let value = parser.parse_text_node(name)?; + if !value.is_empty() { + parser.track_id = Some(value.parse().map_err(|_| CoreError::Unknown)?); + } + Ok(()) +} + +fn parse_extitem_node( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + _input_item: &mut InputItem, +) -> Result<()> { + let tid = parser + .get_required_attrib(name, attributes, "tid")? + .parse::<i32>() + .map_err(|_| CoreError::Unknown)?; + + // tid should exist and should not be None + if let Some(Some(ii)) = parser.track_list.get(tid as usize) { + ii_node.append_item(ii)?; + parser.track_list[tid as usize] = None; + } else { + warn!(parser.logger, "non existing \"tid\" {tid} referenced"); + } + + Ok(()) +} + +fn set_option( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let value = parser.parse_text_node(name)?; + if value.is_empty() { + return Err(CoreError::Unknown); + } + + // TODO: Flag should be zero + input_item.add_option(&value, input_item::Flag::VLC_INPUT_OPTION_UNIQUE)?; + + Ok(()) +} + +fn parse_location( + parser: &mut XmlParser, + name: &str, + attributes: &[OwnedAttribute], + _ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let value = parser.parse_text_node(name)?; + debug!(parser.logger, "Location value: \"{value}\""); + if value.is_empty() { + return Err(CoreError::Unknown); + } + + let uri = parser.process_mrl(&value)?; + debug!(parser.logger, "Track MRI: \"{uri}\""); + input_item.set_uri(Some(&uri)); + + Ok(()) +} + +fn set_item_info( + parser: &mut XmlParser, + name: &str, + _attributes: &[OwnedAttribute], + ii_node: &mut InputItemNode, + input_item: &mut InputItem, +) -> Result<()> { + let value = parser.parse_text_node(name)?; + // let input_item = ii_pair.1.as_mut().ok_or(CoreError::Unknown)?; + match name { + "title" => input_item.set_title(Some(&value)), + "creator" => input_item.set_artist(Some(&value)), + "album" => input_item.set_album(Some(&value)), + "trackNum" => input_item.set_track_number(Some(&value)), + "duration" => input_item.set_duration(Tick::from_miliseconds(Miliseconds::from( + value.parse::<u64>().map_err(|_| CoreError::Unknown)?, + ))), + "annotation" => input_item.set_description(Some(&value)), + "info" => { + if let Ok(mrl) = parser.process_mrl(&value) { + input_item.set_url(Some(&mrl)); + } + } + "image" => { + if let Ok(mrl) = parser.process_mrl(&value) { + input_item.set_artwork_url(Some(&mrl)); + } + } + _ => {} + } + Ok(()) +} diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs new file mode 100644 index 000000000000..579402721771 --- /dev/null +++ b/modules/demux/playlist-rs/src/lib.rs @@ -0,0 +1,186 @@ +//! Playlist Demux module + +use std::{ + io::Read, + path::{self, Path}, + rc::Rc, + result, +}; +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, Module, ThisDemux}, + ModuleArgs, + }, + stream::{impl_stream::BufPeek, DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + url::Url, +}; +use vlcrs_core_macros::module; + +use crate::{ + fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_wpl::FormatWPL, + fmt_xspf::FormatXSPF, +}; + +mod fmt_m3u; +mod fmt_pls; +mod fmt_qtl; +mod fmt_wms; +mod fmt_wpl; +mod fmt_xspf; +mod util_buf; +mod util_input_item; +mod util_xml; + +struct InputContext<'a> { + source: &'a mut Stream, + logger: &'a mut Logger, + path: Option<String>, + input_item: Option<&'a mut InputItem>, +} + +pub struct PlaylistDemuxer<'a> { + input: InputContext<'a>, + parser: Box<dyn PlaylistFormat>, +} + +trait PlaylistFormat { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool>; + + fn parse<'a>( + &'a mut self, + input: &'a mut InputContext<'a>, + input_item_node: &mut InputItemNode, + ) -> Result<()>; + + fn get_extension(&self) -> &'static [&'static str] { + &[] + } +} + +struct Playlist; + +impl Module for Playlist { + fn open<'a>( + mut this_demux: ThisDemux<'a>, + source: &'a mut Stream, + es_out: &'a mut EsOut, + logger: &'a mut Logger, + input_item: Option<&'a mut InputItem>, + args: &mut ModuleArgs, + ) -> Result<DemuxModule<'a>> { + debug!(logger, "Entering playlist-rs open"); + let mut formats: Vec<Box<dyn PlaylistFormat>> = vec![ + Box::new(FormatM3U {}), + Box::new(FormatPLS {}), + Box::new(FormatWPL {}), + Box::new(FormatQTL {}), + Box::new(FormatXSPF {}), + ]; + + let mut high_prior = 0; + let source_path = match this_demux.url() { + Some(result) => Some(result?), + None => None, + }; + + if let Some(path) = source_path { + for format_idx in 0..formats.len() { + if formats[format_idx] + .get_extension() + .iter() + .any(|ext| path[path.len() - ext.len()..].eq_ignore_ascii_case(ext)) + { + formats.swap(high_prior, format_idx); + high_prior += 1; + } + } + } + + let mut input_ctx: InputContext<'a> = InputContext { + source, + logger, + path: source_path.map(str::to_string), + input_item, + }; + + for mut format in formats.into_iter() { + if let Ok(true) = format.can_open(&mut input_ctx) { + return Ok(DemuxModule::ReadDir(Box::new(PlaylistDemuxer { + input: input_ctx, + parser: format, + }))); + } else { + debug!(input_ctx.logger, "Failed to open format") + } + } + + debug!(input_ctx.logger, "No format opened"); + Err(CoreError::Unknown) + } +} + +impl DemuxControl for PlaylistDemuxer<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for PlaylistDemuxer<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading playlist"); + self.parser.parse(&mut self.input, input_item_node)?; + Ok(()) + } +} + +impl InputContext<'_> { + pub fn has_extension(&self, extension: &str) -> bool { + match self.path.as_deref() { + Some(val) => val[val.len() - extension.len()..].eq_ignore_ascii_case(extension), + None => false, + } + } + + pub fn has_mime_type(&self, mime_type: &str) -> bool { + // TODO: Check mimetype + return false; + } + + pub fn process_mrl(&self, media_path: &str) -> Result<String> { + // TODO: URL Fixup + // if let Ok(url) = Url::new(media_path) { + // return Ok(url.uri().to_owned()); + // } + let path = Path::new(media_path); + if path.is_absolute() { + return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); + } + + let mut base_path = + Path::new(self.path.as_deref().ok_or(CoreError::Unknown)?).to_path_buf(); + base_path.pop(); + return Ok(base_path + .join(path) + .to_str() + .ok_or(CoreError::Unknown)? + .to_owned()); + } +} + +module! { + type: Playlist, + capability: "demux" @ 0, + category: SUBCAT_INPUT_DEMUX, + description: "Playlist Rust demuxer", + shortname: "Playlist-rs", +} diff --git a/modules/demux/playlist-rs/src/m3u.rs b/modules/demux/playlist-rs/src/m3u.rs new file mode 100644 index 000000000000..cd5cbf4d5b93 --- /dev/null +++ b/modules/demux/playlist-rs/src/m3u.rs @@ -0,0 +1,144 @@ +//! Playlist_M3U Demux module + +use std::{io::{BufRead}}; + +use vlcrs_core::{ + debug, + error::{Result, CoreError}, + es_out::{EsOut, EsOutBaked}, + messages::Logger, + module::{ + demux::{DemuxModule, Module, ThisDemux}, + ModuleArgs, + }, + stream::{Stream, ReadDirDemux, DemuxControl}, tick::Tick, url::Url, +}; +use vlcrs_core_macros::module; + +use crate::has_extension; + +pub struct Playlist_M3U; + +pub struct Demuxer_M3U<'a> { + source: &'a mut Stream, + es_out: EsOutBaked<'a>, + logger: &'a mut Logger, + url: Url, +} + +/// Function to check if the first non-comment line contains url +fn containsURL(buf: &[u8]) -> Result<bool>{ + for line in buf.lines() { + let l: String = line.map_err(|_| CoreError::Unknown)?; + if l.is_empty() || l.starts_with('#') { + continue + } + match l.find("://") { + Some(3) => return Ok( + l[..3].eq_ignore_ascii_case("mms") + || l[..3].eq_ignore_ascii_case("ftp") + ), + Some(4) => return Ok( + l[..4].eq_ignore_ascii_case("http") + || l[..4].eq_ignore_ascii_case("rtsp") + || l[..4].eq_ignore_ascii_case("ftps") + ), + Some(5) => return Ok( + l[..5].eq_ignore_ascii_case("https") + || l[..5].eq_ignore_ascii_case("ftpes") + ), + _ => return Ok(false) + } + } + return Ok(false); +} + +impl Module for Playlist_M3U { + fn open<'a>( + mut this_demux: ThisDemux<'a>, + source: &'a mut Stream, + es_out: &'a mut EsOut, + logger: &'a mut Logger, + _args: &mut ModuleArgs, + ) -> Result<DemuxModule<'a>> { + // let mut offset = 0; + let mime_type = match source.mime_type()? { + Some(val) => val.to_lowercase(), + None => return Err(CoreError::Unknown) + }; + let peek = source.peek(1024)?; + let mut buf = peek.buf(); + if buf.len() < 8 { + return Err(CoreError::Unknown); + } + + /* UTF-8 Byte Order Mark */ + if &buf[..3] == &[0xEF, 0xBB, 0xBF] { + if buf.len() < 12 { + return Err(CoreError::Unknown); + } + // TODO: pf_dup = CheckUnicode + // offset = 3; + buf = &buf[3..]; + } + if has_extension(&mut this_demux, ".m3u8")? || buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) { + // TODO: pf_dup = CheckUnicode + } + + + + if !buf.starts_with("#EXTM3U".as_bytes()) + && mime_type != "application/mpegurl" + && mime_type != "application/x-mpegurl" + && mime_type != "audio/mpegurl" + && mime_type != "vnd.apple.mpegURL" + && mime_type != "audio/x-mpegurl" + && !has_extension(&mut this_demux, ".m3u8")? + && !has_extension(&mut this_demux, ".m3u")? + && !has_extension(&mut this_demux, ".vlc")? + && !buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) + && !containsURL(buf)? { + return Err(CoreError::Unknown); + } + + /* + Ignoring this since buf is already modified to buf[..3] + if (offset && vlc_stream_Read(p_stream->s, NULL, offset) != offset) + return VLC_EGENERIC; + */ + + debug!(logger, "Found a valid M3U playlist"); + return Ok(DemuxModule::ReadDir(Box::new(Demuxer_M3U { + source, + logger, + es_out: es_out.into(), + url: Url::new(this_demux.url().ok_or(CoreError::Unknown)??)? + }))) + } +} + + +impl DemuxControl for Demuxer_M3U<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl ReadDirDemux for Demuxer_M3U<'_> { + fn read_dir(&mut self, input_item_node: &mut vlcrs_core::input_item::InputItemNode) -> Result<()> { + debug!(self.logger, "Read dir called"); + Ok(()) + } +} + +module! { + type: Playlist_M3U, + capability: "demux" @ 10, + category: SUBCAT_INPUT_DEMUX, + description: "Playlist Rust demuxer: M3U", + shortname: "Playlist-rs-m3u", +} diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs new file mode 100644 index 000000000000..602f6c5f62dc --- /dev/null +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -0,0 +1,62 @@ +use std::{str, collections::HashSet}; +use vlcrs_core::error::{CoreError, Result}; + +use crate::InputContext; + +pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<Option<&'a str>> { + if buf_i < buf.len() { + Ok(Some(str::from_utf8(&buf[buf_i..]).map_err(|_| CoreError::Unknown)?)) + } else { + Ok(None) + } +} + +pub fn skip_chars(buf: &[u8], buf_i: &mut usize, skip: &[u8]) -> usize { + while *buf_i < buf.len() && skip.contains(&buf[*buf_i]) { + *buf_i += 1; + } + return *buf_i; +} + +pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { + if seq.is_empty() { + return Some(buf_i); // Immediate match + } + let (mut left, mut right) = (buf_i, buf_i + seq.len()); + while right <= buf.len() { + if &buf[right..left] == seq { + return Some(left); + } + left += 1; + right += 1; + } + None +} + +pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { + if buf_i + prefix.len() > prefix.len() { + return false; + } + return buf[buf_i..buf_i + prefix.len()].eq_ignore_ascii_case(prefix.as_bytes()); +} + +pub fn read_float(buf: &[u8], buf_i: &mut usize) -> Result<f32> { + while *buf_i < buf.len() && buf[*buf_i].is_ascii_whitespace() { + *buf_i += 1; + } + + if *buf_i >= buf.len() { + return Err(CoreError::Unknown); + } + + let start_i = *buf_i; + + let float_chars: HashSet<char> = "+-0123456789.eEpPxXINFTYAinftya".chars().collect(); + while *buf_i < buf.len() && float_chars.contains(&buf[*buf_i].into()) { + *buf_i += 1; + } + + return str::from_utf8(&buf[start_i..*buf_i])? + .parse::<f32>() + .map_err(|_| CoreError::Unknown); +} diff --git a/modules/demux/playlist-rs/src/util_input_item.rs b/modules/demux/playlist-rs/src/util_input_item.rs new file mode 100644 index 000000000000..dfef8dcc5228 --- /dev/null +++ b/modules/demux/playlist-rs/src/util_input_item.rs @@ -0,0 +1,37 @@ +use vlcrs_core::{ + error::{CoreError, Result}, + stream::Stream, tick::Tick, input_item::{InputItemNode, InputItem}, +}; + +pub(crate) type InputItemRefPair<'a> = (&'a mut InputItemNode, &'a mut Option<&'a mut InputItem>); + +pub const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; +pub struct InputItemMeta<'a> { + pub duration: Tick, + pub artist: Option<&'a str>, + pub name: Option<&'a str>, + pub group: Option<&'a str>, + pub group_title: Option<&'a str>, + pub album_art: Option<&'a str>, + pub language: Option<&'a str>, + pub tvgid: Option<&'a str>, + pub options: Vec<&'a str>, + pub children: Vec<InputItemMeta<'a>> +} + +impl<'a> Default for InputItemMeta<'a> { + fn default() -> InputItemMeta<'a> { + InputItemMeta { + duration: Tick::MAX, + artist: None, + name: None, + group: None, + group_title: None, + album_art: None, + language: None, + tvgid: None, + options: Vec::new(), + children: Vec::new() + } + } +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs new file mode 100644 index 000000000000..c58d79d621f2 --- /dev/null +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -0,0 +1,230 @@ +use std::path::Path; + +use vlcrs_core::{error::Result, messages::Logger, stream::Stream, input_item::{InputItem, InputItemNode}, warn, url::Url}; + +use xml::{ + attribute::OwnedAttribute, + reader::{Events, XmlEvent}, EventReader, +}; + +use vlcrs_core::{debug, error, error::CoreError}; + +pub struct XmlParser<'a> { + pub logger: &'a mut Logger, + pub parser_iter: Events<&'a mut Stream>, + pub track_id: Option<i32>, + pub track_list: Vec<Option<&'a mut InputItem>>, + pub path: Option<String> + // root_input_item_node: &'a mut InputItemNode, + // pub root_input_item: InputItemRefPair<'a>, + // input_items: Vec<InputItemPair>, + // input_item_refs: Vec<InputItemRefPair<'a>> +} + +impl XmlParser<'_> { + pub fn new<'a>( + logger: &'a mut Logger, + source: &'a mut Stream, + path: Option<String> + ) -> Result<XmlParser<'a>> { + let parser = EventReader::new(source as &mut Stream); + let parser_iter: Events<&mut Stream> = parser.into_iter(); + Ok(XmlParser { + logger, + parser_iter, + track_id: None, + track_list: Vec::new(), + path + }) + } + + pub fn process_mrl(&self, media_path: &str) -> Result<String> { + // TODO: URL Fixup + if let Ok(url) = Url::new(media_path) { + return Ok(url.uri().to_owned()); + } + let path = Path::new(media_path); + if path.is_absolute() { + return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); + } + + let mut base_path = Path::new(self.path.as_deref().ok_or(CoreError::Unknown)?).to_path_buf(); + base_path.pop(); + return Ok(base_path + .join(path) + .to_str() + .ok_or(CoreError::Unknown)? + .to_owned()); + } + + pub fn get_required_attrib<'a>(&mut self, name: &str, attributes: &'a [OwnedAttribute], attrib_name: &str) -> Result<&'a str> { + match attributes.iter().find(|attrib| attrib.name.local_name == attrib_name) { + Some(appl) => Ok(appl.value.as_str()), + None => { + warn!(self.logger, "<{name}> requires \"{attrib_name}\" attribute"); + return Err(CoreError::Unknown); + }, + } + } + + pub fn skip_element( + &mut self, + exp_name: &str + ) -> Result<()>{ + let mut depth = 1; + for e in self.parser_iter.by_ref() { + let elem = e.map_err(|_| CoreError::Unknown)?; + match elem { + XmlEvent::StartElement { .. } => { depth += 1; }, + XmlEvent::EndElement { .. } => { depth -= 1; }, + _ => {} + } + if depth == 0 { + if let XmlEvent::EndElement {name, .. } = elem { + if name.local_name == exp_name { + return Ok(()); + } + } + break; + } + } + + error!(self.logger, "Failed to close <{exp_name}>"); + return Err(CoreError::Unknown); + } + + pub fn parse_text_node( + &mut self, + exp_name: &str + ) -> Result<String>{ + let mut depth = 1; + let mut inner_text = String::new(); + for e in self.parser_iter.by_ref() { + let elem = e.map_err(|_| CoreError::Unknown)?; + match elem { + XmlEvent::StartElement { .. } => { depth += 1; }, + XmlEvent::EndElement { name } => { + depth -= 1; + if depth == 0 { + if name.local_name == exp_name { + return Ok(inner_text); + } + break; + } + }, + XmlEvent::Characters(text) => { + inner_text = text; + }, + _ => {} + } + } + + error!(self.logger, "Failed to close <{exp_name}>"); + return Err(CoreError::Unknown); + } + + + pub fn find_next_element( + &mut self, + exp_name: &str, + max_depth: u32, + ) -> Result<Vec<OwnedAttribute>> { + let mut depth: u32 = 0; + for e in self.parser_iter.by_ref() { + if depth == max_depth { + error!(self.logger, "MAX_DEPTH ({max_depth}) exceeded. Expected <{exp_name}> not found."); + return Err(CoreError::Unknown); + } + let elem = e.map_err(|_| CoreError::Unknown)?; + match elem { + XmlEvent::StartElement { + name, attributes, .. + } => { + if name.local_name.eq_ignore_ascii_case(exp_name) { + debug!(self.logger, "Found tag {exp_name}"); + return Ok(attributes); + } else { + depth += 1; + } + } + XmlEvent::StartDocument { .. } + | XmlEvent::ProcessingInstruction { .. } + | XmlEvent::Whitespace(_) => {} + _ => { + error!(self.logger, "Expected <{exp_name}> opening tag. Found: {elem:?}"); + return Err(CoreError::Unknown); + } + } + } + + error!(self.logger, "Not Found <{}>", exp_name); + return Err(CoreError::Unknown); + } + +} + + +/* +parse_node! (.., { + "node1" => { + "node2" => { + "node3" => handle_fn, + "node4" => handle_fn + } + }, + "node5" => handler_fn2 +}) + */ +macro_rules! parse_node { + ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { + $($name:expr => $handler:tt),* + }) => { + use xml::{ + reader::{Events, XmlEvent}, + }; + use crate::util_xml::handle; + + while let Some(e) = $parser.parser_iter.next() { + let elem = e.map_err(|_| CoreError::Unknown)?; + match elem { + XmlEvent::StartElement { name, attributes, .. } => { + let name_str = name.local_name.as_str(); + let attrib = attributes.as_slice(); + debug!($parser.logger, "Found tag: <{name_str}>"); + $( + if name_str.eq_ignore_ascii_case($name) { + handle!($parser, name_str, attrib, $ii_node, $ii, $handler); + // $handler($parser, name_str, attributes.as_slice(), $ii_node, $ii)?; + } else + )* + { + warn!($parser.logger, "Skipping unknown tag: <{}> in <{}>", name.local_name, $root_node); + $parser.skip_element(name.local_name.as_str())?; + } + } + XmlEvent::EndElement { name, .. } => { + if name.local_name.eq_ignore_ascii_case($root_node) { + break; + } + } + _ => {} + } + } + }; +} + +macro_rules! handle { + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, $handler:ident) => { + // Call handler function + $handler($parser, $node, $attrib, $ii_node, $ii)?; + }; + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, $handler:tt) => { + // Inline parser + parse_node!($parser, $node, $ii_node, $ii, $handler); + }; +} + +pub(crate) use parse_node; +pub(crate) use handle; + +use crate::InputContext; diff --git a/modules/demux/playlist-rs/todo.md b/modules/demux/playlist-rs/todo.md new file mode 100644 index 000000000000..da0f72425933 --- /dev/null +++ b/modules/demux/playlist-rs/todo.md @@ -0,0 +1,4 @@ +[x] Improve try_open and open arguments (through struct) +[ ] Check mimetype +[ ] Use playlist meta struct for population and then add them to the playlist +[ ] Test WMS Parser -- GitLab From 5f8bd602e31f5799a383c41b0f6ecbb33048c750 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Wed, 12 Jul 2023 21:16:24 -0600 Subject: [PATCH 02/24] Rust Core changes for playlist-rs --- modules/vlcrs-core/src/input_item.rs | 97 ++++++++++++++++++++++---- modules/vlcrs-core/src/module/demux.rs | 29 ++++++-- modules/vlcrs-core/src/stream.rs | 4 +- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/modules/vlcrs-core/src/input_item.rs b/modules/vlcrs-core/src/input_item.rs index 427c5d79a39f..d39845c54ee2 100644 --- a/modules/vlcrs-core/src/input_item.rs +++ b/modules/vlcrs-core/src/input_item.rs @@ -1,7 +1,11 @@ +use std::alloc::alloc; +use std::alloc::Layout; use std::ffi::CStr; use std::ffi::CString; +use std::ptr; use std::ptr::NonNull; +use vlcrs_core_sys::input_item_AddInfo; use vlcrs_core_sys::input_item_AddOption; use vlcrs_core_sys::input_item_CopyOptions; use vlcrs_core_sys::input_item_GetDuration; @@ -9,8 +13,12 @@ use vlcrs_core_sys::input_item_IsArtFetched; use vlcrs_core_sys::input_item_IsPreparsed; use vlcrs_core_sys::input_item_NewExt; use vlcrs_core_sys::input_item_SetDuration; +use vlcrs_core_sys::input_item_SetName; +use vlcrs_core_sys::input_item_GetName; use vlcrs_core_sys::input_item_node_AppendItem; +use vlcrs_core_sys::input_item_node_AppendNode; use vlcrs_core_sys::input_item_node_Delete; +use vlcrs_core_sys::input_item_node_RemoveNode; use vlcrs_core_sys::{input_item_Copy, input_item_Release, input_item_node_t, input_item_t}; use crate::error::cvt; @@ -21,7 +29,7 @@ macro_rules! input_meta { ($name:ident, $set_name:ident, $get_fn:ident, $set_fn:ident) => { #[doc = concat!("Get the *", stringify!($name), "* of this input item")] // #[doc(alias = stringify!($get_fn))] - pub fn $name(&mut self) -> Option<&str> { + pub fn $name(&self) -> Option<&str> { use vlcrs_core_sys::$get_fn; // SAFETY: TODO @@ -36,7 +44,7 @@ macro_rules! input_meta { #[doc = concat!("Set the *", stringify!($name), "* of this input item")] // #[doc(alias = stringify!($set_fn))] - pub fn $set_name(&mut self, $name: Option<&str>) { + pub fn $set_name(&self, $name: Option<&str>) { use vlcrs_core_sys::$set_fn; let c_string = if let Some(name) = $name { @@ -45,7 +53,6 @@ macro_rules! input_meta { } else { std::ptr::null_mut() }; - // SAFETY: TODO unsafe { $set_fn(self.0.as_ptr(), c_string) } } @@ -58,10 +65,13 @@ pub use vlcrs_core_sys::input_item_type_e as Type; /// An input item #[doc(alias = "input_item_t")] +#[repr(transparent)] pub struct InputItem(NonNull<input_item_t>); impl InputItem { input_meta!(title, set_title, input_item_GetTitle, input_item_SetTitle); + input_meta!(uri, set_uri, input_item_GetURI, input_item_SetURI); + input_meta!(name, set_name, input_item_GetName, input_item_SetName); input_meta!( artist, set_artist, @@ -199,9 +209,17 @@ impl InputItem { input_item_SetDiscTotal ); + pub fn from_ptr(ptr: NonNull<input_item_t>) -> Box<InputItem> { + unsafe { + let ret_ptr = alloc(Layout::new::<InputItem>()) as *mut InputItem; + (*ret_ptr).0 = ptr; + Box::from_raw(ret_ptr) + } + } + /// Create a new input item #[doc(alias = "input_item_NewExt")] - pub fn new(uri: String, name: String) -> Result<InputItem> { + pub fn new(uri: &str, name: &str) -> Result<InputItem> { let c_uri = CString::new(uri).unwrap(); let c_name = CString::new(name).unwrap(); @@ -218,11 +236,27 @@ impl InputItem { .map(InputItem) } + /// Create a new input item + #[doc(alias = "input_item_NewExt")] + pub fn new_empty() -> Result<InputItem> { + // SAFETY: TODO + cvp(unsafe { + input_item_NewExt( + std::ptr::null(), + std::ptr::null(), + Tick::INVALID.0, + Type::ITEM_TYPE_UNKNOWN, + NetType::ITEM_NET_UNKNOWN, + ) + }) + .map(InputItem) + } + /// Create a new input item with extend capabilities #[doc(alias = "input_item_NewExt")] pub fn new_ext( - uri: String, - name: String, + uri: &str, + name: &str, duration: Tick, type_: Type, net_type: NetType, @@ -252,6 +286,26 @@ impl InputItem { cvt(unsafe { input_item_AddOption(self.0.as_ptr(), c_opt.as_ptr(), flag as _) }) } + /// Add a string info + #[doc(alias = "input_item_AddInfo")] + pub fn add_info_str(&mut self, category: &str, name: &str, value: &str) -> Result<()> { + let c_category = CString::new(category).unwrap(); + let c_name = CString::new(name).unwrap(); + let c_format = CString::new("%s").unwrap(); + let c_value = CString::new(value).unwrap(); + + // SAFETY: TODO + cvt(unsafe { + input_item_AddInfo( + self.0.as_ptr(), + c_category.as_ptr(), + c_name.as_ptr(), + c_format.as_ptr(), + c_value.as_ptr(), + ) + }) + } + /// Get the duration of this input item #[doc(alias = "input_item_GetDuration")] pub fn duration(&mut self) -> Tick { @@ -312,16 +366,35 @@ impl InputItemNode { InputItemNode(ptr) } + /// Append an item to the current node pub fn append_item(&mut self, item: &InputItem) -> Result<InputItemNode> { - // SAFETY: drop(release on item) + // SAFETY: `item` is a shared object so there is no worries about use-after-free and + // the current node is alive and well formed. cvp(unsafe { input_item_node_AppendItem(self.0.as_ptr(), item.0.as_ptr()) }) .map(InputItemNode) } -} -impl Drop for InputItemNode { - fn drop(&mut self) { - // SAFETY: TODO - unsafe { input_item_node_Delete(self.0.as_ptr()) } + /// Append a node to the current node + pub fn append_node(&mut self, node: &InputItemNode) { + // SAFETY: `node` is a shared object so there is no worries about use-after-free and + // the current node is alive and well formed. + unsafe { input_item_node_AppendNode(self.0.as_ptr(), node.0.as_ptr()) }; + } + + /// Remove the node from the current node and delete them + pub fn remove_and_delete_node(&mut self, node: InputItemNode) { + // SAFETY: We own the node, so we can safely remove it from the current node + unsafe { input_item_node_RemoveNode(self.0.as_ptr(), node.0.as_ptr()) } + // SAFETY: We have just remove the node, we can now safely delete it + unsafe { input_item_node_Delete(node.0.as_ptr()) } + } + + /// Get first item from current node + pub fn get_item<'a>(&'a self) -> Option<InputItem> { + if let Some(input_item) = NonNull::new(unsafe { self.0.as_ref().p_item }) { + return Some(InputItem(input_item)); + } else { + return None; + } } } diff --git a/modules/vlcrs-core/src/module/demux.rs b/modules/vlcrs-core/src/module/demux.rs index 4db8806ff971..a50be69826d2 100644 --- a/modules/vlcrs-core/src/module/demux.rs +++ b/modules/vlcrs-core/src/module/demux.rs @@ -4,13 +4,14 @@ use std::ffi::CStr; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ptr::{self, NonNull}; +use std::rc::Rc; -use vlcrs_core_sys::{demux_t, input_item_node_t, vlc_logger}; +use vlcrs_core_sys::{demux_t, input_item_node_t, vlc_logger, input_item_t}; use vlcrs_core_sys::{es_out_t, vlc_object_t, vlc_tick_t}; use crate::error::{CoreError, Errno, Result}; use crate::es_out::EsOut; -use crate::input_item::InputItemNode; +use crate::input_item::{InputItemNode, InputItem}; use crate::messages::Logger; use crate::object::VlcObjectRef; use crate::stream::{Demux, DemuxControl, ReadDirDemux, Stream}; @@ -21,7 +22,7 @@ use super::args::ModuleArgs; /// A representation of a stream module pub enum DemuxModule<'a> { Demux(Box<dyn Demux + 'a>), - ReadDir(Box<dyn ReadDirDemux + 'a>), + ReadDir(Box<dyn ReadDirDemux<'a> + 'a>), } /// A opaque representation of the current demux module @@ -36,6 +37,7 @@ pub trait Module { source: &'a mut Stream, es_out: &'a mut EsOut, logger: &'a mut Logger, + _input_item: Option<&'a mut InputItem>, _args: &mut ModuleArgs, ) -> Result<DemuxModule<'a>>; } @@ -104,6 +106,14 @@ impl ThisDemux<'_> { pub fn object(&mut self) -> VlcObjectRef<'_> { VlcObjectRef::from_raw(self.0 as *mut _) } + + pub fn input_item(&self) -> Option<Box<InputItem>> { + if let Some(input_item_ptr) = NonNull::new(unsafe { *(self.0) }.p_input_item) { + Some(InputItem::from_ptr(input_item_ptr)) + } else { + None + } + } } /// Generic module open callback for demux_t like steam @@ -140,6 +150,14 @@ pub unsafe extern "C" fn module_open<T: Module>(object: *mut vlc_object_t) -> i3 let mut_logger: &'static mut Logger = unsafe { logger_ptr_ptr.cast::<Logger>().as_mut::<'static>().unwrap() }; + // SAFETY: TODO + let mut_input_item = if unsafe { (*ptr_demux).p_input_item }.is_null() { + None + } else { + let input_item_ptr_ptr: *mut *mut input_item_t = unsafe { &mut (*ptr_demux).p_input_item }; + Some(unsafe { input_item_ptr_ptr.cast::<InputItem>().as_mut().unwrap() }) + }; + let mut module_args = ModuleArgs(NonNull::new(object).unwrap()); match T::open( @@ -147,6 +165,7 @@ pub unsafe extern "C" fn module_open<T: Module>(object: *mut vlc_object_t) -> i3 mut_stream, mut_es_out, mut_logger, + mut_input_item, &mut module_args, ) { Ok(demux_module) => unsafe { register(ptr_demux, demux_module) }, @@ -249,7 +268,7 @@ unsafe fn register(this: *mut demux_t, demux_module: DemuxModule<'_>) -> i32 { __bindgen_anon_1: vlcrs_core_sys::vlc_stream_operations__bindgen_ty_1 { demux: vlcrs_core_sys::vlc_stream_operations__bindgen_ty_1__bindgen_ty_2 { demux: None, - readdir: Some(pf_readdir::<dyn ReadDirDemux>), + readdir: Some(pf_readdir::<dyn for<'a> ReadDirDemux<'a>>), can_record: None, can_control_rate: None, has_unsupported_meta: None, @@ -310,7 +329,7 @@ unsafe extern "C" fn pf_demux<D: Demux + ?Sized>(stream: *mut demux_t) -> libc:: } } -unsafe extern "C" fn pf_readdir<D: ReadDirDemux + ?Sized>( +unsafe extern "C" fn pf_readdir<D: for<'a> ReadDirDemux<'a> + ?Sized>( demux: *mut demux_t, input_item_node: *mut input_item_node_t, ) -> i32 { diff --git a/modules/vlcrs-core/src/stream.rs b/modules/vlcrs-core/src/stream.rs index d29ad65c268b..ca614088a0b2 100644 --- a/modules/vlcrs-core/src/stream.rs +++ b/modules/vlcrs-core/src/stream.rs @@ -55,10 +55,10 @@ pub trait Demux: DemuxControl { } /// This trait allows defining a stream for demuxing some datas. -pub trait ReadDirDemux: DemuxControl { +pub trait ReadDirDemux<'a>: DemuxControl { /// Pull some data from the stream and output in the InputItemNode. #[doc(alias = "pf_readdir")] - fn read_dir(&mut self, input_item_node: &mut InputItemNode) -> Result<()>; + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()>; } pub trait DemuxControl { -- GitLab From d2fb9dd277f59cba57f3020c077489ddf5be8257 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Wed, 12 Jul 2023 21:17:39 -0600 Subject: [PATCH 03/24] Added playlist-rs to rust workspace and build --- modules/Cargo.toml | 2 +- modules/demux/Makefile.am | 12 ++++++++++++ modules/demux/meson.build | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 5746329785d9..9e26488c0a70 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["stream_filter/inflate-rs", "demux/flv-rs"] +members = ["stream_filter/inflate-rs", "demux/flv-rs", "demux/playlist-rs"] [workspace.dependencies] vlcrs-core = { path = "vlcrs-core/" } diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am index 8f71cb7ce7d3..a9257ee713d6 100644 --- a/modules/demux/Makefile.am +++ b/modules/demux/Makefile.am @@ -32,6 +32,18 @@ if HAVE_RUST demux_LTLIBRARIES += libflv_rs_plugin.la endif +libplaylist_rs.la: + @$(LIBTOOL_CARGO) $(srcdir)/demux/playlist-rs/ $@ + +libplaylist_rs_plugin_la_SOURCES = \ + demux/playlist-rs/Cargo.toml \ + demux/playlist-rs/src/lib.rs +libplaylist_rs_plugin_la_LIBADD = libplaylist_rs.la + +if HAVE_RUST +demux_LTLIBRARIES += libplaylist_rs_plugin.la +endif + xiph_test_SOURCES = demux/xiph_test.c demux/xiph.h check_PROGRAMS += xiph_test TESTS += xiph_test diff --git a/modules/demux/meson.build b/modules/demux/meson.build index a433e5ec0e59..727c3ba095a8 100644 --- a/modules/demux/meson.build +++ b/modules/demux/meson.build @@ -592,3 +592,11 @@ vlc_rust_modules += { 'sources' : files('flv-rs/src/lib.rs'), 'cargo_toml' : files('flv-rs/Cargo.toml'), } + + +# Playlist Rust demuxer +vlc_rust_modules += { + 'name' : 'playlist_rs', + 'sources' : files('playlist-rs/src/lib.rs'), + 'cargo_toml' : files('playlist-rs/Cargo.toml'), +} -- GitLab From 9b0a18e90b9c545bb6c973a93dd49f564004a9ee Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Wed, 12 Jul 2023 21:18:21 -0600 Subject: [PATCH 04/24] flv-rs changes for extra input_item arg in vlc module open --- modules/demux/flv-rs/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/demux/flv-rs/src/lib.rs b/modules/demux/flv-rs/src/lib.rs index 5d2b31528194..1a719c859b81 100644 --- a/modules/demux/flv-rs/src/lib.rs +++ b/modules/demux/flv-rs/src/lib.rs @@ -14,7 +14,7 @@ use vlcrs_core::{ }, stream::{Demux, DemuxControl, Frame, Stream}, tick::Tick, - warn, + warn, input_item::InputItem, }; use vlcrs_core_macros::module; @@ -45,6 +45,7 @@ impl Module for FLV { source: &'a mut Stream, es_out: &'a mut EsOut, logger: &'a mut Logger, + _input_item: Option<&'a mut InputItem>, _args: &mut ModuleArgs, ) -> Result<DemuxModule<'a>> { let buf = source.peek(3 + 1 + 1 + 4)?; -- GitLab From 99933078b30e6e263b78023161ca81603c25118c Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Thu, 13 Jul 2023 02:34:02 -0600 Subject: [PATCH 05/24] Added sgimb parser, Fixed util_buf --- modules/demux/playlist-rs/src/fmt_sgimb.rs | 273 ++++++++++++++------- modules/demux/playlist-rs/src/lib.rs | 6 +- modules/demux/playlist-rs/src/m3u.rs | 144 ----------- modules/demux/playlist-rs/src/util_buf.rs | 64 ++++- 4 files changed, 247 insertions(+), 240 deletions(-) delete mode 100644 modules/demux/playlist-rs/src/m3u.rs diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index b31b4ec653ce..97029fd8d7c3 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -1,136 +1,227 @@ use std::{io::Read, path::Path, rc::Rc}; use vlcrs_core::{ - debug, + debug, error, error::{CoreError, Result}, es_out::{EsOut, EsOutBaked}, - input_item::InputItem, + input_item::{InputItem, InputItemNode}, messages::Logger, module::{ demux::{DemuxModule, ThisDemux}, ModuleArgs, }, stream::{DemuxControl, ReadDirDemux, Stream}, - tick::Tick, + tick::{Microseconds, Tick}, url::Url, }; -use crate::{has_extension, PlaylistFormat, process_mrl}; +use crate::{ + util_buf::{ + find_in_buf, find_in_buf_ignore_case, match_ignore_case, read_int, read_to_end, skip_chars, + }, + InputContext, PlaylistFormat, +}; -pub struct FormatSGIMB; +pub(crate) struct FormatSGIMB { + pub uri: Option<String>, + pub server: Option<String>, + pub location: Option<String>, + pub name: Option<String>, + pub user: Option<String>, + pub password: Option<String>, + pub mcast_ip: Option<String>, + pub rtsp_kasenna: bool, + pub mcast_port: i32, + pub packet_size: i32, + pub duration: Tick, + pub port: i32, + pub sid: i32, + pub concert: bool, +} -impl PlaylistFormat for FormatSGIMB { - fn try_open( - &mut self, - path: &Option<&str>, - buf: &[u8], - es_out: &EsOut, - logger: &mut Logger, - _args: &ModuleArgs, - ) -> Result<bool> { - debug!(logger, "Testing if file is SGIMB"); - - // find_in_buf() - - let header = std::str::from_utf8(&buf[..10])?; - - if !header.eq_ignore_ascii_case("[playlist]") && !has_extension(path, ".SGIMB") { - debug!(logger, "Not a valid SGIMB playlist file"); - return Err(CoreError::Unknown); +impl Default for FormatSGIMB { + fn default() -> Self { + Self { + uri: None, + server: None, + location: None, + name: None, + user: None, + password: None, + mcast_ip: None, + rtsp_kasenna: false, + mcast_port: 0, + packet_size: 0, + duration: Tick::ZERO, + port: 0, + sid: 0, + concert: false, } - - return Ok(true); } +} + +impl PlaylistFormat for FormatSGIMB { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is SGIMB"); + let peek = input.source.peek(1024)?; + let buf = peek.buf(); + + Ok(find_in_buf(buf, 0, "sgiNameServerHost=".as_bytes()).is_some()) + } fn parse( &mut self, input: &mut InputContext, input_item_node: &mut InputItemNode, ) -> Result<()> { - debug!(self.logger, "Read dir called"); + debug!(input.logger, "Read dir called"); // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - self.source + input + .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; - let mut i_item: i32 = -1; - let mut psz_mrl: Rc<String> = String::new().into(); - let mut psz_name: Rc<String> = String::new().into(); - for line in buf.lines() { - debug!(self.logger, "Line: {line}"); + debug!(input.logger, "Line: {line}"); + self.parse_line(line.as_bytes(), input, input_item_node)?; + } - if line == "[playlist]" { - continue; + if let Some(mcast_ip) = &self.mcast_ip { + self.uri = Some(format!("udp://@{}:{}", mcast_ip, self.mcast_port)); + } + + if self.uri.is_none() { + if let (Some(server), Some(location)) = (&self.server, &self.location) { + self.uri = Some(format!( + "rtsp://{}:{}{}", + server, + if self.port > 0 { self.port } else { 554 }, + location + )); } + } - let (psz_key, psz_value) = match line.split_once('=') { - Some(parts) => parts, - None => continue, - }; - - match psz_key { - "version" => debug!(self.logger, "SGIMB file version: {psz_value}"), - "numberofentries" => debug!(self.logger, "SGIMB should have {psz_value} entries"), - _ => { - let (_, i_new_item_str) = - psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); - - if i_new_item_str.is_empty() { - debug!(self.logger, "couldn't find item number in key"); - continue; - } + if self.concert { + if let Some(uri) = &self.uri { + self.uri = Some(format!( + "{}%3FMeDiAbAsEshowingId={}%26MeDiAbAsEconcert%3FMeDiAbAsE", + uri, self.sid + )); + } else { + error!(input.logger, "No URI was found."); + return Err(CoreError::Unknown); + } + } - let i_new_item = i_new_item_str - .parse::<i32>() - .map_err(|_| CoreError::Unknown)?; - - if i_item == -1 { - i_item = i_new_item; - } else if i_item != i_new_item { - debug!(self.logger, "Adding item {psz_mrl}, {psz_name}"); - let input = InputItem::new(&psz_mrl, &psz_name)?; - input_item_node.append_item(&input)?; - i_item = i_new_item; - } + let mut child = InputItem::new_empty()?; + child.set_uri(self.uri.as_deref()); + child.set_name(match &self.name { + Some(_) => self.name.as_deref(), + None => self.uri.as_deref(), + }); + child.set_duration(self.duration); + + if self.packet_size != 0 && self.mcast_ip.is_some() { + let option = format!("mtu={}", self.packet_size); + child.add_option( + &option, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + )?; + } - let key_name = &psz_key[..psz_key.len() - i_new_item_str.len()]; - - debug!(self.logger, "Key: [{key_name}] Item : {i_item}"); - match key_name.to_ascii_lowercase().as_str() { - "file" => { - let abs_path = process_mrl(self.path.as_deref(), psz_value)?; - psz_mrl = Rc::new(abs_path); - } - "title" => { - psz_name = Rc::new(psz_value.to_owned()); - } - "length" => {} - _ => { - debug!(self.logger, "unknown key found in SGIMB file: {key_name}"); - } - }; - } - }; + if self.mcast_ip.is_none() { + child.add_option( + "rtsp-caching=5000", + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + )?; } - // Add last item - if i_item != -1 { - let input = InputItem::new(&psz_mrl, &psz_name)?; - input_item_node.append_item(&input)?; - - debug!( - self.logger, - "Added last item {}, {}", - psz_mrl.to_string(), - psz_name.to_string() - ); + if self.mcast_ip.is_none() && self.rtsp_kasenna { + child.add_option( + "rtsp-kasenna", + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + )?; } + input_item_node.append_item(&child); + Ok(()) } +} + +impl FormatSGIMB { + fn parse_line( + &mut self, + mut line: &[u8], + input: &mut InputContext, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + let mut buf_i = 0; + skip_chars(line, &mut buf_i, " \t\n\r".as_bytes()); + let start_pos = buf_i; + + match_ignore_case!(line, buf_i, { + "rtsp://" => { + self.uri = read_to_end(line, start_pos)?.map(str::to_string); + }, + "Stream=" => { + if let Some(end) = find_in_buf(line, buf_i, "\"".as_bytes()) { + if starts_with_ignore_case(line, buf_i, "xdma://") { + self.uri = Some( + String::from("rtsp") + + std::str::from_utf8(&line[buf_i..end]).map_err(|_|CoreError::Unknown)? + ); + } else { + self.uri = Some( + String::from_utf8(line[buf_i..end].to_vec()).map_err(|_|CoreError::Unknown)? + ); + } + } + }, + "sgiNameServerHost=" => { + self.server = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiMovieName=" => { + self.location = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiUserAccount=" => { + self.user = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiUserPassword=" => { + self.password = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiShowingName=" => { + self.name = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiFormatName=" => { + self.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4".as_bytes()).is_some(); + }, + "sgiMulticastAddress=" => { + self.mcast_ip = read_to_end(line, buf_i)?.map(str::to_string); + }, + "sgiMulticastPort=" => { + self.mcast_port = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiPacketSize=" => { + self.packet_size = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiDuration=" => { + self.duration = Tick::from_microseconds(Microseconds::from(read_int(line, &mut buf_i).unwrap_or(0))); + }, + "sgiRtspPort=" => { + self.port = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiSid=" => { + self.sid = read_int(line, &mut buf_i).unwrap_or(0); + }, + "DeliveryService=cds" => { + self.concert = true; + }, + }); + Ok(()) + } } diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 579402721771..e73340471fb1 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -23,13 +23,14 @@ use vlcrs_core::{ use vlcrs_core_macros::module; use crate::{ - fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_wpl::FormatWPL, - fmt_xspf::FormatXSPF, + fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_sgimb::FormatSGIMB, + fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, }; mod fmt_m3u; mod fmt_pls; mod fmt_qtl; +mod fmt_sgimb; mod fmt_wms; mod fmt_wpl; mod fmt_xspf; @@ -81,6 +82,7 @@ impl Module for Playlist { Box::new(FormatWPL {}), Box::new(FormatQTL {}), Box::new(FormatXSPF {}), + Box::new(FormatSGIMB {..Default::default()}), ]; let mut high_prior = 0; diff --git a/modules/demux/playlist-rs/src/m3u.rs b/modules/demux/playlist-rs/src/m3u.rs deleted file mode 100644 index cd5cbf4d5b93..000000000000 --- a/modules/demux/playlist-rs/src/m3u.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Playlist_M3U Demux module - -use std::{io::{BufRead}}; - -use vlcrs_core::{ - debug, - error::{Result, CoreError}, - es_out::{EsOut, EsOutBaked}, - messages::Logger, - module::{ - demux::{DemuxModule, Module, ThisDemux}, - ModuleArgs, - }, - stream::{Stream, ReadDirDemux, DemuxControl}, tick::Tick, url::Url, -}; -use vlcrs_core_macros::module; - -use crate::has_extension; - -pub struct Playlist_M3U; - -pub struct Demuxer_M3U<'a> { - source: &'a mut Stream, - es_out: EsOutBaked<'a>, - logger: &'a mut Logger, - url: Url, -} - -/// Function to check if the first non-comment line contains url -fn containsURL(buf: &[u8]) -> Result<bool>{ - for line in buf.lines() { - let l: String = line.map_err(|_| CoreError::Unknown)?; - if l.is_empty() || l.starts_with('#') { - continue - } - match l.find("://") { - Some(3) => return Ok( - l[..3].eq_ignore_ascii_case("mms") - || l[..3].eq_ignore_ascii_case("ftp") - ), - Some(4) => return Ok( - l[..4].eq_ignore_ascii_case("http") - || l[..4].eq_ignore_ascii_case("rtsp") - || l[..4].eq_ignore_ascii_case("ftps") - ), - Some(5) => return Ok( - l[..5].eq_ignore_ascii_case("https") - || l[..5].eq_ignore_ascii_case("ftpes") - ), - _ => return Ok(false) - } - } - return Ok(false); -} - -impl Module for Playlist_M3U { - fn open<'a>( - mut this_demux: ThisDemux<'a>, - source: &'a mut Stream, - es_out: &'a mut EsOut, - logger: &'a mut Logger, - _args: &mut ModuleArgs, - ) -> Result<DemuxModule<'a>> { - // let mut offset = 0; - let mime_type = match source.mime_type()? { - Some(val) => val.to_lowercase(), - None => return Err(CoreError::Unknown) - }; - let peek = source.peek(1024)?; - let mut buf = peek.buf(); - if buf.len() < 8 { - return Err(CoreError::Unknown); - } - - /* UTF-8 Byte Order Mark */ - if &buf[..3] == &[0xEF, 0xBB, 0xBF] { - if buf.len() < 12 { - return Err(CoreError::Unknown); - } - // TODO: pf_dup = CheckUnicode - // offset = 3; - buf = &buf[3..]; - } - if has_extension(&mut this_demux, ".m3u8")? || buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) { - // TODO: pf_dup = CheckUnicode - } - - - - if !buf.starts_with("#EXTM3U".as_bytes()) - && mime_type != "application/mpegurl" - && mime_type != "application/x-mpegurl" - && mime_type != "audio/mpegurl" - && mime_type != "vnd.apple.mpegURL" - && mime_type != "audio/x-mpegurl" - && !has_extension(&mut this_demux, ".m3u8")? - && !has_extension(&mut this_demux, ".m3u")? - && !has_extension(&mut this_demux, ".vlc")? - && !buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) - && !containsURL(buf)? { - return Err(CoreError::Unknown); - } - - /* - Ignoring this since buf is already modified to buf[..3] - if (offset && vlc_stream_Read(p_stream->s, NULL, offset) != offset) - return VLC_EGENERIC; - */ - - debug!(logger, "Found a valid M3U playlist"); - return Ok(DemuxModule::ReadDir(Box::new(Demuxer_M3U { - source, - logger, - es_out: es_out.into(), - url: Url::new(this_demux.url().ok_or(CoreError::Unknown)??)? - }))) - } -} - - -impl DemuxControl for Demuxer_M3U<'_> { - fn source_stream(&mut self) -> Option<&mut Stream> { - Some(self.source) - } - - fn time(&mut self) -> vlcrs_core::tick::Tick { - Tick::ZERO - } -} - -impl ReadDirDemux for Demuxer_M3U<'_> { - fn read_dir(&mut self, input_item_node: &mut vlcrs_core::input_item::InputItemNode) -> Result<()> { - debug!(self.logger, "Read dir called"); - Ok(()) - } -} - -module! { - type: Playlist_M3U, - capability: "demux" @ 10, - category: SUBCAT_INPUT_DEMUX, - description: "Playlist Rust demuxer: M3U", - shortname: "Playlist-rs-m3u", -} diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 602f6c5f62dc..c1924773a44f 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -24,7 +24,22 @@ pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { } let (mut left, mut right) = (buf_i, buf_i + seq.len()); while right <= buf.len() { - if &buf[right..left] == seq { + if &buf[left..right] == seq { + return Some(left); + } + left += 1; + right += 1; + } + None +} + +pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { + if seq.is_empty() { + return Some(buf_i); // Immediate match + } + let (mut left, mut right) = (buf_i, buf_i + seq.len()); + while right <= buf.len() { + if buf[left..right].eq_ignore_ascii_case(seq) { return Some(left); } left += 1; @@ -34,10 +49,11 @@ pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { } pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { - if buf_i + prefix.len() > prefix.len() { + let end = buf_i + prefix.len(); + if end > buf.len() { return false; } - return buf[buf_i..buf_i + prefix.len()].eq_ignore_ascii_case(prefix.as_bytes()); + return buf[buf_i..end].eq_ignore_ascii_case(prefix.as_bytes()); } pub fn read_float(buf: &[u8], buf_i: &mut usize) -> Result<f32> { @@ -60,3 +76,45 @@ pub fn read_float(buf: &[u8], buf_i: &mut usize) -> Result<f32> { .parse::<f32>() .map_err(|_| CoreError::Unknown); } + +// Note: Does not support octal/hexadecimal +pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { + while *buf_i < buf.len() && buf[*buf_i].is_ascii_whitespace() { + *buf_i += 1; + } + + if *buf_i >= buf.len() { + return Err(CoreError::Unknown); + } + + let start_i = *buf_i; + + let float_chars: HashSet<char> = "+-0123456789".chars().collect(); + while *buf_i < buf.len() && float_chars.contains(&buf[*buf_i].into()) { + *buf_i += 1; + } + + return str::from_utf8(&buf[start_i..*buf_i])? + .parse::<i32>() + .map_err(|_| CoreError::Unknown); +} + +macro_rules! match_ignore_case { + ($buf:expr, $buf_i:expr, { + $($start_str:expr => $handler:tt),* + $(,)? + }) => { + use crate::util_buf::starts_with_ignore_case; + + $( + if starts_with_ignore_case($buf, $buf_i, $start_str) { + $buf_i = $buf_i + $start_str.len(); + $handler; + } else + )* + { + } + }; +} + +pub(crate) use match_ignore_case; -- GitLab From 4a30659c2954c8b81ece2e37c852f01320ea0dc4 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 14 Jul 2023 21:25:38 -0600 Subject: [PATCH 06/24] podcast changes --- modules/demux/playlist-rs/src/fmt_podcast.rs | 120 ++++++++++++++ modules/demux/playlist-rs/src/fmt_wpl.rs | 21 +-- modules/demux/playlist-rs/src/lib.rs | 1 + modules/demux/playlist-rs/src/util_xml.rs | 155 +++++++++++++------ 4 files changed, 234 insertions(+), 63 deletions(-) create mode 100644 modules/demux/playlist-rs/src/fmt_podcast.rs diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs new file mode 100644 index 000000000000..8027e1f26b13 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -0,0 +1,120 @@ +use std::{any::Any, io::Read, path::Path, rc::Rc}; + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Miliseconds, Tick}, + url::Url, + warn, +}; +use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; + +use crate::{ + util_buf::starts_with_ignore_case, + util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, + util_xml::{find_next_element_from_buf, parse_node, XmlParser}, + InputContext, PlaylistFormat, +}; + +pub struct FormatPodcast { + is_item: bool, + is_image: bool, + item_mrl: Option<String>, + item_size: Option<String>, + item_type: Option<String>, + item_name: Option<String>, + item_date: Option<String>, + item_author: Option<String>, + item_category: Option<String>, + item_duration: Option<String>, + item_keywords: Option<String>, + item_subtitle: Option<String>, + item_summary: Option<String>, + art_url: Option<String>, +} + +impl PlaylistFormat for FormatPodcast { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is Podcast"); + + //TODO: p_this->force ??? + if input.has_mime_type("text/xml") || input.has_mime_type("application/xml") { + let peek = input.source.peek(1024)?; + let buf = peek.buf(); + + return find_next_element_from_buf(buf, input.logger, "rss"); + } else if !input.has_mime_type("application/rss+xml") { + return Ok(false); + } + + return Ok(true); + } + + fn parse<'a>( + &'a mut self, + mut input: &'a mut InputContext<'a>, + mut input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading Podcast metafile"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let mut input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + xml_parser.find_next_element("rss", 1)?; + + parse_node!(&mut xml_parser, "rss", &mut input_item_node, &mut input_item, { + "item" => { + [attributes, value] { + parse_node!(xml_parser, "item", &mut input_item_node, &mut input_item, { + "title" => { self.item_name = Some(value.clone()); }, + + }); + } + }, + // |xml_parser: &mut XmlParser, _, attributes: &[OwnedAttribute], ii_node, ii| { + // parse_node!(xml_parser, "item", ii_node, ii, { + // "title" => {}, + // "itunes:author" => {}, + // "author" => {}, + // }); + // } + // ), + "image" => { self.is_image = true }, + "enclosure" => { + [attributes] { + for attr in attributes { + let name = attr.name.local_name.as_str(); + let value = attr.value.to_owned(); + match name { + "url" => self.item_mrl = Some(value), + "length" => self.item_size = Some(value), + "type" => self.item_type = Some(value), + _ => { + debug!(xml_parser.logger, "unhandled attribute {name} in <enclosure>"); + } + } + } + } + } + , + }); + + Ok(()) + } + +} diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs index b8320ebf87fe..0e55cc2b8c33 100644 --- a/modules/demux/playlist-rs/src/fmt_wpl.rs +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -23,7 +23,7 @@ use xml::{ use crate::{ util_input_item::InputItemMeta, - util_xml::{parse_node, XmlParser}, + util_xml::{find_next_element_from_buf, parse_node, XmlParser}, InputContext, PlaylistFormat, }; @@ -42,24 +42,7 @@ impl PlaylistFormat for FormatWPL { let peek = input.source.peek(2048)?; let buf = peek.buf(); // Should be an XML file & Root element should be smil - let parser = EventReader::new(buf); - - for e in parser { - if let XmlEvent::StartElement { name, .. } = e.map_err(|_| CoreError::Unknown)? { - if name.local_name.eq_ignore_ascii_case("smil") { - debug!(input.logger, "Found smil tag in WPL playlist"); - return Ok(true); - } else { - error!( - input.logger, - "Invalid WPL playlist. Root element should have been <smil>" - ); - return Ok(false); - } - }; - } - - return Ok(false); + return find_next_element_from_buf(buf, input.logger, "smil"); } fn parse( diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index e73340471fb1..327dbdd759fb 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -34,6 +34,7 @@ mod fmt_sgimb; mod fmt_wms; mod fmt_wpl; mod fmt_xspf; +mod fmt_podcast; mod util_buf; mod util_input_item; mod util_xml; diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index c58d79d621f2..f5db8adc767c 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -1,10 +1,18 @@ use std::path::Path; -use vlcrs_core::{error::Result, messages::Logger, stream::Stream, input_item::{InputItem, InputItemNode}, warn, url::Url}; +use vlcrs_core::{ + error::Result, + input_item::{InputItem, InputItemNode}, + messages::Logger, + stream::Stream, + url::Url, + warn, +}; use xml::{ attribute::OwnedAttribute, - reader::{Events, XmlEvent}, EventReader, + reader::{Events, XmlEvent}, + EventReader, }; use vlcrs_core::{debug, error, error::CoreError}; @@ -14,18 +22,17 @@ pub struct XmlParser<'a> { pub parser_iter: Events<&'a mut Stream>, pub track_id: Option<i32>, pub track_list: Vec<Option<&'a mut InputItem>>, - pub path: Option<String> - // root_input_item_node: &'a mut InputItemNode, - // pub root_input_item: InputItemRefPair<'a>, - // input_items: Vec<InputItemPair>, - // input_item_refs: Vec<InputItemRefPair<'a>> + pub path: Option<String>, // root_input_item_node: &'a mut InputItemNode, + // pub root_input_item: InputItemRefPair<'a>, + // input_items: Vec<InputItemPair>, + // input_item_refs: Vec<InputItemRefPair<'a>> } impl XmlParser<'_> { pub fn new<'a>( logger: &'a mut Logger, source: &'a mut Stream, - path: Option<String> + path: Option<String>, ) -> Result<XmlParser<'a>> { let parser = EventReader::new(source as &mut Stream); let parser_iter: Events<&mut Stream> = parser.into_iter(); @@ -34,7 +41,7 @@ impl XmlParser<'_> { parser_iter, track_id: None, track_list: Vec::new(), - path + path, }) } @@ -48,7 +55,8 @@ impl XmlParser<'_> { return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); } - let mut base_path = Path::new(self.path.as_deref().ok_or(CoreError::Unknown)?).to_path_buf(); + let mut base_path = + Path::new(self.path.as_deref().ok_or(CoreError::Unknown)?).to_path_buf(); base_path.pop(); return Ok(base_path .join(path) @@ -57,30 +65,39 @@ impl XmlParser<'_> { .to_owned()); } - pub fn get_required_attrib<'a>(&mut self, name: &str, attributes: &'a [OwnedAttribute], attrib_name: &str) -> Result<&'a str> { - match attributes.iter().find(|attrib| attrib.name.local_name == attrib_name) { + pub fn get_required_attrib<'a>( + &mut self, + name: &str, + attributes: &'a [OwnedAttribute], + attrib_name: &str, + ) -> Result<&'a str> { + match attributes + .iter() + .find(|attrib| attrib.name.local_name == attrib_name) + { Some(appl) => Ok(appl.value.as_str()), None => { warn!(self.logger, "<{name}> requires \"{attrib_name}\" attribute"); return Err(CoreError::Unknown); - }, + } } } - pub fn skip_element( - &mut self, - exp_name: &str - ) -> Result<()>{ + pub fn skip_element(&mut self, exp_name: &str) -> Result<()> { let mut depth = 1; for e in self.parser_iter.by_ref() { let elem = e.map_err(|_| CoreError::Unknown)?; match elem { - XmlEvent::StartElement { .. } => { depth += 1; }, - XmlEvent::EndElement { .. } => { depth -= 1; }, + XmlEvent::StartElement { .. } => { + depth += 1; + } + XmlEvent::EndElement { .. } => { + depth -= 1; + } _ => {} } if depth == 0 { - if let XmlEvent::EndElement {name, .. } = elem { + if let XmlEvent::EndElement { name, .. } = elem { if name.local_name == exp_name { return Ok(()); } @@ -88,22 +105,21 @@ impl XmlParser<'_> { break; } } - + error!(self.logger, "Failed to close <{exp_name}>"); return Err(CoreError::Unknown); } - pub fn parse_text_node( - &mut self, - exp_name: &str - ) -> Result<String>{ + pub fn parse_text_node(&mut self, exp_name: &str) -> Result<String> { let mut depth = 1; let mut inner_text = String::new(); for e in self.parser_iter.by_ref() { let elem = e.map_err(|_| CoreError::Unknown)?; match elem { - XmlEvent::StartElement { .. } => { depth += 1; }, - XmlEvent::EndElement { name } => { + XmlEvent::StartElement { .. } => { + depth += 1; + } + XmlEvent::EndElement { name } => { depth -= 1; if depth == 0 { if name.local_name == exp_name { @@ -111,19 +127,18 @@ impl XmlParser<'_> { } break; } - }, + } XmlEvent::Characters(text) => { inner_text = text; - }, + } _ => {} } } - + error!(self.logger, "Failed to close <{exp_name}>"); return Err(CoreError::Unknown); } - pub fn find_next_element( &mut self, exp_name: &str, @@ -132,7 +147,10 @@ impl XmlParser<'_> { let mut depth: u32 = 0; for e in self.parser_iter.by_ref() { if depth == max_depth { - error!(self.logger, "MAX_DEPTH ({max_depth}) exceeded. Expected <{exp_name}> not found."); + error!( + self.logger, + "MAX_DEPTH ({max_depth}) exceeded. Expected <{exp_name}> not found." + ); return Err(CoreError::Unknown); } let elem = e.map_err(|_| CoreError::Unknown)?; @@ -151,7 +169,10 @@ impl XmlParser<'_> { | XmlEvent::ProcessingInstruction { .. } | XmlEvent::Whitespace(_) => {} _ => { - error!(self.logger, "Expected <{exp_name}> opening tag. Found: {elem:?}"); + error!( + self.logger, + "Expected <{exp_name}> opening tag. Found: {elem:?}" + ); return Err(CoreError::Unknown); } } @@ -160,9 +181,29 @@ impl XmlParser<'_> { error!(self.logger, "Not Found <{}>", exp_name); return Err(CoreError::Unknown); } - } +pub fn find_next_element_from_buf( + buf: &[u8], + logger: &mut Logger, + elem_name: &str, +) -> Result<bool> { + let parser = EventReader::new(buf); + + for e in parser { + if let XmlEvent::StartElement { name, .. } = e.map_err(|_| CoreError::Unknown)? { + if name.local_name.eq_ignore_ascii_case(elem_name) { + debug!(logger, "Found {elem_name} tag in XML"); + return Ok(true); + } else { + error!(logger, "Element <{elem_name}> not found"); + return Ok(false); + } + }; + } + + Ok(false) +} /* parse_node! (.., { @@ -177,10 +218,10 @@ parse_node! (.., { */ macro_rules! parse_node { ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { - $($name:expr => $handler:tt),* + $($name:expr => $handler:tt),* $(,)? }) => { use xml::{ - reader::{Events, XmlEvent}, + reader::{XmlEvent}, }; use crate::util_xml::handle; @@ -192,10 +233,11 @@ macro_rules! parse_node { let attrib = attributes.as_slice(); debug!($parser.logger, "Found tag: <{name_str}>"); $( - if name_str.eq_ignore_ascii_case($name) { + if name_str.eq_ignore_ascii_case($name) + // if false $( || name_str.eq_ignore_ascii_case($name))* + { handle!($parser, name_str, attrib, $ii_node, $ii, $handler); - // $handler($parser, name_str, attributes.as_slice(), $ii_node, $ii)?; - } else + } else )* { warn!($parser.logger, "Skipping unknown tag: <{}> in <{}>", name.local_name, $root_node); @@ -214,17 +256,42 @@ macro_rules! parse_node { } macro_rules! handle { + // Direct function call ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, $handler:ident) => { - // Call handler function $handler($parser, $node, $attrib, $ii_node, $ii)?; }; - ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, $handler:tt) => { - // Inline parser - parse_node!($parser, $node, $ii_node, $ii, $handler); + // parse sub nodes + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, { + $($name:expr => $handler:tt),* $(,)? + }) => { + parse_node!($parser, $node, $ii_node, $ii, { + $($name => $handler,)* + }); + }; + // Custom code with brackets + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + { [$attrib_var:ident] $code:tt } + ) => { + let $attrib_var = $attrib; + $code; + }; + // Custom code with brackets + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + { [$attrib_var:ident, $value_var:ident] $code:tt } + ) => { + let $attrib_var = $attrib; + let $value_var = $parser.parse_text_node($node)?; + $code; + }; + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + $handler:tt) => { + // Evaluate inline + $handler; + // $handler($parser, $node, $attrib, $ii_node, $ii); }; } -pub(crate) use parse_node; pub(crate) use handle; +pub(crate) use parse_node; use crate::InputContext; -- GitLab From 3c10bf2bb80af93beed1511b9022146f33a9abcc Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Tue, 18 Jul 2023 00:31:48 -0600 Subject: [PATCH 07/24] added podcast parser --- modules/demux/playlist-rs/src/fmt_podcast.rs | 142 ++++++++++++------- modules/demux/playlist-rs/src/lib.rs | 7 +- 2 files changed, 98 insertions(+), 51 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index 8027e1f26b13..daa6e5e41e44 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -1,17 +1,19 @@ +/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + use std::{any::Any, io::Read, path::Path, rc::Rc}; use vlcrs_core::{ debug, error::{CoreError, Result}, es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode}, + input_item::{self, InputItem, InputItemNode, MetaType::*}, messages::Logger, module::{ demux::{DemuxModule, ThisDemux}, ModuleArgs, }, stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Miliseconds, Tick}, + tick::{Miliseconds, Tick, Seconds}, url::Url, warn, }; @@ -24,22 +26,7 @@ use crate::{ InputContext, PlaylistFormat, }; -pub struct FormatPodcast { - is_item: bool, - is_image: bool, - item_mrl: Option<String>, - item_size: Option<String>, - item_type: Option<String>, - item_name: Option<String>, - item_date: Option<String>, - item_author: Option<String>, - item_category: Option<String>, - item_duration: Option<String>, - item_keywords: Option<String>, - item_subtitle: Option<String>, - item_summary: Option<String>, - art_url: Option<String>, -} +pub struct FormatPodcast; impl PlaylistFormat for FormatPodcast { fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { @@ -72,49 +59,106 @@ impl PlaylistFormat for FormatPodcast { path, } = input; - let mut input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut art_url: Option<String> = None; + let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; xml_parser.find_next_element("rss", 1)?; - parse_node!(&mut xml_parser, "rss", &mut input_item_node, &mut input_item, { + parse_node!(&mut xml_parser, "rss", &mut input_item_node, &mut root_input_item, { + "title" => { [_a, value] { root_input_item.set_name(Some(&value)); } }, + "link" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Link", &value)?; } }, + "copyright" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Copyright", &value)?; } }, + "itunes:category" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Category", &value)?; } }, + "itunes:keywords" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Keywords", &value)?; } }, + "itunes:subtitle" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Subtitle", &value)?; } }, + "itunes:summary" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Summary", &value)?; } }, + "description" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Summary", &value)?; } }, "item" => { - [attributes, value] { + [_attribute, value] { + let mut input_item = InputItem::new_empty()?; + + macro_rules! add_info { + ($info:expr, $field:expr) => { + input_item.add_info_str("Podcast Info", $info, $field)?; + }; + ($info:expr, $field:expr, $meta:expr) => { + input_item.add_info_str("Podcast Info", $info, $field)?; + input_item.set_meta($meta, $field); + }; + } + parse_node!(xml_parser, "item", &mut input_item_node, &mut input_item, { - "title" => { self.item_name = Some(value.clone()); }, - - }); - } - }, - // |xml_parser: &mut XmlParser, _, attributes: &[OwnedAttribute], ii_node, ii| { - // parse_node!(xml_parser, "item", ii_node, ii, { - // "title" => {}, - // "itunes:author" => {}, - // "author" => {}, - // }); - // } - // ), - "image" => { self.is_image = true }, - "enclosure" => { - [attributes] { - for attr in attributes { - let name = attr.name.local_name.as_str(); - let value = attr.value.to_owned(); - match name { - "url" => self.item_mrl = Some(value), - "length" => self.item_size = Some(value), - "type" => self.item_type = Some(value), - _ => { - debug!(xml_parser.logger, "unhandled attribute {name} in <enclosure>"); + "title" => { input_item.set_name(Some(&value)); }, + "itunes:author" => { add_info!("Podcast Author", &value, vlc_meta_Artist); }, + "author" => { add_info!("Podcast Author", &value, vlc_meta_Artist); }, + "itunes:summary" => { add_info!("Podcast Summary", &value, vlc_meta_Description); }, + "description" => { add_info!("Podcast Summary", &value, vlc_meta_Description); }, + "pubDate" => { add_info!("Podcast Publication Date", &value, vlc_meta_Date); }, + "itunes:category" => { add_info!("Podcast Subcategory", &value); }, + "itunes:duration" => { + add_info!("Podcast Duration", &value); + root_input_item.set_duration(match parse_time(&value) { + Ok(ticks) => ticks, + Err(_) => Tick::INVALID, + }); + }, + "itunes:keywords" => { add_info!("Podcast Keywords", &value); }, + "itunes:subtitle" => { add_info!("Podcast Subtitle", &value); }, + "enclosure" => { + [attributes] { + for attr in attributes { + let name = attr.name.local_name.as_str(); + let value = attr.value.to_owned(); + match name { + "url" => input_item.set_uri(Some(&value)), + "length" => add_info!("Podcast Size", format!("{value} bytes").as_str()), + "type" => add_info!("Podcast Type", &value), + _ => { + debug!(xml_parser.logger, "unhandled attribute {name} in <enclosure>"); + } + } + } } - } + }, + }); + + if let Some(art_url) = &art_url { + input_item.set_artwork_url(Some(art_url)); + } + + if input_item.uri().is_some() { + input_item_node.append_item(&input_item)?; } } - } - , + }, + "image" => { + "url" => { [_attrib, value] { + art_url = Some(value); + }} + }, }); Ok(()) } } + +fn parse_time(val: &str) -> Result<Tick> { + + let [h, m, s]: [u32; 3] = match val.split(":").collect::<Vec<&str>>().as_slice() { + [h, m, s] => [ + h.parse().map_err(|_|CoreError::Unknown)?, + m.parse().map_err(|_|CoreError::Unknown)?, + s.parse().map_err(|_|CoreError::Unknown)? + ], + [h, m] => [ + h.parse().map_err(|_|CoreError::Unknown)?, + m.parse().map_err(|_|CoreError::Unknown)?, + 0 + ], + _ => return Err(CoreError::Unknown) + }; + + Ok(Tick::from_seconds(Seconds::from(( h*60 + m )*60 + s ))) +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 327dbdd759fb..58fa85763649 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -24,7 +24,7 @@ use vlcrs_core_macros::module; use crate::{ fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_sgimb::FormatSGIMB, - fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, + fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, }; mod fmt_m3u; @@ -35,6 +35,7 @@ mod fmt_wms; mod fmt_wpl; mod fmt_xspf; mod fmt_podcast; +mod fmt_itml; mod util_buf; mod util_input_item; mod util_xml; @@ -83,7 +84,9 @@ impl Module for Playlist { Box::new(FormatWPL {}), Box::new(FormatQTL {}), Box::new(FormatXSPF {}), - Box::new(FormatSGIMB {..Default::default()}), + Box::new(FormatSGIMB {..Default::default()}, ), + Box::new(FormatPodcast {}), + Box::new(FormatITML {}), ]; let mut high_prior = 0; -- GitLab From 5f03011334083364311fd3e14a902c62824a0942 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Tue, 18 Jul 2023 00:32:09 -0600 Subject: [PATCH 08/24] added itml parser --- modules/demux/playlist-rs/src/fmt_itml.rs | 157 ++++++++++++++++++++++ modules/demux/playlist-rs/src/util_xml.rs | 6 +- modules/vlcrs-core/src/input_item.rs | 19 ++- 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 modules/demux/playlist-rs/src/fmt_itml.rs diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs new file mode 100644 index 000000000000..b30753a4341d --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -0,0 +1,157 @@ +/// Sample: https://metacpan.org/release/DINOMITE/Mac-iTunes-Library-0.9/view/lib/Mac/iTunes/Library/XML.pm#NOTES_ON_iTUNES_XML_FORMAT +/* +<dict> + <key>Playlists</key> + <array> + <dict> + <key>Name</key><string>Library</string> + <key>Master</key><true/> + <key>Playlist ID</key><integer>201</integer> + <key>Playlist Persistent ID</key><string>707F6A2CE6E601F5</string> + <key>Visible</key><false/> + <key>All Items</key><true/> + <key>Playlist Items</key> + <array> + <dict> + <key>Track ID</key><integer>173</integer> + </dict> + <dict> + <key>Track ID</key><integer>175</integer> + </dict> + <dict> + <key>Track ID</key><integer>177</integer> + </dict> + </array> + </dict> + </array> +</dict> + */ + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode, MetaType::*}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Miliseconds, Tick, Seconds, Microseconds}, + url::Url, + warn, +}; +use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; + +use crate::{ + util_buf::starts_with_ignore_case, + util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, + util_xml::{find_next_element_from_buf, parse_node, XmlParser}, + InputContext, PlaylistFormat, +}; + +pub struct FormatITML; + +impl PlaylistFormat for FormatITML { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is ITML"); + + //TODO: p_this->force ??? + if !input.has_extension(".xml") { + return Err(CoreError::Unknown); + } + let peek = input.source.peek(128)?; + let buf = peek.buf(); + + if !starts_with_ignore_case(buf, 0, "<!DOCTYPE plist ") { + return Err(CoreError::Unknown); + } + + return Ok(true); + } + + fn parse<'a>( + &'a mut self, + mut input: &'a mut InputContext<'a>, + mut input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading ITML metafile"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + let mut version: Option<String> = None; + + let attribs = xml_parser.find_next_element("plist", 1)?; + for attrib in attribs { + match attrib.name.local_name.as_str() { + "version" => version = Some(attrib.value), + _ => warn!(xml_parser.logger, "invalid <plist> attribute:\"{}\"", attrib.name.local_name) + } + } + + if version.is_none() { + warn!(xml_parser.logger, "<plist> requires \"version\" attribute" ); + } + + parse_node!(&mut xml_parser, "plist", &mut input_item_node, &mut root_input_item, { + "dict" => { + "array" => { + "dict" => { + [_attr] { + let mut input_item = InputItem::new_empty()?; + let mut key_name: Option<String> = None; + parse_node!(&mut xml_parser, "dict", &mut input_item_node, &mut root_input_item, { + "key" => { [_attr, value] {key_name = Some(value);} }, + "integer" => { [_attr, value] { + if let Some(name) = &key_name { set_input_value(&mut input_item, name, &value)?; } + }}, + "string" => { [_attr, value] { + if let Some(name) = &key_name { set_input_value(&mut input_item, name, &value)?; } + }}, + "date" => { [_attr, value] { + if let Some(name) = &key_name { set_input_value(&mut input_item, name, &value)?; } + }}, + }); + + if input_item.uri().is_some() { + input_item_node.append_item(&input_item)?; + } else { + warn!(xml_parser.logger, "ignoring track without Location entry"); + } + } + } + }, + } + }); + + Ok(()) + } + +} + +fn set_input_value(input_item: &mut InputItem, name: &str, value: &str) -> Result<()> { + match name { + "Name" => { input_item.set_title(Some(value)); }, + "Artist" => { input_item.set_artist(Some(value)); }, + "Album" => { input_item.set_album(Some(value)); }, + "Genre" => { input_item.set_genre(Some(value)); }, + "Track Number" => { input_item.set_track_number(Some(value)) }, + "Location" => { input_item.set_uri(Some(value)); }, + "Total Time" => { input_item.set_duration( + Tick::from_microseconds(Microseconds::from( + value.parse::<i64>().map_err(|_|CoreError::Unknown)? + )) + ) }, + _ => {} + } + Ok(()) +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index f5db8adc767c..76bec370f9e4 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -216,6 +216,7 @@ parse_node! (.., { "node5" => handler_fn2 }) */ +// TODO: Make independent of args macro_rules! parse_node { ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { $($name:expr => $handler:tt),* $(,)? @@ -228,15 +229,14 @@ macro_rules! parse_node { while let Some(e) = $parser.parser_iter.next() { let elem = e.map_err(|_| CoreError::Unknown)?; match elem { - XmlEvent::StartElement { name, attributes, .. } => { + XmlEvent::StartElement { name, attributes: _attr, .. } => { let name_str = name.local_name.as_str(); - let attrib = attributes.as_slice(); debug!($parser.logger, "Found tag: <{name_str}>"); $( if name_str.eq_ignore_ascii_case($name) // if false $( || name_str.eq_ignore_ascii_case($name))* { - handle!($parser, name_str, attrib, $ii_node, $ii, $handler); + handle!($parser, name_str, &_attr, $ii_node, $ii, $handler); } else )* { diff --git a/modules/vlcrs-core/src/input_item.rs b/modules/vlcrs-core/src/input_item.rs index d39845c54ee2..bb74772cf9b0 100644 --- a/modules/vlcrs-core/src/input_item.rs +++ b/modules/vlcrs-core/src/input_item.rs @@ -13,6 +13,7 @@ use vlcrs_core_sys::input_item_IsArtFetched; use vlcrs_core_sys::input_item_IsPreparsed; use vlcrs_core_sys::input_item_NewExt; use vlcrs_core_sys::input_item_SetDuration; +use vlcrs_core_sys::input_item_SetMeta; use vlcrs_core_sys::input_item_SetName; use vlcrs_core_sys::input_item_GetName; use vlcrs_core_sys::input_item_node_AppendItem; @@ -62,6 +63,7 @@ macro_rules! input_meta { pub use vlcrs_core_sys::input_item_net_type as NetType; pub use vlcrs_core_sys::input_item_option_e as Flag; pub use vlcrs_core_sys::input_item_type_e as Type; +pub use vlcrs_core_sys::vlc_meta_type_t as MetaType; /// An input item #[doc(alias = "input_item_t")] @@ -306,6 +308,21 @@ impl InputItem { }) } + /// Add meta + #[doc(alias = "input_item_SetMeta")] + pub fn set_meta(&mut self, meta_type: MetaType, value: &str) { + let c_value = CString::new(value).unwrap(); + + // SAFETY: TODO + unsafe { + input_item_SetMeta( + self.0.as_ptr(), + meta_type, + c_value.as_ptr(), + ) + } + } + /// Get the duration of this input item #[doc(alias = "input_item_GetDuration")] pub fn duration(&mut self) -> Tick { @@ -315,7 +332,7 @@ impl InputItem { /// Set the duration of this input item #[doc(alias = "input_item_SetDuration")] - pub fn set_duration(&mut self, dur: Tick) { + pub fn set_duration(&self, dur: Tick) { // SAFETY: The points to a valid and alive input item unsafe { input_item_SetDuration(self.0.as_ptr(), dur.0) } } -- GitLab From dcd1f1091aa97e68dee4b13bde85213d641325dd Mon Sep 17 00:00:00 2001 From: Akram Ansari <mohdakram.ansari@ucalgary.ca> Date: Wed, 19 Jul 2023 23:53:43 -0600 Subject: [PATCH 09/24] added asx, fixed xml parser --- modules/demux/playlist-rs/src/fmt_asx.rs | 175 ++++++++++++++++++++++ modules/demux/playlist-rs/src/fmt_itml.rs | 21 +-- modules/demux/playlist-rs/src/fmt_xspf.rs | 11 +- modules/demux/playlist-rs/src/lib.rs | 5 +- modules/demux/playlist-rs/src/util_xml.rs | 99 ++++++------ modules/vlcrs-core/src/input_item.rs | 4 +- 6 files changed, 247 insertions(+), 68 deletions(-) create mode 100644 modules/demux/playlist-rs/src/fmt_asx.rs diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs new file mode 100644 index 000000000000..4ba761bb6283 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -0,0 +1,175 @@ +/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + input_item::{InputItemNode, InputItem}, + warn, tick::Tick, +}; +use xml::attribute::OwnedAttribute; + +use crate::{ + util_buf::starts_with_ignore_case, + util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, + InputContext, PlaylistFormat, +}; + +pub struct FormatASX; + +impl PlaylistFormat for FormatASX { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is ASX"); + + //TODO: p_this->force ??? + if input.has_extension(".asx") + || input.has_extension(".wax") + || input.has_extension(".wvx") { + return Ok(true); + } else if input.has_mime_type("video/x-ms-asf") || input.has_mime_type("audio/x-ms-wax") { + let peek = input.source.peek(12)?; + let buf = peek.buf(); + if starts_with_ignore_case(buf, 0, "<asx version") { + return Ok(true); + } + } + + return Ok(false); + } + + fn parse<'a>( + &'a mut self, + input: &'a mut InputContext<'a>, + mut input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading ASX metafile"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + // TODO: unicode, asx-xml + + let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + let base = path; + + xml_parser.find_next_element("ASX", 1)?; + + parse_node!(&mut xml_parser, "ASX", &mut input_item_node, &mut root_input_item, { + "Title" => { [_a, value] { root_input_item.set_title(Some(&value)); } }, + "Author" => { [_a, value] { root_input_item.set_artist(Some(&value)); } }, + "Copyright" => { [_a, value] { root_input_item.set_copyright(Some(&value)); } }, + "Moreinfo" => { [attr, value] { + root_input_item.set_url( + Some(get_required_attrib(xml_parser.logger, attr, "href").unwrap_or_else(|_|&value)) + ); + } }, + "Abstract" => { [_a, value] { root_input_item.set_description(Some(&value)); } }, + "Base" => { }, + "Entryref" => { [attr, value] { + let url = get_required_attrib(xml_parser.logger, attr, "href").unwrap_or_else(|_|&value); + let mut input_item = InputItem::new_empty()?; + input_item.set_uri(Some(url)); + input_item.set_name(root_input_item.name()); + input_item_node.append_item(&input_item)?; + }}, + "Entry" => { parse_entry(&mut xml_parser, input_item_node, root_input_item, base); }, + }); + + Ok(()) + } + +} + +fn parse_entry( + parser: &mut XmlParser, + ii_node: &mut InputItemNode, + root_input_item: &mut InputItem, + base: &mut Option<String> +) -> Result<()> { + + let mut title = None; + let mut artist = None; + let mut copyright = None; + let mut description = None; + let mut more_info = None; + let mut duration = None; + let mut start = None; + + let mut num_entry = 0; + + parse_node!(parser, "entry", &mut input_item_node, &mut root_input_item, { + "TITLE" => { [_attr, value] { title = Some(value); } }, + "AUTHOR" => { [_attr, value] { artist = Some(value); } }, + "COPYRIGHT" => { [_attr, value] { copyright = Some(value); } }, + "MOREINFO" => { [attr, value] { + more_info = Some(get_required_attrib(parser.logger, attr, "href") + .map(str::to_string) + .unwrap_or_else(|_|value) + ); + } }, + "ABSTRACT" => { [_attr, value] { description = Some(value); } }, + "DURATION" => { [_attr, value] { duration = Some(parse_time(&value)?); } }, + "STARTTIME" => { [_attr, value] { start = Some(parse_time(&value)?); } }, + "REF" => { [attrib] { + /* Reference Node */ + /* All ref node will be converted into an entry */ + num_entry += 1; + + if title.is_none() { + title = root_input_item.title().map(str::to_string); + } + if artist.is_none() { + artist = root_input_item.artist().map(str::to_string); + } + if copyright.is_none() { + copyright = root_input_item.copyright().map(str::to_string); + } + if description.is_none() { + description = root_input_item.description().map(str::to_string); + } + + let href = get_required_attrib(parser.logger, attrib, "href")?; // Bug in C code + + let name = format!("{}. {}", num_entry, title.as_deref().unwrap_or("")); + let mrl = process_mrl(href, base.as_deref())?; + + let mut input_item = InputItem::new(&mrl, &name)?; + if let Some(start) = start { + if let Err(_) = input_item.add_option( + &format!(":start-time={}", start.to_seconds()), + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED + ) { + warn!(parser.logger, "Failed to add start time option"); + } + } + if let Some(duration) = duration { + input_item.set_duration(duration); + if let Err(_) = input_item.add_option( + &format!(":stop-time={}", (start.unwrap_or(Tick::ZERO) + duration).to_seconds()), + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED + ) { + warn!(parser.logger, "Failed to add stop time option"); + } + } + + input_item.set_title(Some(&name)); + input_item.set_artist(artist.as_deref()); + input_item.set_copyright(copyright.as_deref()); + input_item.set_url(more_info.as_deref()); + input_item.set_description(description.as_deref()); + ii_node.append_item(&input_item)?; + }}, + }); + + Ok(()) +} + +fn parse_time(s: &str) -> Result<Tick> { + // TODO: parse_time not implemented + todo!("parse_time not implemented") +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index b30753a4341d..f1a0b7e53bae 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -30,24 +30,15 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode, MetaType::*}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Miliseconds, Tick, Seconds, Microseconds}, - url::Url, + input_item::{InputItem, InputItemNode}, + tick::{Tick, Microseconds}, warn, }; -use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; + use crate::{ util_buf::starts_with_ignore_case, - util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, - util_xml::{find_next_element_from_buf, parse_node, XmlParser}, + util_xml::{parse_node, XmlParser}, InputContext, PlaylistFormat, }; @@ -73,8 +64,8 @@ impl PlaylistFormat for FormatITML { fn parse<'a>( &'a mut self, - mut input: &'a mut InputContext<'a>, - mut input_item_node: &mut InputItemNode, + input: &'a mut InputContext<'a>, + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading ITML metafile"); diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index 29cb60a160fd..f41c0a7745b5 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -20,7 +20,7 @@ use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; use crate::{ util_buf::starts_with_ignore_case, util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, - util_xml::{parse_node, XmlParser}, + util_xml::{parse_node, XmlParser, get_required_attrib}, InputContext, PlaylistFormat, }; @@ -81,7 +81,7 @@ fn parse_extension_node( ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { - let application = parser.get_required_attrib(name, attributes, "application")?; + let application = get_required_attrib(parser.logger, attributes, "application")?; if application != "http://www.videolan.org/vlc/playlist/0" { debug!(parser.logger, "Skipping \"{application}\" extension tag"); @@ -178,7 +178,7 @@ fn parse_vlcnode_node( ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { - let title = parser.get_required_attrib(name, attributes, "title")?; + let title = get_required_attrib(parser.logger, attributes, "title")?; /* input_item_NewExt( "vlc://nop", psz_title, (0LL), ITEM_TYPE_DIRECTORY, ITEM_NET_UNKNOWN @@ -236,8 +236,7 @@ fn parse_extitem_node( ii_node: &mut InputItemNode, _input_item: &mut InputItem, ) -> Result<()> { - let tid = parser - .get_required_attrib(name, attributes, "tid")? + let tid = get_required_attrib(parser.logger, attributes, "tid")? .parse::<i32>() .map_err(|_| CoreError::Unknown)?; @@ -283,7 +282,7 @@ fn parse_location( return Err(CoreError::Unknown); } - let uri = parser.process_mrl(&value)?; + let uri = process_mrl(&value)?; debug!(parser.logger, "Track MRI: \"{uri}\""); input_item.set_uri(Some(&uri)); diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 58fa85763649..bd69a2919cb8 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -24,7 +24,7 @@ use vlcrs_core_macros::module; use crate::{ fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_sgimb::FormatSGIMB, - fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, + fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, fmt_asx::FormatASX, }; mod fmt_m3u; @@ -36,6 +36,8 @@ mod fmt_wpl; mod fmt_xspf; mod fmt_podcast; mod fmt_itml; +mod fmt_asx; +mod fmt_b4s; mod util_buf; mod util_input_item; mod util_xml; @@ -87,6 +89,7 @@ impl Module for Playlist { Box::new(FormatSGIMB {..Default::default()}, ), Box::new(FormatPodcast {}), Box::new(FormatITML {}), + Box::new(FormatASX {}), ]; let mut high_prior = 0; diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 76bec370f9e4..1233ebbb67b5 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -2,7 +2,7 @@ use std::path::Path; use vlcrs_core::{ error::Result, - input_item::{InputItem, InputItemNode}, + input_item::InputItem, messages::Logger, stream::Stream, url::Url, @@ -22,10 +22,8 @@ pub struct XmlParser<'a> { pub parser_iter: Events<&'a mut Stream>, pub track_id: Option<i32>, pub track_list: Vec<Option<&'a mut InputItem>>, - pub path: Option<String>, // root_input_item_node: &'a mut InputItemNode, - // pub root_input_item: InputItemRefPair<'a>, - // input_items: Vec<InputItemPair>, - // input_item_refs: Vec<InputItemRefPair<'a>> + pub path: Option<String>, + pub depth: u32, } impl XmlParser<'_> { @@ -42,45 +40,12 @@ impl XmlParser<'_> { track_id: None, track_list: Vec::new(), path, + depth: 0, }) } pub fn process_mrl(&self, media_path: &str) -> Result<String> { - // TODO: URL Fixup - if let Ok(url) = Url::new(media_path) { - return Ok(url.uri().to_owned()); - } - let path = Path::new(media_path); - if path.is_absolute() { - return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); - } - - let mut base_path = - Path::new(self.path.as_deref().ok_or(CoreError::Unknown)?).to_path_buf(); - base_path.pop(); - return Ok(base_path - .join(path) - .to_str() - .ok_or(CoreError::Unknown)? - .to_owned()); - } - - pub fn get_required_attrib<'a>( - &mut self, - name: &str, - attributes: &'a [OwnedAttribute], - attrib_name: &str, - ) -> Result<&'a str> { - match attributes - .iter() - .find(|attrib| attrib.name.local_name == attrib_name) - { - Some(appl) => Ok(appl.value.as_str()), - None => { - warn!(self.logger, "<{name}> requires \"{attrib_name}\" attribute"); - return Err(CoreError::Unknown); - } - } + process_mrl(media_path, self.path.as_deref()) } pub fn skip_element(&mut self, exp_name: &str) -> Result<()> { @@ -158,11 +123,11 @@ impl XmlParser<'_> { XmlEvent::StartElement { name, attributes, .. } => { + depth += 1; if name.local_name.eq_ignore_ascii_case(exp_name) { debug!(self.logger, "Found tag {exp_name}"); + self.depth = depth; return Ok(attributes); - } else { - depth += 1; } } XmlEvent::StartDocument { .. } @@ -183,6 +148,43 @@ impl XmlParser<'_> { } } +pub fn process_mrl(media_path: &str, base_path: Option<&str>) -> Result<String> { + // TODO: URL Fixup + if let Ok(url) = Url::new(media_path) { + return Ok(url.uri().to_owned()); + } + let path = Path::new(media_path); + if path.is_absolute() { + return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); + } + + let mut base_path = + Path::new(base_path.ok_or(CoreError::Unknown)?).to_path_buf(); + base_path.pop(); + return Ok(base_path + .join(path) + .to_str() + .ok_or(CoreError::Unknown)? + .to_owned()); +} + +pub fn get_required_attrib<'a>( + logger: &mut Logger, + attributes: &'a [OwnedAttribute], + attrib_name: &str, +) -> Result<&'a str> { + match attributes + .iter() + .find(|attrib| attrib.name.local_name.eq_ignore_ascii_case(attrib_name)) + { + Some(appl) => Ok(appl.value.as_str()), + None => { + warn!(logger, "Required \"{attrib_name}\" attribute not found"); + return Err(CoreError::Unknown); + } + } +} + pub fn find_next_element_from_buf( buf: &[u8], logger: &mut Logger, @@ -224,12 +226,15 @@ macro_rules! parse_node { use xml::{ reader::{XmlEvent}, }; + use vlcrs_core::{warn, error}; use crate::util_xml::handle; while let Some(e) = $parser.parser_iter.next() { let elem = e.map_err(|_| CoreError::Unknown)?; match elem { XmlEvent::StartElement { name, attributes: _attr, .. } => { + let start_depth = $parser.depth; + $parser.depth += 1; let name_str = name.local_name.as_str(); debug!($parser.logger, "Found tag: <{name_str}>"); $( @@ -241,10 +246,17 @@ macro_rules! parse_node { )* { warn!($parser.logger, "Skipping unknown tag: <{}> in <{}>", name.local_name, $root_node); - $parser.skip_element(name.local_name.as_str())?; + $parser.skip_element(name_str)?; + } + if $parser.depth > start_depth { + $parser.skip_element(name_str)?; + } + if $parser.depth != start_depth { + error!($parser.logger, "XML tree validation failed at <{name_str}>"); } } XmlEvent::EndElement { name, .. } => { + $parser.depth -= 1; if name.local_name.eq_ignore_ascii_case($root_node) { break; } @@ -287,7 +299,6 @@ macro_rules! handle { $handler:tt) => { // Evaluate inline $handler; - // $handler($parser, $node, $attrib, $ii_node, $ii); }; } diff --git a/modules/vlcrs-core/src/input_item.rs b/modules/vlcrs-core/src/input_item.rs index bb74772cf9b0..c6f5810fe76a 100644 --- a/modules/vlcrs-core/src/input_item.rs +++ b/modules/vlcrs-core/src/input_item.rs @@ -45,7 +45,7 @@ macro_rules! input_meta { #[doc = concat!("Set the *", stringify!($name), "* of this input item")] // #[doc(alias = stringify!($set_fn))] - pub fn $set_name(&self, $name: Option<&str>) { + pub fn $set_name(&mut self, $name: Option<&str>) { use vlcrs_core_sys::$set_fn; let c_string = if let Some(name) = $name { @@ -332,7 +332,7 @@ impl InputItem { /// Set the duration of this input item #[doc(alias = "input_item_SetDuration")] - pub fn set_duration(&self, dur: Tick) { + pub fn set_duration(&mut self, dur: Tick) { // SAFETY: The points to a valid and alive input item unsafe { input_item_SetDuration(self.0.as_ptr(), dur.0) } } -- GitLab From 3bbe9c70380303dd8a5dda340d5ce26ebdeba30a Mon Sep 17 00:00:00 2001 From: Akram Ansari <mohdakram.ansari@ucalgary.ca> Date: Fri, 21 Jul 2023 01:03:07 -0600 Subject: [PATCH 10/24] Added b4s parser --- modules/demux/playlist-rs/src/fmt_b4s.rs | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 modules/demux/playlist-rs/src/fmt_b4s.rs diff --git a/modules/demux/playlist-rs/src/fmt_b4s.rs b/modules/demux/playlist-rs/src/fmt_b4s.rs new file mode 100644 index 000000000000..97679539abe4 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_b4s.rs @@ -0,0 +1,75 @@ +/* +Sample: https://b4s-pl-parser.sourceforge.net/screenshots.php +*/ + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + input_item::{InputItemNode, InputItem}, + warn, tick::Tick, +}; +use xml::attribute::OwnedAttribute; + +use crate::{ + util_buf::starts_with_ignore_case, + util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, + InputContext, PlaylistFormat, +}; + +pub struct FormatB4S; + +impl PlaylistFormat for FormatB4S { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is B4S"); + + return Ok(input.has_extension(".b4s")); + } + + fn parse<'a>( + &'a mut self, + input: &'a mut InputContext<'a>, + input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading B4S metafile"); + + let InputContext { + input_item: input_item_opt, + logger, + source, + path, + } = input; + + let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + + xml_parser.find_next_element("WinampXML", 1)?; + for attrib in xml_parser.find_next_element("playlist", 1)? { + let attr_val = attrib.value; + match attrib.name.local_name.as_str() { + "num_entries" => debug!(xml_parser.logger, "playlist has {attr_val} entries"), + "label" => root_input_item.set_name(Some(&attr_val)), + attr_name => { + warn!(xml_parser.logger, "stray attribute {attr_name} with value {attr_val} in element <playlist>"); + } + } + }; + + parse_node!(&mut xml_parser, "B4S", &mut input_item_node, &mut root_input_item, { + "entry" => { [attrib] { + let mut input_item = InputItem::new_empty()?; + input_item.set_uri(get_required_attrib(xml_parser.logger, attrib, "Playstring").ok()); + parse_node!(&mut xml_parser, "entry", &mut input_item_node, &mut root_input_item, { + "Name" => { [_a, val] { input_item.set_name(Some(&val)); } }, + "Genre" => { [_a, val] { input_item.set_genre(Some(&val)); } }, + "Nowplaying" => { [_a, val] { input_item.set_now_playing(Some(&val)); } }, + "Listeners" => { error!(xml_parser.logger, "Unsupported meta listeners") }, + "Bitrate" => { error!(xml_parser.logger, "Unsupported meta bitrate") }, + }); + input_item_node.append_item(&input_item)?; + } }, + }); + + Ok(()) + } + +} -- GitLab From 9a630765a5df99d63a42435dccf33a604dac2b58 Mon Sep 17 00:00:00 2001 From: Akram Ansari <mohdakram.ansari@ucalgary.ca> Date: Tue, 25 Jul 2023 00:52:28 -0600 Subject: [PATCH 11/24] added ifo and bdmv parsers --- modules/demux/playlist-rs/src/fmt_bdmv.rs | 55 +++++++++++++++ modules/demux/playlist-rs/src/fmt_ifo.rs | 85 +++++++++++++++++++++++ modules/demux/playlist-rs/src/fmt_xspf.rs | 2 +- modules/demux/playlist-rs/src/lib.rs | 6 +- modules/demux/playlist-rs/todo.md | 3 + 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 modules/demux/playlist-rs/src/fmt_bdmv.rs create mode 100644 modules/demux/playlist-rs/src/fmt_ifo.rs diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs new file mode 100644 index 000000000000..49fd4f764c64 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -0,0 +1,55 @@ +/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + input_item::{InputItemNode, InputItem}, + warn, tick::Tick, +}; +use xml::attribute::OwnedAttribute; + +use crate::{ + util_buf::starts_with_ignore_case, + util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, + InputContext, PlaylistFormat, +}; + +pub struct FormatBDMV; + +impl PlaylistFormat for FormatBDMV { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is BDMV"); + + //TODO: p_this->force ??? + // INDEX.BDMV is the only valid filename. + if !input.has_extension("index.bdmv") { + return Ok(false); + } + let peek = input.source.peek(12)?; + let buf = peek.buf(); + if !starts_with_ignore_case(buf, 0, "INDX0200") { + return Ok(false); + } + + return Ok(true); + } + + fn parse<'a>( + &'a mut self, + input: &'a mut InputContext<'a>, + mut input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading BDMV"); + + let path = input.path.as_deref().ok_or(CoreError::Unknown)?; + let name = &path[..path.len()-15]; + let mut input_item = InputItem::new(name, name)?; + // input_item_AddOption( p_input, "demux=bluray", VLC_INPUT_OPTION_TRUSTED ); + input_item.add_option("demux=bluray", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED)?; + input_item_node.append_item(&input_item)?; + + Ok(()) + } + +} diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs new file mode 100644 index 000000000000..c4a9870455c7 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -0,0 +1,85 @@ +/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + + +use vlcrs_core::{ + debug, + error::{CoreError, Result}, + input_item::{InputItemNode, InputItem}, + warn, tick::Tick, +}; +use xml::attribute::OwnedAttribute; + +use crate::{ + util_buf::starts_with_ignore_case, + InputContext, PlaylistFormat, +}; + +enum IFOKind { + Dvd, + DvdVr +} + +pub struct FormatIFO { + kind: Option<IFOKind> +} + +impl PlaylistFormat for FormatIFO { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + debug!(input.logger, "Testing if file is IFO"); + + if !input.has_extension(".IFO") { + return Ok(false); + } + + let path = input.path.as_deref().ok_or(CoreError::Unknown)?; + let file = &path[path.len()-15..]; + if file[..8].eq_ignore_ascii_case("VIDEO_TS") || file[..4].eq_ignore_ascii_case("VTS_") { + /* Valid filenames are : + * - VIDEO_TS.IFO + * - VTS_XX_X.IFO where X are digits + */ + self.kind = Some(IFOKind::Dvd); + } else if file[..8].eq_ignore_ascii_case("VR_MANGR") { + /* Valid filename for DVD-VR is VR_MANGR.IFO */ + self.kind = Some(IFOKind::DvdVr); + } else { + return Ok(false); + } + + let peek = input.source.peek(12)?; + let buf = peek.buf(); + return Ok(starts_with_ignore_case(buf, 0, match self.kind { + Some(IFOKind::Dvd) => "DVDVIDEO", + Some(IFOKind::DvdVr) => "DVD_RTR_", + None => return Ok(false), + })); + } + + fn parse<'a>( + &'a mut self, + input: &'a mut InputContext<'a>, + mut input_item_node: &mut InputItemNode, + ) -> Result<()> { + debug!(input.logger, "Reading IFO"); + + let path = input.path.as_deref().ok_or(CoreError::Unknown)?; + let name = &path[..path.len()-12]; + match self.kind { + Some(IFOKind::Dvd) => { + let mut input_item = InputItem::new(name, name)?; + input_item.add_option("demux=dvd", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED)?; + input_item_node.append_item(&input_item)?; + }, + Some(IFOKind::DvdVr) => { + let item_name = String::from(name) + "VR_MOVIE.VRO"; + let input_item = InputItem::new(&item_name, &item_name)?; + input_item_node.append_item(&input_item)?; + }, + None => return Err(CoreError::Unknown), + } + + + Ok(()) + } + +} diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index f41c0a7745b5..46b1490fd702 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -282,7 +282,7 @@ fn parse_location( return Err(CoreError::Unknown); } - let uri = process_mrl(&value)?; + let uri = parser.process_mrl(&value)?; debug!(parser.logger, "Track MRI: \"{uri}\""); input_item.set_uri(Some(&uri)); diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index bd69a2919cb8..a6defdb6e720 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -24,7 +24,7 @@ use vlcrs_core_macros::module; use crate::{ fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_sgimb::FormatSGIMB, - fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, fmt_asx::FormatASX, + fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, fmt_asx::FormatASX, fmt_bdmv::FormatBDMV, fmt_ifo::FormatIFO, }; mod fmt_m3u; @@ -38,6 +38,8 @@ mod fmt_podcast; mod fmt_itml; mod fmt_asx; mod fmt_b4s; +mod fmt_bdmv; +mod fmt_ifo; mod util_buf; mod util_input_item; mod util_xml; @@ -90,6 +92,8 @@ impl Module for Playlist { Box::new(FormatPodcast {}), Box::new(FormatITML {}), Box::new(FormatASX {}), + Box::new(FormatBDMV {}), + Box::new(FormatIFO { kind: None }), ]; let mut high_prior = 0; diff --git a/modules/demux/playlist-rs/todo.md b/modules/demux/playlist-rs/todo.md index da0f72425933..f4f7b841c903 100644 --- a/modules/demux/playlist-rs/todo.md +++ b/modules/demux/playlist-rs/todo.md @@ -2,3 +2,6 @@ [ ] Check mimetype [ ] Use playlist meta struct for population and then add them to the playlist [ ] Test WMS Parser +[ ] unicode compatibility check +[ ] RAM +[ ] Separate loggers \ No newline at end of file -- GitLab From 74f7271337fbe068e87550dc1b01eb3f4892835d Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 28 Jul 2023 01:29:01 -0600 Subject: [PATCH 12/24] Added RAM parser --- modules/demux/playlist-rs/src/fmt_asx.rs | 4 +- modules/demux/playlist-rs/src/fmt_bdmv.rs | 2 +- modules/demux/playlist-rs/src/fmt_ifo.rs | 6 +- modules/demux/playlist-rs/src/fmt_m3u.rs | 2 +- modules/demux/playlist-rs/src/fmt_qtl.rs | 8 - modules/demux/playlist-rs/src/fmt_ram.rs | 206 +++++++++++++++++++++ modules/demux/playlist-rs/src/fmt_sgimb.rs | 6 +- modules/demux/playlist-rs/src/fmt_xspf.rs | 2 +- modules/demux/playlist-rs/src/lib.rs | 23 ++- modules/demux/playlist-rs/src/util_buf.rs | 8 + modules/demux/playlist-rs/todo.md | 4 +- modules/vlcrs-core/src/input_item.rs | 2 +- 12 files changed, 243 insertions(+), 30 deletions(-) create mode 100644 modules/demux/playlist-rs/src/fmt_ram.rs diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 4ba761bb6283..4566c6d9087f 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -142,7 +142,7 @@ fn parse_entry( if let Some(start) = start { if let Err(_) = input_item.add_option( &format!(":start-time={}", start.to_seconds()), - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32 ) { warn!(parser.logger, "Failed to add start time option"); } @@ -151,7 +151,7 @@ fn parse_entry( input_item.set_duration(duration); if let Err(_) = input_item.add_option( &format!(":stop-time={}", (start.unwrap_or(Tick::ZERO) + duration).to_seconds()), - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32 ) { warn!(parser.logger, "Failed to add stop time option"); } diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index 49fd4f764c64..1bb19305865d 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -46,7 +46,7 @@ impl PlaylistFormat for FormatBDMV { let name = &path[..path.len()-15]; let mut input_item = InputItem::new(name, name)?; // input_item_AddOption( p_input, "demux=bluray", VLC_INPUT_OPTION_TRUSTED ); - input_item.add_option("demux=bluray", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED)?; + input_item.add_option("demux=bluray", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32)?; input_item_node.append_item(&input_item)?; Ok(()) diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index c4a9870455c7..ee2409ed738f 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -14,13 +14,13 @@ use crate::{ InputContext, PlaylistFormat, }; -enum IFOKind { +pub enum IFOKind { Dvd, DvdVr } pub struct FormatIFO { - kind: Option<IFOKind> + pub kind: Option<IFOKind> } impl PlaylistFormat for FormatIFO { @@ -67,7 +67,7 @@ impl PlaylistFormat for FormatIFO { match self.kind { Some(IFOKind::Dvd) => { let mut input_item = InputItem::new(name, name)?; - input_item.add_option("demux=dvd", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED)?; + input_item.add_option("demux=dvd", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32)?; input_item_node.append_item(&input_item)?; }, Some(IFOKind::DvdVr) => { diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 488468fcae2a..13bebe6d9573 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -179,7 +179,7 @@ impl PlaylistFormat for FormatM3U { // TODO: Pass No Flag (0) input_item.add_option( option, - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_UNIQUE, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_UNIQUE as u32, )?; } input_item_node.append_item(&input_item)?; diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 74b21a9a0fb9..5f651fd082df 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -26,14 +26,6 @@ use crate::{util_input_item::InputItemMeta, PlaylistFormat, util_xml::{XmlParser const ROOT_NODE_MAX_DEPTH: u32 = 2; pub struct FormatQTL; -pub struct DemuxerQTL<'a> { - source: &'a mut Stream, - es_out: EsOutBaked<'a>, - logger: &'a mut Logger, - path: Option<String>, - input_item: Option<&'a mut InputItem>, -} - #[derive(Debug)] enum QtlFullScreenType { FullScreenNormal, diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs new file mode 100644 index 000000000000..d8d58c1893a9 --- /dev/null +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -0,0 +1,206 @@ +use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str, io::Read}; + +use vlcrs_core::{ + debug, error, + error::{CoreError, Result}, + es_out::{EsOut, EsOutBaked}, + input_item::{self, InputItem, InputItemNode}, + messages::Logger, + module::{ + demux::{DemuxModule, ThisDemux}, + ModuleArgs, + }, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Seconds, Tick}, + warn, +}; +use xml::{ + attribute::OwnedAttribute, + name::{Name, OwnedName}, + reader::{Events, XmlEvent}, + EventReader, +}; + +use crate::{util_input_item::InputItemMeta, PlaylistFormat, util_xml::{XmlParser, process_mrl}, InputContext, util_buf::{starts_with, read_to_end, skip_chars, read_int}}; + +pub struct FormatRAM; + +impl PlaylistFormat for FormatRAM { + fn can_open( + &mut self, + input: &mut InputContext + ) -> Result<bool> { + debug!(input.logger, "Testing if file is RAM"); + + if !input.has_extension(".ram") || !input.has_extension(".rm"){ + return Ok(false); + } + + let peek = input.source.peek(4)?; + let buf = peek.buf(); + + if !starts_with(buf, 0, ".ra") || !starts_with(buf, 0, ".RMF"){ + return Ok(false); + } + + debug!(input.logger, "found valid RAM playlist"); + + return Ok(true); + } + + fn parse<'a>( + &mut self, + input: &mut InputContext, + input_item_node: &mut InputItemNode + ) -> Result<()> { + debug!(input.logger, "Starting RAM parser"); + + let mut buf: String = String::new(); + input.source + .read_to_string(&mut buf) + .map_err(|_| CoreError::Unknown)?; + + for line in buf.lines() { + debug!(input.logger, "Line: {line}"); + + + let line_buf = line.as_bytes(); + + let mut buf_i = 0; + let end = line_buf.len(); + + // Skip leading tabs and spaces + skip_chars(line_buf, &mut buf_i, " \t\n\r".as_bytes()); + + if buf_i >= end { + break; + } + + /* Ignore comments */ + if line_buf[buf_i] as char == '#' { + continue; + } + + let media_path = match read_to_end(line_buf, buf_i)? { + Some(val) => val, + None => continue, + }; + + let mrl = match process_mrl(media_path, input.path.as_deref()) { + Ok(val) => val, + Err(_) => continue, + }; + + let mut input = InputItem::new(&mrl, &mrl)?; + + if let Some(option_start) = mrl.find('?') { + for option in mrl[option_start+1..].split('&') { + + let (param, value) = match option.split_once('=') { + Some(val) => val, + None => break, + }; + + + match param { + "clipinfo" => parse_clipinfo(value, &mut input), + "author" => input.set_publisher(Some(value)), // TODO: EnsureUTF8 + "start" => if mrl.starts_with("rtsp") { + let start_time = parse_time(value.as_bytes()); + input.add_option(format!(":start-time={}", start_time).as_str(), 0)?; + }, + "end" => { + let stop_time = parse_time(value.as_bytes()); + input.add_option(format!(":stop-time={}", stop_time).as_str(), 0)?; + }, + "title" => input.set_title(Some(value)), + "copyright" => input.set_copyright(Some(value)), + _ => {} + } + } + } + + input.set_name(input.title().map(str::to_string).as_deref()); + + input_item_node.append_item(&input)?; + + } + + Ok(()) + } + + fn get_extension(&self) -> &'static [&'static str] { + &[".ram", ".rm"] + } +} + + +fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { + let option_start = match clipinfo.find('"') { + Some(val) => val + 1, + None => return, + }; + + for option in clipinfo[option_start..].split(&['|', '"']) { + let (param, value) = match option.split_once('=') { + Some(val) => val, + None => break, + }; + match param { + "artist name" => input.set_artist(Some(value)), + "title" => input.set_title(Some(value)), + "album name" => input.set_album(Some(value)), + "genre" => input.set_genre(Some(value)), + "year" => input.set_date(Some(value)), + "cdnum" => input.set_track_number(Some(value)), + "comments" => input.set_description(Some(value)), + _ => {} + } + } +} + +fn parse_time(buf: &[u8]) -> i32 { + let mut buf_i = 0; + + // skip leading spaces if any + skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + + let mut result = match read_int(buf, &mut buf_i) { + Ok(val) => val, + Err(_) => 0, + }; + + skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + + // Parse minute + if buf_i < buf.len() && buf[buf_i] as char == ':' { + result *= 60; + buf_i += 1; + + skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + + result += match read_int(buf, &mut buf_i) { + Ok(val) => val, + Err(_) => 0, + }; + + } + + skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + + // Parse second + if buf_i < buf.len() && buf[buf_i] as char == ':' { + result *= 60; + buf_i += 1; + + skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + + result += match read_int(buf, &mut buf_i) { + Ok(val) => val, + Err(_) => 0, + }; + + } + + return result; +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index 97029fd8d7c3..0093309ba083 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -128,21 +128,21 @@ impl PlaylistFormat for FormatSGIMB { let option = format!("mtu={}", self.packet_size); child.add_option( &option, - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, )?; } if self.mcast_ip.is_none() { child.add_option( "rtsp-caching=5000", - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, )?; } if self.mcast_ip.is_none() && self.rtsp_kasenna { child.add_option( "rtsp-kasenna", - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED, + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, )?; } diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index 46b1490fd702..549fb54f3cad 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -264,7 +264,7 @@ fn set_option( } // TODO: Flag should be zero - input_item.add_option(&value, input_item::Flag::VLC_INPUT_OPTION_UNIQUE)?; + input_item.add_option(&value, input_item::Flag::VLC_INPUT_OPTION_UNIQUE as u32)?; Ok(()) } diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index a6defdb6e720..ce08eb9357da 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -23,23 +23,25 @@ use vlcrs_core::{ use vlcrs_core_macros::module; use crate::{ - fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_qtl::FormatQTL, fmt_sgimb::FormatSGIMB, - fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_itml::FormatITML, fmt_podcast::FormatPodcast, fmt_asx::FormatASX, fmt_bdmv::FormatBDMV, fmt_ifo::FormatIFO, + fmt_asx::FormatASX, fmt_bdmv::FormatBDMV, fmt_ifo::FormatIFO, fmt_itml::FormatITML, + fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_podcast::FormatPodcast, fmt_qtl::FormatQTL, + fmt_sgimb::FormatSGIMB, fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_ram::FormatRAM, }; +mod fmt_asx; +mod fmt_b4s; +mod fmt_bdmv; +mod fmt_ifo; +mod fmt_itml; mod fmt_m3u; mod fmt_pls; +mod fmt_podcast; mod fmt_qtl; +mod fmt_ram; mod fmt_sgimb; mod fmt_wms; mod fmt_wpl; mod fmt_xspf; -mod fmt_podcast; -mod fmt_itml; -mod fmt_asx; -mod fmt_b4s; -mod fmt_bdmv; -mod fmt_ifo; mod util_buf; mod util_input_item; mod util_xml; @@ -88,12 +90,15 @@ impl Module for Playlist { Box::new(FormatWPL {}), Box::new(FormatQTL {}), Box::new(FormatXSPF {}), - Box::new(FormatSGIMB {..Default::default()}, ), + Box::new(FormatSGIMB { + ..Default::default() + }), Box::new(FormatPodcast {}), Box::new(FormatITML {}), Box::new(FormatASX {}), Box::new(FormatBDMV {}), Box::new(FormatIFO { kind: None }), + Box::new(FormatRAM { }), ]; let mut high_prior = 0; diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index c1924773a44f..1fe117b9efbb 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -48,6 +48,14 @@ pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<u None } +pub fn starts_with(buf: &[u8], buf_i: usize, prefix: &str) -> bool { + let end = buf_i + prefix.len(); + if end > buf.len() { + return false; + } + return buf[buf_i..end].eq(prefix.as_bytes()); +} + pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { let end = buf_i + prefix.len(); if end > buf.len() { diff --git a/modules/demux/playlist-rs/todo.md b/modules/demux/playlist-rs/todo.md index f4f7b841c903..544fc04b4791 100644 --- a/modules/demux/playlist-rs/todo.md +++ b/modules/demux/playlist-rs/todo.md @@ -4,4 +4,6 @@ [ ] Test WMS Parser [ ] unicode compatibility check [ ] RAM -[ ] Separate loggers \ No newline at end of file +[ ] Separate loggers +[ ] UTF-8 checks +[ ] implement get_extension function for all parsers \ No newline at end of file diff --git a/modules/vlcrs-core/src/input_item.rs b/modules/vlcrs-core/src/input_item.rs index c6f5810fe76a..3a3c3fe8b7d4 100644 --- a/modules/vlcrs-core/src/input_item.rs +++ b/modules/vlcrs-core/src/input_item.rs @@ -281,7 +281,7 @@ impl InputItem { /// Add an option #[doc(alias = "input_item_AddOption")] - pub fn add_option(&mut self, option: &str, flag: Flag) -> Result<()> { + pub fn add_option(&mut self, option: &str, flag: u32) -> Result<()> { let c_opt = CString::new(option).unwrap(); // SAFETY: TODO -- GitLab From 126f521ec51710e2b37eb46dd94f893028460ab1 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 4 Aug 2023 14:47:00 -0600 Subject: [PATCH 13/24] cleanup and tests init --- modules/demux/playlist-rs/src/fmt_asx.rs | 1 - modules/demux/playlist-rs/src/fmt_b4s.rs | 16 ++--- modules/demux/playlist-rs/src/fmt_bdmv.rs | 5 +- modules/demux/playlist-rs/src/fmt_m3u.rs | 24 +++----- modules/demux/playlist-rs/src/fmt_ram.rs | 61 ++++++++++--------- modules/demux/playlist-rs/src/fmt_sgimb.rs | 14 ++--- modules/demux/playlist-rs/src/fmt_wms.rs | 4 +- modules/demux/playlist-rs/src/util_buf.rs | 43 ++++++++++++- .../demux/playlist-rs/src/util_input_item.rs | 3 +- 9 files changed, 98 insertions(+), 73 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 4566c6d9087f..d1885b7d8614 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -7,7 +7,6 @@ use vlcrs_core::{ input_item::{InputItemNode, InputItem}, warn, tick::Tick, }; -use xml::attribute::OwnedAttribute; use crate::{ util_buf::starts_with_ignore_case, diff --git a/modules/demux/playlist-rs/src/fmt_b4s.rs b/modules/demux/playlist-rs/src/fmt_b4s.rs index 97679539abe4..4fbd68804f63 100644 --- a/modules/demux/playlist-rs/src/fmt_b4s.rs +++ b/modules/demux/playlist-rs/src/fmt_b4s.rs @@ -5,14 +5,12 @@ Sample: https://b4s-pl-parser.sourceforge.net/screenshots.php use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItemNode, InputItem}, - warn, tick::Tick, + input_item::{InputItem, InputItemNode}, + warn, }; -use xml::attribute::OwnedAttribute; use crate::{ - util_buf::starts_with_ignore_case, - util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, + util_xml::{get_required_attrib, parse_node, XmlParser}, InputContext, PlaylistFormat, }; @@ -49,10 +47,13 @@ impl PlaylistFormat for FormatB4S { "num_entries" => debug!(xml_parser.logger, "playlist has {attr_val} entries"), "label" => root_input_item.set_name(Some(&attr_val)), attr_name => { - warn!(xml_parser.logger, "stray attribute {attr_name} with value {attr_val} in element <playlist>"); + warn!( + xml_parser.logger, + "stray attribute {attr_name} with value {attr_val} in element <playlist>" + ); } } - }; + } parse_node!(&mut xml_parser, "B4S", &mut input_item_node, &mut root_input_item, { "entry" => { [attrib] { @@ -71,5 +72,4 @@ impl PlaylistFormat for FormatB4S { Ok(()) } - } diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index 1bb19305865d..144f6d287b3d 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -5,13 +5,10 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItemNode, InputItem}, - warn, tick::Tick, }; -use xml::attribute::OwnedAttribute; use crate::{ util_buf::starts_with_ignore_case, - util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, InputContext, PlaylistFormat, }; @@ -38,7 +35,7 @@ impl PlaylistFormat for FormatBDMV { fn parse<'a>( &'a mut self, input: &'a mut InputContext<'a>, - mut input_item_node: &mut InputItemNode, + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading BDMV"); diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 13bebe6d9573..3b43a72b5725 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -1,23 +1,14 @@ //! Playlist_M3U Demux module use std::{ - collections::HashSet, - io::{BufRead, Read}, - rc::Rc, str, + io::{BufRead, Read} }; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, input_item::{InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Seconds, Tick}, }; @@ -133,29 +124,30 @@ impl PlaylistFormat for FormatM3U { Ok(_) => { if buf_i < end && line_buf[buf_i] as char == ',' { buf_i += 1; - meta.name = read_to_end(line_buf, buf_i)?; + meta.name = Some(read_to_end(line_buf, buf_i)?); } } Err(_) => debug!(input.logger, "Parsing of IPTV diots failed"), } } } else if starts_with_ignore_case(line_buf, buf_i, "EXTGRP:") { - meta.group = read_to_end(line_buf, buf_i + 7)?; + meta.group = Some(read_to_end(line_buf, buf_i + 7)?); } else if starts_with_ignore_case(line_buf, buf_i, "EXTVLCOPT:") { - if let Some(vlc_option) = read_to_end(line_buf, buf_i + 10)? { + let vlc_option = read_to_end(line_buf, buf_i + 10)?; + if !vlc_option.is_empty() { meta.options.push(vlc_option); } } else if starts_with_ignore_case(line_buf, buf_i, "EXTALBUMARTURL:") { - meta.album_art = read_to_end(line_buf, buf_i + 15)?; + meta.album_art = Some(read_to_end(line_buf, buf_i + 15)?); } else if starts_with_ignore_case(line_buf, buf_i, "PLAYLIST:") { if let Some(mut input_item) = input_item_node.get_item() { - input_item.set_title(read_to_end(line_buf, buf_i + 15)?); + input_item.set_title(Some(read_to_end(line_buf, buf_i + 15)?)); } } } else if starts_with_ignore_case(line_buf, buf_i, "RTSPtext") { // special case to handle QuickTime RTSPtext redirect files } else { - let media_path = read_to_end(line_buf, buf_i)?.ok_or(CoreError::Unknown)?; + let media_path = read_to_end(line_buf, buf_i)?; if meta.group.is_some() && meta.group_title.is_none() { meta.group_title = meta.group.clone(); } diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index d8d58c1893a9..74fccca0e156 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -1,4 +1,4 @@ -use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str, io::Read}; +use std::{borrow::BorrowMut, fmt::Display, io::Read, rc::Rc, str}; use vlcrs_core::{ debug, error, @@ -21,25 +21,27 @@ use xml::{ EventReader, }; -use crate::{util_input_item::InputItemMeta, PlaylistFormat, util_xml::{XmlParser, process_mrl}, InputContext, util_buf::{starts_with, read_to_end, skip_chars, read_int}}; +use crate::{ + util_buf::{read_int, read_to_end, skip_chars, starts_with}, + util_input_item::InputItemMeta, + util_xml::{process_mrl, XmlParser}, + InputContext, PlaylistFormat, +}; pub struct FormatRAM; impl PlaylistFormat for FormatRAM { - fn can_open( - &mut self, - input: &mut InputContext - ) -> Result<bool> { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is RAM"); - if !input.has_extension(".ram") || !input.has_extension(".rm"){ + if !input.has_extension(".ram") || !input.has_extension(".rm") { return Ok(false); } let peek = input.source.peek(4)?; let buf = peek.buf(); - if !starts_with(buf, 0, ".ra") || !starts_with(buf, 0, ".RMF"){ + if !starts_with(buf, 0, ".ra") || !starts_with(buf, 0, ".RMF") { return Ok(false); } @@ -51,19 +53,19 @@ impl PlaylistFormat for FormatRAM { fn parse<'a>( &mut self, input: &mut InputContext, - input_item_node: &mut InputItemNode + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Starting RAM parser"); let mut buf: String = String::new(); - input.source + input + .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; for line in buf.lines() { debug!(input.logger, "Line: {line}"); - let line_buf = line.as_bytes(); let mut buf_i = 0; @@ -82,10 +84,10 @@ impl PlaylistFormat for FormatRAM { } let media_path = match read_to_end(line_buf, buf_i)? { - Some(val) => val, - None => continue, + "" => continue, + val => val, }; - + let mrl = match process_mrl(media_path, input.path.as_deref()) { Ok(val) => val, Err(_) => continue, @@ -94,25 +96,28 @@ impl PlaylistFormat for FormatRAM { let mut input = InputItem::new(&mrl, &mrl)?; if let Some(option_start) = mrl.find('?') { - for option in mrl[option_start+1..].split('&') { - + for option in mrl[option_start + 1..].split('&') { let (param, value) = match option.split_once('=') { Some(val) => val, None => break, }; - match param { "clipinfo" => parse_clipinfo(value, &mut input), "author" => input.set_publisher(Some(value)), // TODO: EnsureUTF8 - "start" => if mrl.starts_with("rtsp") { - let start_time = parse_time(value.as_bytes()); - input.add_option(format!(":start-time={}", start_time).as_str(), 0)?; - }, + "start" => { + if mrl.starts_with("rtsp") { + let start_time = parse_time(value.as_bytes()); + input.add_option( + format!(":start-time={}", start_time).as_str(), + 0, + )?; + } + } "end" => { let stop_time = parse_time(value.as_bytes()); input.add_option(format!(":stop-time={}", stop_time).as_str(), 0)?; - }, + } "title" => input.set_title(Some(value)), "copyright" => input.set_copyright(Some(value)), _ => {} @@ -123,7 +128,6 @@ impl PlaylistFormat for FormatRAM { input.set_name(input.title().map(str::to_string).as_deref()); input_item_node.append_item(&input)?; - } Ok(()) @@ -134,7 +138,6 @@ impl PlaylistFormat for FormatRAM { } } - fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { let option_start = match clipinfo.find('"') { Some(val) => val + 1, @@ -161,10 +164,10 @@ fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { fn parse_time(buf: &[u8]) -> i32 { let mut buf_i = 0; - + // skip leading spaces if any skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); - + let mut result = match read_int(buf, &mut buf_i) { Ok(val) => val, Err(_) => 0, @@ -183,11 +186,10 @@ fn parse_time(buf: &[u8]) -> i32 { Ok(val) => val, Err(_) => 0, }; - } skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); - + // Parse second if buf_i < buf.len() && buf[buf_i] as char == ':' { result *= 60; @@ -199,8 +201,7 @@ fn parse_time(buf: &[u8]) -> i32 { Ok(val) => val, Err(_) => 0, }; - } return result; -} \ No newline at end of file +} diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index 0093309ba083..c794e2289c30 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -165,7 +165,7 @@ impl FormatSGIMB { match_ignore_case!(line, buf_i, { "rtsp://" => { - self.uri = read_to_end(line, start_pos)?.map(str::to_string); + self.uri = Some(read_to_end(line, start_pos)?.to_string()); }, "Stream=" => { if let Some(end) = find_in_buf(line, buf_i, "\"".as_bytes()) { @@ -182,25 +182,25 @@ impl FormatSGIMB { } }, "sgiNameServerHost=" => { - self.server = read_to_end(line, buf_i)?.map(str::to_string); + self.server = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiMovieName=" => { - self.location = read_to_end(line, buf_i)?.map(str::to_string); + self.location = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiUserAccount=" => { - self.user = read_to_end(line, buf_i)?.map(str::to_string); + self.user = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiUserPassword=" => { - self.password = read_to_end(line, buf_i)?.map(str::to_string); + self.password = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiShowingName=" => { - self.name = read_to_end(line, buf_i)?.map(str::to_string); + self.name = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiFormatName=" => { self.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4".as_bytes()).is_some(); }, "sgiMulticastAddress=" => { - self.mcast_ip = read_to_end(line, buf_i)?.map(str::to_string); + self.mcast_ip = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiMulticastPort=" => { self.mcast_port = read_int(line, &mut buf_i).unwrap_or(0); diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 85c456b5c944..9d9597f003cb 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -70,8 +70,8 @@ impl PlaylistFormat for FormatWMS { continue; } - let mut value = if value_str[..7].eq_ignore_ascii_case("http://") { - ("mmsh".to_owned() + &value_str[4..]) + let value = if value_str[..7].eq_ignore_ascii_case("http://") { + "mmsh".to_owned() + &value_str[4..] } else { value_str.to_owned() }; diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 1fe117b9efbb..73c95762bf25 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -3,11 +3,11 @@ use vlcrs_core::error::{CoreError, Result}; use crate::InputContext; -pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<Option<&'a str>> { +pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { if buf_i < buf.len() { - Ok(Some(str::from_utf8(&buf[buf_i..]).map_err(|_| CoreError::Unknown)?)) + Ok(str::from_utf8(&buf[buf_i..]).map_err(|_| CoreError::Unknown)?) } else { - Ok(None) + Ok("") } } @@ -107,6 +107,10 @@ pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { .map_err(|_| CoreError::Unknown); } +pub fn ensure_utf8(buf: &mut [u8], buf_i: &mut usize) { + +} + macro_rules! match_ignore_case { ($buf:expr, $buf_i:expr, { $($start_str:expr => $handler:tt),* @@ -126,3 +130,36 @@ macro_rules! match_ignore_case { } pub(crate) use match_ignore_case; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_to_end() -> Result<()> { + let buf = b"test buffer line \n \t $%#Q$ \n"; + let result = read_to_end(buf, 0)?; + assert_eq!(result.as_bytes(), buf); + + let result = read_to_end(buf, 10)?; + assert_eq!(result.as_bytes(), &buf[10..]); + + let result = read_to_end(b"", 10)?; + assert_eq!(result, ""); + + let result = read_to_end(b"", 0)?; + assert_eq!(result, ""); + + Ok(()) + } + + #[test] + fn test_skip_chars() -> Result<()>{ + let buf = b"test buffer line \n \t $%#Q$ \n"; + let mut buf_i = 0; + skip_chars(buf, &mut buf_i, b" tste"); + assert_eq!(buf_i, 5); + Ok(()) + } + +} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/util_input_item.rs b/modules/demux/playlist-rs/src/util_input_item.rs index dfef8dcc5228..987191f8757e 100644 --- a/modules/demux/playlist-rs/src/util_input_item.rs +++ b/modules/demux/playlist-rs/src/util_input_item.rs @@ -1,6 +1,5 @@ use vlcrs_core::{ - error::{CoreError, Result}, - stream::Stream, tick::Tick, input_item::{InputItemNode, InputItem}, + tick::Tick, input_item::{InputItemNode, InputItem}, }; pub(crate) type InputItemRefPair<'a> = (&'a mut InputItemNode, &'a mut Option<&'a mut InputItem>); -- GitLab From e716c0241d31c00199aa667eb15a905997b72f25 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 4 Aug 2023 15:07:08 -0600 Subject: [PATCH 14/24] cleanup and tests --- modules/demux/playlist-rs/src/fmt_asx.rs | 6 +- modules/demux/playlist-rs/src/fmt_ifo.rs | 5 +- modules/demux/playlist-rs/src/fmt_itml.rs | 2 +- modules/demux/playlist-rs/src/fmt_m3u.rs | 22 +++--- modules/demux/playlist-rs/src/fmt_pls.rs | 15 +--- modules/demux/playlist-rs/src/fmt_podcast.rs | 24 ++---- modules/demux/playlist-rs/src/fmt_qtl.rs | 24 ++---- modules/demux/playlist-rs/src/fmt_ram.rs | 25 ++---- modules/demux/playlist-rs/src/fmt_sgimb.rs | 18 ++--- modules/demux/playlist-rs/src/fmt_wms.rs | 15 +--- modules/demux/playlist-rs/src/fmt_wpl.rs | 34 +++------ modules/demux/playlist-rs/src/fmt_xspf.rs | 35 ++++----- modules/demux/playlist-rs/src/lib.rs | 18 ++--- modules/demux/playlist-rs/src/util_buf.rs | 76 ++++++++++++++++++- .../demux/playlist-rs/src/util_input_item.rs | 6 +- modules/demux/playlist-rs/src/util_xml.rs | 2 +- 16 files changed, 148 insertions(+), 179 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index d1885b7d8614..084ab2f9ed25 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -39,7 +39,7 @@ impl PlaylistFormat for FormatASX { fn parse<'a>( &'a mut self, input: &'a mut InputContext<'a>, - mut input_item_node: &mut InputItemNode, + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading ASX metafile"); @@ -76,7 +76,7 @@ impl PlaylistFormat for FormatASX { input_item.set_name(root_input_item.name()); input_item_node.append_item(&input_item)?; }}, - "Entry" => { parse_entry(&mut xml_parser, input_item_node, root_input_item, base); }, + "Entry" => { parse_entry(&mut xml_parser, input_item_node, root_input_item, base).ok(); }, }); Ok(()) @@ -168,7 +168,7 @@ fn parse_entry( Ok(()) } -fn parse_time(s: &str) -> Result<Tick> { +fn parse_time(_s: &str) -> Result<Tick> { // TODO: parse_time not implemented todo!("parse_time not implemented") } \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index ee2409ed738f..92777112c6e5 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -5,9 +5,8 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItemNode, InputItem}, - warn, tick::Tick, }; -use xml::attribute::OwnedAttribute; + use crate::{ util_buf::starts_with_ignore_case, @@ -58,7 +57,7 @@ impl PlaylistFormat for FormatIFO { fn parse<'a>( &'a mut self, input: &'a mut InputContext<'a>, - mut input_item_node: &mut InputItemNode, + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading IFO"); diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index f1a0b7e53bae..1063bff03131 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -76,7 +76,7 @@ impl PlaylistFormat for FormatITML { path, } = input; - let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let _root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; let mut version: Option<String> = None; diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 3b43a72b5725..0054433d9d9f 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -42,7 +42,7 @@ impl PlaylistFormat for FormatM3U { if !buf.starts_with("#EXTM3U".as_bytes()) && !buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) - && !containsURL(buf)? + && !contains_url(buf)? && !input.has_mime_type("application/mpegurl") && !input.has_mime_type("application/x-mpegurl") && !input.has_mime_type("audio/mpegurl") @@ -115,12 +115,12 @@ impl PlaylistFormat for FormatM3U { if line_buf[buf_i] as char == ',' { /* EXTINF:1,title*/ /* EXTINF: -123.12 ,title*/ - (meta.artist, meta.name) = parse_EXTINF_title(line_buf, buf_i + 1)?; + (meta.artist, meta.name) = parse_extinf_title(line_buf, buf_i + 1)?; } else if line_buf[buf_i].is_ascii_alphabetic() { /*EXTINF: -1 tvg-foo="val" tvg-foo2="val",title EXTINF: -1 tvg-foo="val,val2" ,title*/ - match parse_EXTINF_iptv_diots_in_duration(&mut meta, line_buf, &mut buf_i) { + match parse_extinf_iptv_diots_in_duration(&mut meta, line_buf, &mut buf_i) { Ok(_) => { if buf_i < end && line_buf[buf_i] as char == ',' { buf_i += 1; @@ -186,7 +186,7 @@ impl PlaylistFormat for FormatM3U { } } -fn parse_EXTINF_title<'a>( +fn parse_extinf_title<'a>( buf: &'a [u8], buf_i: usize, ) -> Result<(Option<&'a str>, Option<&'a str>)> { @@ -212,7 +212,7 @@ fn parse_EXTINF_title<'a>( )); } -fn parse_EXTINF_iptv_diots_in_duration<'a>( +fn parse_extinf_iptv_diots_in_duration<'a>( meta: &mut InputItemMeta<'a>, buf: &'a [u8], buf_i: &mut usize, @@ -229,7 +229,7 @@ fn parse_EXTINF_iptv_diots_in_duration<'a>( let key_i = key_start.ok_or(CoreError::Unknown)?; let val_i = val_start.ok_or(CoreError::Unknown)?; let eq_i = eq_start.ok_or(CoreError::Unknown)?; - parse_EXTINF_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; + parse_extinf_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; (key_start, val_start, eq_start) = (None, None, None); } @@ -270,17 +270,17 @@ fn parse_EXTINF_iptv_diots_in_duration<'a>( let key_i = key_start.ok_or(CoreError::Unknown)?; let val_i = val_start.ok_or(CoreError::Unknown)?; let eq_i = eq_start.ok_or(CoreError::Unknown)?; - parse_EXTINF_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; + parse_extinf_iptv_diots(meta, buf, key_i, eq_i - 1, val_i, *buf_i - 1)?; } Ok(()) } -fn parse_EXTINF_iptv_diots<'a>( +fn parse_extinf_iptv_diots<'a>( meta: &mut InputItemMeta<'a>, buf: &'a [u8], - mut key_start: usize, - mut key_end: usize, + key_start: usize, + _key_end: usize, mut val_start: usize, mut val_end: usize, ) -> Result<()> { @@ -310,7 +310,7 @@ fn parse_EXTINF_iptv_diots<'a>( } /// Function to check if the first non-comment line contains url -fn containsURL(buf: &[u8]) -> Result<bool> { +fn contains_url(buf: &[u8]) -> Result<bool> { for line in buf.lines() { let l: String = line.map_err(|_| CoreError::Unknown)?; if l.is_empty() || l.starts_with('#') { diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index 6f95952a2d01..a081c15981e2 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -1,18 +1,9 @@ -use std::{io::Read, path::Path, rc::Rc}; +use std::{io::Read, rc::Rc}; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{InputItem, self, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::Tick, - url::Url, + input_item::{InputItem, InputItemNode}, }; use crate::{PlaylistFormat, InputContext}; @@ -26,7 +17,7 @@ impl PlaylistFormat for FormatPLS { ) -> Result<bool> { debug!(input.logger, "Testing if file is PLS"); let peek = input.source.peek(1024)?; - let mut buf = peek.buf(); + let buf = peek.buf(); if buf.len() < 10 { return Err(CoreError::Unknown); diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index daa6e5e41e44..17d24cb8155a 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -1,27 +1,15 @@ -/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc -use std::{any::Any, io::Read, path::Path, rc::Rc}; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode, MetaType::*}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Miliseconds, Tick, Seconds}, - url::Url, + input_item::{InputItem, InputItemNode, MetaType::*}, + tick::{Tick, Seconds}, warn, }; -use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; + use crate::{ - util_buf::starts_with_ignore_case, - util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, util_xml::{find_next_element_from_buf, parse_node, XmlParser}, InputContext, PlaylistFormat, }; @@ -47,8 +35,8 @@ impl PlaylistFormat for FormatPodcast { fn parse<'a>( &'a mut self, - mut input: &'a mut InputContext<'a>, - mut input_item_node: &mut InputItemNode, + input: &'a mut InputContext<'a>, + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading Podcast metafile"); @@ -80,7 +68,7 @@ impl PlaylistFormat for FormatPodcast { macro_rules! add_info { ($info:expr, $field:expr) => { - input_item.add_info_str("Podcast Info", $info, $field)?; + input_item.add_info_str("Podcast Info", $info, $field)? }; ($info:expr, $field:expr, $meta:expr) => { input_item.add_info_str("Podcast Info", $info, $field)?; diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 5f651fd082df..56b4757ce800 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -1,27 +1,13 @@ -use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str}; +use std::{str}; use vlcrs_core::{ debug, error, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Seconds, Tick}, - warn, -}; -use xml::{ - attribute::OwnedAttribute, - name::{Name, OwnedName}, - reader::{Events, XmlEvent}, - EventReader, + input_item::{InputItem, InputItemNode}, }; -use crate::{util_input_item::InputItemMeta, PlaylistFormat, util_xml::{XmlParser}, InputContext}; + +use crate::{PlaylistFormat, util_xml::{XmlParser}, InputContext}; const ROOT_NODE_MAX_DEPTH: u32 = 2; pub struct FormatQTL; @@ -72,7 +58,7 @@ impl PlaylistFormat for FormatQTL { path, } = input; - let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; + let _input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; let attributes = xml_parser.find_next_element("embed", ROOT_NODE_MAX_DEPTH)?; diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index 74fccca0e156..bb335c538a1b 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -1,30 +1,15 @@ -use std::{borrow::BorrowMut, fmt::Display, io::Read, rc::Rc, str}; +use std::{io::Read, str}; use vlcrs_core::{ - debug, error, + debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Seconds, Tick}, - warn, -}; -use xml::{ - attribute::OwnedAttribute, - name::{Name, OwnedName}, - reader::{Events, XmlEvent}, - EventReader, + input_item::{InputItem, InputItemNode}, }; + use crate::{ util_buf::{read_int, read_to_end, skip_chars, starts_with}, - util_input_item::InputItemMeta, - util_xml::{process_mrl, XmlParser}, + util_xml::{process_mrl}, InputContext, PlaylistFormat, }; diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index c794e2289c30..cdca6944eca4 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -1,18 +1,10 @@ -use std::{io::Read, path::Path, rc::Rc}; +use std::io::Read; use vlcrs_core::{ debug, error, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, input_item::{InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Microseconds, Tick}, - url::Url, }; use crate::{ @@ -146,7 +138,7 @@ impl PlaylistFormat for FormatSGIMB { )?; } - input_item_node.append_item(&child); + input_item_node.append_item(&child).ok(); Ok(()) } @@ -155,9 +147,9 @@ impl PlaylistFormat for FormatSGIMB { impl FormatSGIMB { fn parse_line( &mut self, - mut line: &[u8], - input: &mut InputContext, - input_item_node: &mut InputItemNode, + line: &[u8], + _input: &mut InputContext, + _input_item_node: &mut InputItemNode, ) -> Result<()> { let mut buf_i = 0; skip_chars(line, &mut buf_i, " \t\n\r".as_bytes()); diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 9d9597f003cb..0ce696ef08ab 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -1,18 +1,9 @@ -use std::{io::Read, path::Path, rc::Rc}; +use std::{io::Read}; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, input_item::{InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::Tick, - url::Url, warn, }; @@ -28,7 +19,7 @@ impl PlaylistFormat for FormatWMS { ) -> Result<bool> { debug!(input.logger, "Testing if file is WMS"); let peek = input.source.peek(1024)?; - let mut buf = peek.buf(); + let buf = peek.buf(); if buf.len() < 10 || !starts_with_ignore_case(buf, 0, "[Reference]") { return Err(CoreError::Unknown); @@ -77,7 +68,7 @@ impl PlaylistFormat for FormatWMS { }; let input_item = InputItem::new(&value, &value)?; - input_item_node.append_item(&input_item); + input_item_node.append_item(&input_item).ok(); }; } diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs index 0e55cc2b8c33..8116cfd27d29 100644 --- a/modules/demux/playlist-rs/src/fmt_wpl.rs +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -1,34 +1,18 @@ -use std::{borrow::BorrowMut, fmt::Display, rc::Rc, str}; +use std::str; use vlcrs_core::{ debug, error, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Seconds, Tick}, + input_item::{InputItem, InputItemNode}, warn, }; -use xml::{ - attribute::OwnedAttribute, - name::{Name, OwnedName}, - reader::{Events, XmlEvent}, - EventReader, -}; +use xml::attribute::OwnedAttribute; use crate::{ - util_input_item::InputItemMeta, util_xml::{find_next_element_from_buf, parse_node, XmlParser}, InputContext, PlaylistFormat, }; -const MAX_SEARCH_DEPTH: u32 = 100; - pub struct FormatWPL; impl PlaylistFormat for FormatWPL { @@ -131,8 +115,8 @@ fn read_head( fn read_body( parser: &mut XmlParser, - name: &str, - attributes: &[OwnedAttribute], + _name: &str, + _attributes: &[OwnedAttribute], ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { @@ -192,8 +176,8 @@ fn parse_meta( fn read_title( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], - ii_node: &mut InputItemNode, + _attributes: &[OwnedAttribute], + _ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { let value = parser.parse_text_node(name)?; @@ -206,10 +190,10 @@ fn read_title( fn parse_media( parser: &mut XmlParser, - name: &str, + _name: &str, attributes: &[OwnedAttribute], ii_node: &mut InputItemNode, - input_item: &mut InputItem, + _input_item: &mut InputItem, ) -> Result<()> { for attr in attributes { let attr_name = attr.name.local_name.as_str(); diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index 549fb54f3cad..bb1a524e0073 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -1,25 +1,16 @@ -use std::{any::Any, io::Read, path::Path, rc::Rc}; + use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, input_item::{self, InputItem, InputItemNode}, - messages::Logger, - module::{ - demux::{DemuxModule, ThisDemux}, - ModuleArgs, - }, - stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Miliseconds, Tick}, - url::Url, warn, }; -use xml::{attribute::OwnedAttribute, reader::Events, EventReader}; +use xml::{attribute::OwnedAttribute}; use crate::{ - util_buf::starts_with_ignore_case, - util_input_item::{InputItemRefPair, INPUT_ITEM_URI_NOP}, + util_input_item::{INPUT_ITEM_URI_NOP}, util_xml::{parse_node, XmlParser, get_required_attrib}, InputContext, PlaylistFormat, }; @@ -40,7 +31,7 @@ impl PlaylistFormat for FormatXSPF { fn parse<'a>( &'a mut self, - mut input: &'a mut InputContext<'a>, + input: &'a mut InputContext<'a>, input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Reading XSPF metafile"); @@ -100,7 +91,7 @@ fn parse_extension_node( fn parse_tracklist_node( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], + _attributes: &[OwnedAttribute], ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { @@ -114,7 +105,7 @@ fn parse_tracklist_node( fn parse_track_node( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], + _attributes: &[OwnedAttribute], ii_node: &mut InputItemNode, _input_item: &mut InputItem, ) -> Result<()> { @@ -178,7 +169,7 @@ fn parse_vlcnode_node( ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { - let title = get_required_attrib(parser.logger, attributes, "title")?; + let _title = get_required_attrib(parser.logger, attributes, "title")?; /* input_item_NewExt( "vlc://nop", psz_title, (0LL), ITEM_TYPE_DIRECTORY, ITEM_NET_UNKNOWN @@ -218,7 +209,7 @@ fn parse_vlcnode_node( fn parse_vlcid( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], + _attributes: &[OwnedAttribute], _ii_node: &mut InputItemNode, _input_item: &mut InputItem, ) -> Result<()> { @@ -231,7 +222,7 @@ fn parse_vlcid( fn parse_extitem_node( parser: &mut XmlParser, - name: &str, + _name: &str, attributes: &[OwnedAttribute], ii_node: &mut InputItemNode, _input_item: &mut InputItem, @@ -254,8 +245,8 @@ fn parse_extitem_node( fn set_option( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], - ii_node: &mut InputItemNode, + _attributes: &[OwnedAttribute], + _ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { let value = parser.parse_text_node(name)?; @@ -272,7 +263,7 @@ fn set_option( fn parse_location( parser: &mut XmlParser, name: &str, - attributes: &[OwnedAttribute], + _attributes: &[OwnedAttribute], _ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { @@ -293,7 +284,7 @@ fn set_item_info( parser: &mut XmlParser, name: &str, _attributes: &[OwnedAttribute], - ii_node: &mut InputItemNode, + _ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { let value = parser.parse_text_node(name)?; diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index ce08eb9357da..d4e6146c5c86 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,24 +1,20 @@ //! Playlist Demux module use std::{ - io::Read, - path::{self, Path}, - rc::Rc, - result, + path::{Path}, }; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut, EsOutBaked}, - input_item::{self, InputItem, InputItemNode}, + es_out::{EsOut}, + input_item::{InputItem, InputItemNode}, messages::Logger, module::{ demux::{DemuxModule, Module, ThisDemux}, ModuleArgs, }, - stream::{impl_stream::BufPeek, DemuxControl, ReadDirDemux, Stream}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::Tick, - url::Url, }; use vlcrs_core_macros::module; @@ -78,10 +74,10 @@ impl Module for Playlist { fn open<'a>( mut this_demux: ThisDemux<'a>, source: &'a mut Stream, - es_out: &'a mut EsOut, + _es_out: &'a mut EsOut, logger: &'a mut Logger, input_item: Option<&'a mut InputItem>, - args: &mut ModuleArgs, + _args: &mut ModuleArgs, ) -> Result<DemuxModule<'a>> { debug!(logger, "Entering playlist-rs open"); let mut formats: Vec<Box<dyn PlaylistFormat>> = vec![ @@ -169,7 +165,7 @@ impl InputContext<'_> { } } - pub fn has_mime_type(&self, mime_type: &str) -> bool { + pub fn has_mime_type(&self, _mime_type: &str) -> bool { // TODO: Check mimetype return false; } diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 73c95762bf25..3ca414fa6e16 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -1,7 +1,7 @@ use std::{str, collections::HashSet}; use vlcrs_core::error::{CoreError, Result}; -use crate::InputContext; + pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { if buf_i < buf.len() { @@ -107,7 +107,8 @@ pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { .map_err(|_| CoreError::Unknown); } -pub fn ensure_utf8(buf: &mut [u8], buf_i: &mut usize) { +#[allow(unused)] +pub fn ensure_utf8(_buf: &mut [u8], _buf_i: &mut usize) { } @@ -157,9 +158,78 @@ mod tests { fn test_skip_chars() -> Result<()>{ let buf = b"test buffer line \n \t $%#Q$ \n"; let mut buf_i = 0; - skip_chars(buf, &mut buf_i, b" tste"); + skip_chars(buf, &mut buf_i, b" tsteB234"); assert_eq!(buf_i, 5); Ok(()) } + #[test] + fn test_find_in_buf() -> Result<()>{ + let buf = b"test buffer line \n \t $%#Q$ \n"; + + assert_eq!(find_in_buf(buf, 0, b"test"), Some(0)); + assert_eq!(find_in_buf(buf, 0, b"teste"), None); + assert_eq!(find_in_buf(buf, 1, b"test"), None); + assert_eq!(find_in_buf(buf, 100, b"test"), None); + assert_eq!(find_in_buf(buf, 5, b"buf"), Some(5)); + assert_eq!(find_in_buf(buf, 0, b"buf"), Some(5)); + assert_eq!(find_in_buf(buf, 0, b"buF"), None); + + Ok(()) + } + + #[test] + fn test_find_in_buf_ignore_case() -> Result<()>{ + let buf = b"test buffer line \n \t $%#Q$ \n"; + + assert_eq!(find_in_buf_ignore_case(buf, 0, b"test"), Some(0)); + assert_eq!(find_in_buf_ignore_case(buf, 0, b"teste"), None); + assert_eq!(find_in_buf_ignore_case(buf, 1, b"test"), None); + assert_eq!(find_in_buf_ignore_case(buf, 100, b"test"), None); + assert_eq!(find_in_buf_ignore_case(buf, 5, b"buf"), Some(5)); + assert_eq!(find_in_buf_ignore_case(buf, 0, b"buf"), Some(5)); + assert_eq!(find_in_buf_ignore_case(buf, 0, b"buF"), Some(5)); + + Ok(()) + } + + #[test] + fn test_starts_with() -> Result<()>{ + let buf = b"test buffer line \n \t $%#Q$ \n"; + + assert_eq!(starts_with(buf, 0, "buffer"), false); + assert_eq!(starts_with(buf, 0, "test"), true); + assert_eq!(starts_with(buf, 5, "buffer"), true); + assert_eq!(starts_with(buf, 5, "test"), false); + assert_eq!(starts_with(buf, 500, "test"), false); + assert_eq!(starts_with(buf, 0, "tesT"), false); + + Ok(()) + } + + + #[test] + fn test_starts_with_ignore_case() -> Result<()>{ + let buf = b"test buffer line \n \t $%#Q$ \n"; + + assert_eq!(starts_with_ignore_case(buf, 0, "buffer"), false); + assert_eq!(starts_with_ignore_case(buf, 0, "test"), true); + assert_eq!(starts_with_ignore_case(buf, 5, "buffer"), true); + assert_eq!(starts_with_ignore_case(buf, 5, "test"), false); + assert_eq!(starts_with_ignore_case(buf, 500, "test"), false); + assert_eq!(starts_with_ignore_case(buf, 0, "tesT"), true); + + Ok(()) + } + + #[test] + fn test_read_float() -> Result<()>{ + assert_eq!(read_float(b" 98993489 ", &mut 0)?, 98993489 as f32); + assert_eq!(read_float(b"3.14", &mut 0)?, 3.14 as f32); + assert_eq!(read_float(b" 3.14 ", &mut 0)?, 3.14 as f32); + assert!(read_float(b" sdf3.14 ", &mut 0).is_err()); + + Ok(()) + } + } \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/util_input_item.rs b/modules/demux/playlist-rs/src/util_input_item.rs index 987191f8757e..69bd71daab44 100644 --- a/modules/demux/playlist-rs/src/util_input_item.rs +++ b/modules/demux/playlist-rs/src/util_input_item.rs @@ -1,8 +1,4 @@ -use vlcrs_core::{ - tick::Tick, input_item::{InputItemNode, InputItem}, -}; - -pub(crate) type InputItemRefPair<'a> = (&'a mut InputItemNode, &'a mut Option<&'a mut InputItem>); +use vlcrs_core::tick::Tick; pub const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; pub struct InputItemMeta<'a> { diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 1233ebbb67b5..62577ce4ccb6 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -305,4 +305,4 @@ macro_rules! handle { pub(crate) use handle; pub(crate) use parse_node; -use crate::InputContext; + -- GitLab From ceec0eb324f8ae80c2443efa8b20116ff2d0f050 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 4 Aug 2023 16:33:10 -0600 Subject: [PATCH 15/24] mock logger --- modules/demux/playlist-rs/src/fmt_m3u.rs | 31 ++++++++- modules/demux/playlist-rs/src/fmt_sgimb.rs | 2 + modules/demux/playlist-rs/src/fmt_xspf.rs | 3 +- modules/demux/playlist-rs/src/lib.rs | 1 - modules/demux/playlist-rs/src/util_buf.rs | 64 ++++++++++++------- .../demux/playlist-rs/src/util_input_item.rs | 32 ---------- modules/demux/playlist-rs/src/util_xml.rs | 35 ++++++---- modules/vlcrs-core/src/messages.rs | 12 ++++ 8 files changed, 111 insertions(+), 69 deletions(-) delete mode 100644 modules/demux/playlist-rs/src/util_input_item.rs diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 0054433d9d9f..e7a3163b3c14 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -14,10 +14,39 @@ use vlcrs_core::{ use crate::{ util_buf::{find_in_buf, read_float, read_to_end, skip_chars, starts_with_ignore_case}, - util_input_item::InputItemMeta, InputContext, PlaylistFormat, }; +pub struct InputItemMeta<'a> { + pub duration: Tick, + pub artist: Option<&'a str>, + pub name: Option<&'a str>, + pub group: Option<&'a str>, + pub group_title: Option<&'a str>, + pub album_art: Option<&'a str>, + pub language: Option<&'a str>, + pub tvgid: Option<&'a str>, + pub options: Vec<&'a str>, + pub children: Vec<InputItemMeta<'a>> +} + +impl<'a> Default for InputItemMeta<'a> { + fn default() -> InputItemMeta<'a> { + InputItemMeta { + duration: Tick::MAX, + artist: None, + name: None, + group: None, + group_title: None, + album_art: None, + language: None, + tvgid: None, + options: Vec::new(), + children: Vec::new() + } + } +} + pub struct FormatM3U; impl PlaylistFormat for FormatM3U { diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index cdca6944eca4..48e0f6f4fb03 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -145,6 +145,8 @@ impl PlaylistFormat for FormatSGIMB { } impl FormatSGIMB { + + #[allow(unused_assignments)] fn parse_line( &mut self, line: &[u8], diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index bb1a524e0073..b1162b6e751f 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -10,11 +10,12 @@ use vlcrs_core::{ use xml::{attribute::OwnedAttribute}; use crate::{ - util_input_item::{INPUT_ITEM_URI_NOP}, util_xml::{parse_node, XmlParser, get_required_attrib}, InputContext, PlaylistFormat, }; +const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; + pub struct FormatXSPF; impl PlaylistFormat for FormatXSPF { diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index d4e6146c5c86..363f082e27f4 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -39,7 +39,6 @@ mod fmt_wms; mod fmt_wpl; mod fmt_xspf; mod util_buf; -mod util_input_item; mod util_xml; struct InputContext<'a> { diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 3ca414fa6e16..5b696aa44839 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -1,8 +1,6 @@ -use std::{str, collections::HashSet}; +use std::{collections::HashSet, str}; use vlcrs_core::error::{CoreError, Result}; - - pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { if buf_i < buf.len() { Ok(str::from_utf8(&buf[buf_i..]).map_err(|_| CoreError::Unknown)?) @@ -108,22 +106,21 @@ pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { } #[allow(unused)] -pub fn ensure_utf8(_buf: &mut [u8], _buf_i: &mut usize) { - -} +pub fn ensure_utf8(_buf: &mut [u8], _buf_i: &mut usize) {} macro_rules! match_ignore_case { ($buf:expr, $buf_i:expr, { $($start_str:expr => $handler:tt),* - $(,)? + $(,)? }) => { use crate::util_buf::starts_with_ignore_case; $( - if starts_with_ignore_case($buf, $buf_i, $start_str) { + if starts_with_ignore_case($buf, $buf_i, $start_str) + { $buf_i = $buf_i + $start_str.len(); $handler; - } else + } else )* { } @@ -150,12 +147,12 @@ mod tests { let result = read_to_end(b"", 0)?; assert_eq!(result, ""); - + Ok(()) } #[test] - fn test_skip_chars() -> Result<()>{ + fn test_skip_chars() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; let mut buf_i = 0; skip_chars(buf, &mut buf_i, b" tsteB234"); @@ -164,7 +161,7 @@ mod tests { } #[test] - fn test_find_in_buf() -> Result<()>{ + fn test_find_in_buf() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; assert_eq!(find_in_buf(buf, 0, b"test"), Some(0)); @@ -174,12 +171,12 @@ mod tests { assert_eq!(find_in_buf(buf, 5, b"buf"), Some(5)); assert_eq!(find_in_buf(buf, 0, b"buf"), Some(5)); assert_eq!(find_in_buf(buf, 0, b"buF"), None); - + Ok(()) } #[test] - fn test_find_in_buf_ignore_case() -> Result<()>{ + fn test_find_in_buf_ignore_case() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; assert_eq!(find_in_buf_ignore_case(buf, 0, b"test"), Some(0)); @@ -189,12 +186,12 @@ mod tests { assert_eq!(find_in_buf_ignore_case(buf, 5, b"buf"), Some(5)); assert_eq!(find_in_buf_ignore_case(buf, 0, b"buf"), Some(5)); assert_eq!(find_in_buf_ignore_case(buf, 0, b"buF"), Some(5)); - + Ok(()) } #[test] - fn test_starts_with() -> Result<()>{ + fn test_starts_with() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; assert_eq!(starts_with(buf, 0, "buffer"), false); @@ -203,13 +200,12 @@ mod tests { assert_eq!(starts_with(buf, 5, "test"), false); assert_eq!(starts_with(buf, 500, "test"), false); assert_eq!(starts_with(buf, 0, "tesT"), false); - + Ok(()) } - #[test] - fn test_starts_with_ignore_case() -> Result<()>{ + fn test_starts_with_ignore_case() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; assert_eq!(starts_with_ignore_case(buf, 0, "buffer"), false); @@ -218,18 +214,40 @@ mod tests { assert_eq!(starts_with_ignore_case(buf, 5, "test"), false); assert_eq!(starts_with_ignore_case(buf, 500, "test"), false); assert_eq!(starts_with_ignore_case(buf, 0, "tesT"), true); - + Ok(()) } #[test] - fn test_read_float() -> Result<()>{ + fn test_read_float() -> Result<()> { assert_eq!(read_float(b" 98993489 ", &mut 0)?, 98993489 as f32); assert_eq!(read_float(b"3.14", &mut 0)?, 3.14 as f32); assert_eq!(read_float(b" 3.14 ", &mut 0)?, 3.14 as f32); + assert_eq!(read_float(b" 3.14df ", &mut 0)?, 3.14 as f32); assert!(read_float(b" sdf3.14 ", &mut 0).is_err()); - + Ok(()) } -} \ No newline at end of file + #[test] + fn test_read_int() -> Result<()> { + assert_eq!(read_int(b" 98993489 ", &mut 0)?, 98993489); + assert_eq!(read_int(b"1", &mut 0)?, 1); + assert!(read_int(b"", &mut 0).is_err()); + + Ok(()) + } + + #[test] + #[allow(unused_assignments)] + fn test_match_ignore_case() -> Result<()> { + let buf = b"test"; + let mut buf_i = 0; + match_ignore_case!(buf, buf_i, { + "test" => { assert!(true) }, + "l" => { assert!(false) } + }); + + Ok(()) + } +} diff --git a/modules/demux/playlist-rs/src/util_input_item.rs b/modules/demux/playlist-rs/src/util_input_item.rs deleted file mode 100644 index 69bd71daab44..000000000000 --- a/modules/demux/playlist-rs/src/util_input_item.rs +++ /dev/null @@ -1,32 +0,0 @@ -use vlcrs_core::tick::Tick; - -pub const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; -pub struct InputItemMeta<'a> { - pub duration: Tick, - pub artist: Option<&'a str>, - pub name: Option<&'a str>, - pub group: Option<&'a str>, - pub group_title: Option<&'a str>, - pub album_art: Option<&'a str>, - pub language: Option<&'a str>, - pub tvgid: Option<&'a str>, - pub options: Vec<&'a str>, - pub children: Vec<InputItemMeta<'a>> -} - -impl<'a> Default for InputItemMeta<'a> { - fn default() -> InputItemMeta<'a> { - InputItemMeta { - duration: Tick::MAX, - artist: None, - name: None, - group: None, - group_title: None, - album_art: None, - language: None, - tvgid: None, - options: Vec::new(), - children: Vec::new() - } - } -} \ No newline at end of file diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 62577ce4ccb6..7e87cfbd6426 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -1,12 +1,7 @@ use std::path::Path; use vlcrs_core::{ - error::Result, - input_item::InputItem, - messages::Logger, - stream::Stream, - url::Url, - warn, + error::Result, input_item::InputItem, messages::Logger, stream::Stream, url::Url, warn, }; use xml::{ @@ -158,8 +153,7 @@ pub fn process_mrl(media_path: &str, base_path: Option<&str>) -> Result<String> return Ok(path.to_str().ok_or(CoreError::Unknown)?.to_owned()); } - let mut base_path = - Path::new(base_path.ok_or(CoreError::Unknown)?).to_path_buf(); + let mut base_path = Path::new(base_path.ok_or(CoreError::Unknown)?).to_path_buf(); base_path.pop(); return Ok(base_path .join(path) @@ -281,21 +275,21 @@ macro_rules! handle { }); }; // Custom code with brackets - ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, { [$attrib_var:ident] $code:tt } ) => { let $attrib_var = $attrib; $code; }; // Custom code with brackets - ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, { [$attrib_var:ident, $value_var:ident] $code:tt } ) => { let $attrib_var = $attrib; let $value_var = $parser.parse_text_node($node)?; $code; }; - ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, + ($parser:expr, $node:expr, $attrib:expr, $ii_node:expr, $ii:expr, $handler:tt) => { // Evaluate inline $handler; @@ -305,4 +299,23 @@ macro_rules! handle { pub(crate) use handle; pub(crate) use parse_node; +#[cfg(test)] +mod tests { + use xml::name::{Name, OwnedName}; + use super::*; + + #[test] + fn test_get_required_attrib() -> Result<()> { + let attribs = [ + OwnedAttribute::new(OwnedName::from(Name::from("arg1")), "val1"), + OwnedAttribute::new(OwnedName::from(Name::from("arg2")), "val2"), + OwnedAttribute::new(OwnedName::from(Name::from("arg3")), "val3"), + ]; + // let mut logger = Logger::mock(); + + // assert_eq!(get_required_attrib(&mut logger, &attribs, "arg2")?, "val2"); + + Ok(()) + } +} diff --git a/modules/vlcrs-core/src/messages.rs b/modules/vlcrs-core/src/messages.rs index 8f4fe02b476c..3fd82fd6acfe 100644 --- a/modules/vlcrs-core/src/messages.rs +++ b/modules/vlcrs-core/src/messages.rs @@ -11,6 +11,7 @@ pub use vlcrs_core_sys::vlc_log_type as LogType; #[repr(transparent)] pub struct Logger(pub(crate) NonNull<vlc_logger>); +// #[cfg(not(test))] impl Logger { /// Log message to the logger pub fn log(&mut self, priority: LogType, file: &CStr, func: &CStr, line: u32, msg: &str) { @@ -41,6 +42,17 @@ impl Logger { } } +// #[cfg(test)] +// impl Logger { +// pub fn mock() -> Logger { +// Logger(NonNull::<vlc_logger>::dangling()) +// } +// /// Log message to the logger +// pub fn log(&mut self, priority: LogType, _file: &CStr, _func: &CStr, _line: u32, msg: &str) { +// println!("{:?}: {}", priority, msg); +// } +// } + /// Log for a VLC Object #[macro_export] macro_rules! log { -- GitLab From 0829004a601dfbc253eac535a05afe7c93c262a0 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Fri, 4 Aug 2023 16:58:53 -0600 Subject: [PATCH 16/24] added mocked feature --- modules/demux/playlist-rs/Cargo.toml | 3 +++ modules/demux/playlist-rs/src/util_xml.rs | 30 ++++++++++++++++++++--- modules/vlcrs-core/Cargo.toml | 3 +++ modules/vlcrs-core/src/messages.rs | 22 ++++++++--------- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/modules/demux/playlist-rs/Cargo.toml b/modules/demux/playlist-rs/Cargo.toml index 8d6645b34bbd..4aa53b5c5237 100644 --- a/modules/demux/playlist-rs/Cargo.toml +++ b/modules/demux/playlist-rs/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" license = "LGPL-2.1-or-later" edition = "2021" +[features] +mocked = ["vlcrs-core/mocked"] + [dependencies] vlcrs-core = { workspace = true } vlcrs-core-macros = { workspace = true } diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 7e87cfbd6426..9064fe5a617f 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -300,22 +300,44 @@ pub(crate) use handle; pub(crate) use parse_node; #[cfg(test)] +// #[cfg(feature = "mocked")] mod tests { use xml::name::{Name, OwnedName}; - + use super::*; - + #[test] fn test_get_required_attrib() -> Result<()> { + let mut logger = Logger::mock(); let attribs = [ OwnedAttribute::new(OwnedName::from(Name::from("arg1")), "val1"), OwnedAttribute::new(OwnedName::from(Name::from("arg2")), "val2"), OwnedAttribute::new(OwnedName::from(Name::from("arg3")), "val3"), ]; - // let mut logger = Logger::mock(); - // assert_eq!(get_required_attrib(&mut logger, &attribs, "arg2")?, "val2"); + assert_eq!(get_required_attrib(&mut logger, &attribs, "arg2")?, "val2"); + assert!(get_required_attrib(&mut logger, &attribs, "nonexistent").is_err()); + + Ok(()) + } + + #[test] + fn test_find_next_element_from_buf() -> Result<()> { + let mut logger: Logger = Logger::mock(); + let buf = b" + + <parent> + <child></child> + </parent> + "; + + assert_eq!(find_next_element_from_buf(buf, &mut logger, "parent")?, true); + assert_eq!(find_next_element_from_buf(buf, &mut logger, "child")?, false); + assert_eq!(find_next_element_from_buf(buf, &mut logger, "")?, false); + assert_eq!(find_next_element_from_buf(buf, &mut logger, "aasdas")?, false); Ok(()) } + + } diff --git a/modules/vlcrs-core/Cargo.toml b/modules/vlcrs-core/Cargo.toml index 5d880bc0439c..f5035c759815 100644 --- a/modules/vlcrs-core/Cargo.toml +++ b/modules/vlcrs-core/Cargo.toml @@ -3,6 +3,9 @@ name = "vlcrs-core" version = "0.1.0" edition = "2021" +[features] +mocked = [] + [dependencies] vlcrs-core-sys = { path = "sys/" } libc = "0.2" diff --git a/modules/vlcrs-core/src/messages.rs b/modules/vlcrs-core/src/messages.rs index 3fd82fd6acfe..59f9f982f8f1 100644 --- a/modules/vlcrs-core/src/messages.rs +++ b/modules/vlcrs-core/src/messages.rs @@ -11,7 +11,7 @@ pub use vlcrs_core_sys::vlc_log_type as LogType; #[repr(transparent)] pub struct Logger(pub(crate) NonNull<vlc_logger>); -// #[cfg(not(test))] +#[cfg(not(feature = "mocked"))] impl Logger { /// Log message to the logger pub fn log(&mut self, priority: LogType, file: &CStr, func: &CStr, line: u32, msg: &str) { @@ -42,16 +42,16 @@ impl Logger { } } -// #[cfg(test)] -// impl Logger { -// pub fn mock() -> Logger { -// Logger(NonNull::<vlc_logger>::dangling()) -// } -// /// Log message to the logger -// pub fn log(&mut self, priority: LogType, _file: &CStr, _func: &CStr, _line: u32, msg: &str) { -// println!("{:?}: {}", priority, msg); -// } -// } +#[cfg(feature = "mocked")] +impl Logger { + pub fn mock() -> Logger { + Logger(NonNull::<vlc_logger>::dangling()) + } + /// Log message to the logger + pub fn log(&mut self, priority: LogType, _file: &CStr, _func: &CStr, _line: u32, msg: &str) { + println!("{:?}: {}", priority, msg); + } +} /// Log for a VLC Object #[macro_export] -- GitLab From d940ded2387b7042a79fa648ea04786bd5024647 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Mon, 14 Aug 2023 23:12:24 -0600 Subject: [PATCH 17/24] cargo fmt --- modules/demux/playlist-rs/src/fmt_asx.rs | 32 ++++++------ modules/demux/playlist-rs/src/fmt_bdmv.rs | 19 +++----- modules/demux/playlist-rs/src/fmt_ifo.rs | 51 ++++++++++---------- modules/demux/playlist-rs/src/fmt_itml.rs | 46 ++++++++++-------- modules/demux/playlist-rs/src/fmt_m3u.rs | 6 +-- modules/demux/playlist-rs/src/fmt_pls.rs | 46 ++++++++---------- modules/demux/playlist-rs/src/fmt_podcast.rs | 29 +++++------ modules/demux/playlist-rs/src/fmt_qtl.rs | 16 ++---- modules/demux/playlist-rs/src/fmt_ram.rs | 3 +- modules/demux/playlist-rs/src/fmt_sgimb.rs | 1 - modules/demux/playlist-rs/src/fmt_wms.rs | 13 ++--- modules/demux/playlist-rs/src/fmt_xspf.rs | 6 +-- modules/demux/playlist-rs/src/lib.rs | 10 ++-- modules/demux/playlist-rs/src/util_xml.rs | 28 ++++++++--- 14 files changed, 147 insertions(+), 159 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 084ab2f9ed25..cf1fc40395a0 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -1,16 +1,15 @@ /// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc - - use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItemNode, InputItem}, - warn, tick::Tick, + input_item::{InputItem, InputItemNode}, + tick::Tick, + warn, }; use crate::{ util_buf::starts_with_ignore_case, - util_xml::{parse_node, XmlParser, get_required_attrib, process_mrl}, + util_xml::{get_required_attrib, parse_node, process_mrl, XmlParser}, InputContext, PlaylistFormat, }; @@ -19,11 +18,10 @@ pub struct FormatASX; impl PlaylistFormat for FormatASX { fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is ASX"); - + //TODO: p_this->force ??? - if input.has_extension(".asx") - || input.has_extension(".wax") - || input.has_extension(".wvx") { + if input.has_extension(".asx") || input.has_extension(".wax") || input.has_extension(".wvx") + { return Ok(true); } else if input.has_mime_type("video/x-ms-asf") || input.has_mime_type("audio/x-ms-wax") { let peek = input.source.peek(12)?; @@ -62,7 +60,7 @@ impl PlaylistFormat for FormatASX { "Title" => { [_a, value] { root_input_item.set_title(Some(&value)); } }, "Author" => { [_a, value] { root_input_item.set_artist(Some(&value)); } }, "Copyright" => { [_a, value] { root_input_item.set_copyright(Some(&value)); } }, - "Moreinfo" => { [attr, value] { + "Moreinfo" => { [attr, value] { root_input_item.set_url( Some(get_required_attrib(xml_parser.logger, attr, "href").unwrap_or_else(|_|&value)) ); @@ -81,16 +79,14 @@ impl PlaylistFormat for FormatASX { Ok(()) } - } fn parse_entry( parser: &mut XmlParser, ii_node: &mut InputItemNode, root_input_item: &mut InputItem, - base: &mut Option<String> + base: &mut Option<String>, ) -> Result<()> { - let mut title = None; let mut artist = None; let mut copyright = None; @@ -105,7 +101,7 @@ fn parse_entry( "TITLE" => { [_attr, value] { title = Some(value); } }, "AUTHOR" => { [_attr, value] { artist = Some(value); } }, "COPYRIGHT" => { [_attr, value] { copyright = Some(value); } }, - "MOREINFO" => { [attr, value] { + "MOREINFO" => { [attr, value] { more_info = Some(get_required_attrib(parser.logger, attr, "href") .map(str::to_string) .unwrap_or_else(|_|value) @@ -133,14 +129,14 @@ fn parse_entry( } let href = get_required_attrib(parser.logger, attrib, "href")?; // Bug in C code - + let name = format!("{}. {}", num_entry, title.as_deref().unwrap_or("")); let mrl = process_mrl(href, base.as_deref())?; let mut input_item = InputItem::new(&mrl, &name)?; if let Some(start) = start { if let Err(_) = input_item.add_option( - &format!(":start-time={}", start.to_seconds()), + &format!(":start-time={}", start.to_seconds()), vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32 ) { warn!(parser.logger, "Failed to add start time option"); @@ -149,7 +145,7 @@ fn parse_entry( if let Some(duration) = duration { input_item.set_duration(duration); if let Err(_) = input_item.add_option( - &format!(":stop-time={}", (start.unwrap_or(Tick::ZERO) + duration).to_seconds()), + &format!(":stop-time={}", (start.unwrap_or(Tick::ZERO) + duration).to_seconds()), vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32 ) { warn!(parser.logger, "Failed to add stop time option"); @@ -171,4 +167,4 @@ fn parse_entry( fn parse_time(_s: &str) -> Result<Tick> { // TODO: parse_time not implemented todo!("parse_time not implemented") -} \ No newline at end of file +} diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index 144f6d287b3d..319d15c2e115 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -1,23 +1,18 @@ /// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc - - use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItemNode, InputItem}, + input_item::{InputItem, InputItemNode}, }; -use crate::{ - util_buf::starts_with_ignore_case, - InputContext, PlaylistFormat, -}; +use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; pub struct FormatBDMV; impl PlaylistFormat for FormatBDMV { fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is BDMV"); - + //TODO: p_this->force ??? // INDEX.BDMV is the only valid filename. if !input.has_extension("index.bdmv") { @@ -40,13 +35,15 @@ impl PlaylistFormat for FormatBDMV { debug!(input.logger, "Reading BDMV"); let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let name = &path[..path.len()-15]; + let name = &path[..path.len() - 15]; let mut input_item = InputItem::new(name, name)?; // input_item_AddOption( p_input, "demux=bluray", VLC_INPUT_OPTION_TRUSTED ); - input_item.add_option("demux=bluray", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32)?; + input_item.add_option( + "demux=bluray", + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, + )?; input_item_node.append_item(&input_item)?; Ok(()) } - } diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index 92777112c6e5..16b2aa977af2 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -1,25 +1,19 @@ /// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc - - use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItemNode, InputItem}, + input_item::{InputItem, InputItemNode}, }; - -use crate::{ - util_buf::starts_with_ignore_case, - InputContext, PlaylistFormat, -}; +use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; pub enum IFOKind { Dvd, - DvdVr + DvdVr, } pub struct FormatIFO { - pub kind: Option<IFOKind> + pub kind: Option<IFOKind>, } impl PlaylistFormat for FormatIFO { @@ -29,14 +23,14 @@ impl PlaylistFormat for FormatIFO { if !input.has_extension(".IFO") { return Ok(false); } - + let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let file = &path[path.len()-15..]; + let file = &path[path.len() - 15..]; if file[..8].eq_ignore_ascii_case("VIDEO_TS") || file[..4].eq_ignore_ascii_case("VTS_") { /* Valid filenames are : - * - VIDEO_TS.IFO - * - VTS_XX_X.IFO where X are digits - */ + * - VIDEO_TS.IFO + * - VTS_XX_X.IFO where X are digits + */ self.kind = Some(IFOKind::Dvd); } else if file[..8].eq_ignore_ascii_case("VR_MANGR") { /* Valid filename for DVD-VR is VR_MANGR.IFO */ @@ -47,11 +41,15 @@ impl PlaylistFormat for FormatIFO { let peek = input.source.peek(12)?; let buf = peek.buf(); - return Ok(starts_with_ignore_case(buf, 0, match self.kind { - Some(IFOKind::Dvd) => "DVDVIDEO", - Some(IFOKind::DvdVr) => "DVD_RTR_", - None => return Ok(false), - })); + return Ok(starts_with_ignore_case( + buf, + 0, + match self.kind { + Some(IFOKind::Dvd) => "DVDVIDEO", + Some(IFOKind::DvdVr) => "DVD_RTR_", + None => return Ok(false), + }, + )); } fn parse<'a>( @@ -62,23 +60,24 @@ impl PlaylistFormat for FormatIFO { debug!(input.logger, "Reading IFO"); let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let name = &path[..path.len()-12]; + let name = &path[..path.len() - 12]; match self.kind { Some(IFOKind::Dvd) => { let mut input_item = InputItem::new(name, name)?; - input_item.add_option("demux=dvd", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32)?; + input_item.add_option( + "demux=dvd", + vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, + )?; input_item_node.append_item(&input_item)?; - }, + } Some(IFOKind::DvdVr) => { let item_name = String::from(name) + "VR_MOVIE.VRO"; let input_item = InputItem::new(&item_name, &item_name)?; input_item_node.append_item(&input_item)?; - }, + } None => return Err(CoreError::Unknown), } - Ok(()) } - } diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index 1063bff03131..14161ce17b7b 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -26,16 +26,14 @@ </array> </dict> */ - use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - tick::{Tick, Microseconds}, + tick::{Microseconds, Tick}, warn, }; - use crate::{ util_buf::starts_with_ignore_case, util_xml::{parse_node, XmlParser}, @@ -85,12 +83,15 @@ impl PlaylistFormat for FormatITML { for attrib in attribs { match attrib.name.local_name.as_str() { "version" => version = Some(attrib.value), - _ => warn!(xml_parser.logger, "invalid <plist> attribute:\"{}\"", attrib.name.local_name) + _ => warn!( + xml_parser.logger, + "invalid <plist> attribute:\"{}\"", attrib.name.local_name + ), } } if version.is_none() { - warn!(xml_parser.logger, "<plist> requires \"version\" attribute" ); + warn!(xml_parser.logger, "<plist> requires \"version\" attribute"); } parse_node!(&mut xml_parser, "plist", &mut input_item_node, &mut root_input_item, { @@ -112,7 +113,7 @@ impl PlaylistFormat for FormatITML { if let Some(name) = &key_name { set_input_value(&mut input_item, name, &value)?; } }}, }); - + if input_item.uri().is_some() { input_item_node.append_item(&input_item)?; } else { @@ -126,23 +127,30 @@ impl PlaylistFormat for FormatITML { Ok(()) } - } fn set_input_value(input_item: &mut InputItem, name: &str, value: &str) -> Result<()> { match name { - "Name" => { input_item.set_title(Some(value)); }, - "Artist" => { input_item.set_artist(Some(value)); }, - "Album" => { input_item.set_album(Some(value)); }, - "Genre" => { input_item.set_genre(Some(value)); }, - "Track Number" => { input_item.set_track_number(Some(value)) }, - "Location" => { input_item.set_uri(Some(value)); }, - "Total Time" => { input_item.set_duration( - Tick::from_microseconds(Microseconds::from( - value.parse::<i64>().map_err(|_|CoreError::Unknown)? - )) - ) }, + "Name" => { + input_item.set_title(Some(value)); + } + "Artist" => { + input_item.set_artist(Some(value)); + } + "Album" => { + input_item.set_album(Some(value)); + } + "Genre" => { + input_item.set_genre(Some(value)); + } + "Track Number" => input_item.set_track_number(Some(value)), + "Location" => { + input_item.set_uri(Some(value)); + } + "Total Time" => input_item.set_duration(Tick::from_microseconds(Microseconds::from( + value.parse::<i64>().map_err(|_| CoreError::Unknown)?, + ))), _ => {} } Ok(()) -} \ No newline at end of file +} diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index e7a3163b3c14..8cb881160f08 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -1,8 +1,8 @@ //! Playlist_M3U Demux module use std::{ + io::{BufRead, Read}, str, - io::{BufRead, Read} }; use vlcrs_core::{ @@ -27,7 +27,7 @@ pub struct InputItemMeta<'a> { pub language: Option<&'a str>, pub tvgid: Option<&'a str>, pub options: Vec<&'a str>, - pub children: Vec<InputItemMeta<'a>> + pub children: Vec<InputItemMeta<'a>>, } impl<'a> Default for InputItemMeta<'a> { @@ -42,7 +42,7 @@ impl<'a> Default for InputItemMeta<'a> { language: None, tvgid: None, options: Vec::new(), - children: Vec::new() + children: Vec::new(), } } } diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index a081c15981e2..1defd4705017 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -6,15 +6,12 @@ use vlcrs_core::{ input_item::{InputItem, InputItemNode}, }; -use crate::{PlaylistFormat, InputContext}; +use crate::{InputContext, PlaylistFormat}; pub struct FormatPLS; impl PlaylistFormat for FormatPLS { - fn can_open( - &mut self, - input: &mut InputContext, - ) -> Result<bool> { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is PLS"); let peek = input.source.peek(1024)?; let buf = peek.buf(); @@ -24,60 +21,61 @@ impl PlaylistFormat for FormatPLS { } let header = std::str::from_utf8(&buf[..10])?; - + if !header.eq_ignore_ascii_case("[playlist]") && !input.has_extension(".pls") { debug!(input.logger, "Not a valid PLS playlist file"); return Err(CoreError::Unknown); } - + return Ok(true); } fn parse<'a>( &mut self, input: &mut InputContext, - input_item_node: &mut InputItemNode + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Read dir called"); - + // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - input.source + input + .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; - + let mut i_item: i32 = -1; let mut psz_mrl: Rc<String> = String::new().into(); let mut psz_name: Rc<String> = String::new().into(); - + for line in buf.lines() { debug!(input.logger, "Line: {line}"); - + if line == "[playlist]" { continue; } - + let (psz_key, psz_value) = match line.split_once('=') { Some(parts) => parts, None => continue, }; - + match psz_key { "version" => debug!(input.logger, "pls file version: {psz_value}"), "numberofentries" => debug!(input.logger, "pls should have {psz_value} entries"), _ => { let (_, i_new_item_str) = psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); - + if i_new_item_str.is_empty() { debug!(input.logger, "couldn't find item number in key"); continue; } - + let i_new_item = i_new_item_str .parse::<i32>() .map_err(|_| CoreError::Unknown)?; - + if i_item == -1 { i_item = i_new_item; } else if i_item != i_new_item { @@ -86,9 +84,9 @@ impl PlaylistFormat for FormatPLS { input_item_node.append_item(&input_item)?; i_item = i_new_item; } - + let key_name = &psz_key[..psz_key.len() - i_new_item_str.len()]; - + debug!(input.logger, "Key: [{key_name}] Item : {i_item}"); match key_name.to_ascii_lowercase().as_str() { "file" => { @@ -106,12 +104,12 @@ impl PlaylistFormat for FormatPLS { } }; } - + // Add last item if i_item != -1 { let input_item = InputItem::new(&psz_mrl, &psz_name)?; input_item_node.append_item(&input_item)?; - + debug!( input.logger, "Added last item {}, {}", @@ -119,9 +117,7 @@ impl PlaylistFormat for FormatPLS { psz_name.to_string() ); } - - Ok(()) + Ok(()) } - } diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index 17d24cb8155a..e9ca263d33a4 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -1,14 +1,11 @@ - - use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode, MetaType::*}, - tick::{Tick, Seconds}, + tick::{Seconds, Tick}, warn, }; - use crate::{ util_xml::{find_next_element_from_buf, parse_node, XmlParser}, InputContext, PlaylistFormat, @@ -65,7 +62,7 @@ impl PlaylistFormat for FormatPodcast { "item" => { [_attribute, value] { let mut input_item = InputItem::new_empty()?; - + macro_rules! add_info { ($info:expr, $field:expr) => { input_item.add_info_str("Podcast Info", $info, $field)? @@ -84,7 +81,7 @@ impl PlaylistFormat for FormatPodcast { "description" => { add_info!("Podcast Summary", &value, vlc_meta_Description); }, "pubDate" => { add_info!("Podcast Publication Date", &value, vlc_meta_Date); }, "itunes:category" => { add_info!("Podcast Subcategory", &value); }, - "itunes:duration" => { + "itunes:duration" => { add_info!("Podcast Duration", &value); root_input_item.set_duration(match parse_time(&value) { Ok(ticks) => ticks, @@ -129,24 +126,22 @@ impl PlaylistFormat for FormatPodcast { Ok(()) } - } fn parse_time(val: &str) -> Result<Tick> { - let [h, m, s]: [u32; 3] = match val.split(":").collect::<Vec<&str>>().as_slice() { [h, m, s] => [ - h.parse().map_err(|_|CoreError::Unknown)?, - m.parse().map_err(|_|CoreError::Unknown)?, - s.parse().map_err(|_|CoreError::Unknown)? + h.parse().map_err(|_| CoreError::Unknown)?, + m.parse().map_err(|_| CoreError::Unknown)?, + s.parse().map_err(|_| CoreError::Unknown)?, ], [h, m] => [ - h.parse().map_err(|_|CoreError::Unknown)?, - m.parse().map_err(|_|CoreError::Unknown)?, - 0 + h.parse().map_err(|_| CoreError::Unknown)?, + m.parse().map_err(|_| CoreError::Unknown)?, + 0, ], - _ => return Err(CoreError::Unknown) + _ => return Err(CoreError::Unknown), }; - Ok(Tick::from_seconds(Seconds::from(( h*60 + m )*60 + s ))) -} \ No newline at end of file + Ok(Tick::from_seconds(Seconds::from((h * 60 + m) * 60 + s))) +} diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 56b4757ce800..1694d2c4ff5c 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -1,4 +1,4 @@ -use std::{str}; +use std::str; use vlcrs_core::{ debug, error, @@ -6,8 +6,7 @@ use vlcrs_core::{ input_item::{InputItem, InputItemNode}, }; - -use crate::{PlaylistFormat, util_xml::{XmlParser}, InputContext}; +use crate::{util_xml::XmlParser, InputContext, PlaylistFormat}; const ROOT_NODE_MAX_DEPTH: u32 = 2; pub struct FormatQTL; @@ -29,10 +28,7 @@ enum QtlLoopType { } impl PlaylistFormat for FormatQTL { - fn can_open( - &mut self, - input: &mut InputContext - ) -> Result<bool> { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is QTL"); if !input.has_extension(".qtl") { @@ -47,7 +43,7 @@ impl PlaylistFormat for FormatQTL { fn parse<'a>( &mut self, input: &mut InputContext, - input_item_node: &mut InputItemNode + input_item_node: &mut InputItemNode, ) -> Result<()> { debug!(input.logger, "Starting QTL parser"); @@ -61,7 +57,7 @@ impl PlaylistFormat for FormatQTL { let _input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; - let attributes = xml_parser.find_next_element("embed", ROOT_NODE_MAX_DEPTH)?; + let attributes = xml_parser.find_next_element("embed", ROOT_NODE_MAX_DEPTH)?; let mut autoplay: bool = false; let mut controller: bool = false; @@ -172,5 +168,3 @@ impl PlaylistFormat for FormatQTL { &[".qtl"] } } - - diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index bb335c538a1b..38e655563040 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -6,10 +6,9 @@ use vlcrs_core::{ input_item::{InputItem, InputItemNode}, }; - use crate::{ util_buf::{read_int, read_to_end, skip_chars, starts_with}, - util_xml::{process_mrl}, + util_xml::process_mrl, InputContext, PlaylistFormat, }; diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index 48e0f6f4fb03..fadc27a67f92 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -145,7 +145,6 @@ impl PlaylistFormat for FormatSGIMB { } impl FormatSGIMB { - #[allow(unused_assignments)] fn parse_line( &mut self, diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 0ce696ef08ab..7c6450938607 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -1,4 +1,4 @@ -use std::{io::Read}; +use std::io::Read; use vlcrs_core::{ debug, @@ -7,16 +7,12 @@ use vlcrs_core::{ warn, }; -use crate::{util_buf::starts_with_ignore_case, PlaylistFormat, InputContext}; +use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; pub struct FormatWMS; - impl PlaylistFormat for FormatWMS { - fn can_open( - &mut self, - input: &mut InputContext - ) -> Result<bool> { + fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is WMS"); let peek = input.source.peek(1024)?; let buf = peek.buf(); @@ -39,7 +35,8 @@ impl PlaylistFormat for FormatWMS { // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - input.source + input + .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index b1162b6e751f..d2f4acc24bf4 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -1,5 +1,3 @@ - - use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -7,10 +5,10 @@ use vlcrs_core::{ tick::{Miliseconds, Tick}, warn, }; -use xml::{attribute::OwnedAttribute}; +use xml::attribute::OwnedAttribute; use crate::{ - util_xml::{parse_node, XmlParser, get_required_attrib}, + util_xml::{get_required_attrib, parse_node, XmlParser}, InputContext, PlaylistFormat, }; diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 363f082e27f4..9d62e113f117 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,12 +1,10 @@ //! Playlist Demux module -use std::{ - path::{Path}, -}; +use std::path::Path; use vlcrs_core::{ debug, error::{CoreError, Result}, - es_out::{EsOut}, + es_out::EsOut, input_item::{InputItem, InputItemNode}, messages::Logger, module::{ @@ -21,7 +19,7 @@ use vlcrs_core_macros::module; use crate::{ fmt_asx::FormatASX, fmt_bdmv::FormatBDMV, fmt_ifo::FormatIFO, fmt_itml::FormatITML, fmt_m3u::FormatM3U, fmt_pls::FormatPLS, fmt_podcast::FormatPodcast, fmt_qtl::FormatQTL, - fmt_sgimb::FormatSGIMB, fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, fmt_ram::FormatRAM, + fmt_ram::FormatRAM, fmt_sgimb::FormatSGIMB, fmt_wpl::FormatWPL, fmt_xspf::FormatXSPF, }; mod fmt_asx; @@ -93,7 +91,7 @@ impl Module for Playlist { Box::new(FormatASX {}), Box::new(FormatBDMV {}), Box::new(FormatIFO { kind: None }), - Box::new(FormatRAM { }), + Box::new(FormatRAM {}), ]; let mut high_prior = 0; diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 9064fe5a617f..a79399ca56c2 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -300,15 +300,14 @@ pub(crate) use handle; pub(crate) use parse_node; #[cfg(test)] -// #[cfg(feature = "mocked")] mod tests { use xml::name::{Name, OwnedName}; - + use super::*; - + #[test] fn test_get_required_attrib() -> Result<()> { - let mut logger = Logger::mock(); + let mut logger: Logger = Logger::mock(); let attribs = [ OwnedAttribute::new(OwnedName::from(Name::from("arg1")), "val1"), OwnedAttribute::new(OwnedName::from(Name::from("arg2")), "val2"), @@ -325,19 +324,32 @@ mod tests { fn test_find_next_element_from_buf() -> Result<()> { let mut logger: Logger = Logger::mock(); let buf = b" - <parent> <child></child> </parent> "; - assert_eq!(find_next_element_from_buf(buf, &mut logger, "parent")?, true); - assert_eq!(find_next_element_from_buf(buf, &mut logger, "child")?, false); + assert_eq!( + find_next_element_from_buf(buf, &mut logger, "parent")?, + true + ); + assert_eq!( + find_next_element_from_buf(buf, &mut logger, "child")?, + false + ); assert_eq!(find_next_element_from_buf(buf, &mut logger, "")?, false); - assert_eq!(find_next_element_from_buf(buf, &mut logger, "aasdas")?, false); + assert_eq!( + find_next_element_from_buf(buf, &mut logger, "nonexistent")?, + false + ); Ok(()) } + #[test] + fn test_process_mrl() -> Result<()> { + assert!(process_mrl("video.mp4", None).is_err()); + Ok(()) + } } -- GitLab From 58354b08ab47039cd73c9d767f1481cb9ff67bf0 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Tue, 15 Aug 2023 01:35:23 -0600 Subject: [PATCH 18/24] Fixed comments --- modules/demux/playlist-rs/Cargo.toml | 2 +- modules/demux/playlist-rs/src/fmt_asx.rs | 13 ++-- modules/demux/playlist-rs/src/fmt_b4s.rs | 8 +-- modules/demux/playlist-rs/src/fmt_bdmv.rs | 14 ++-- modules/demux/playlist-rs/src/fmt_ifo.rs | 37 +++++++---- modules/demux/playlist-rs/src/fmt_itml.rs | 61 +++++++++-------- modules/demux/playlist-rs/src/fmt_m3u.rs | 28 ++++---- modules/demux/playlist-rs/src/fmt_pls.rs | 24 +++---- modules/demux/playlist-rs/src/fmt_podcast.rs | 46 ++++++------- modules/demux/playlist-rs/src/fmt_qtl.rs | 20 ++++-- modules/demux/playlist-rs/src/fmt_ram.rs | 26 ++++---- modules/demux/playlist-rs/src/fmt_sgimb.rs | 12 ++-- modules/demux/playlist-rs/src/fmt_wms.rs | 4 +- modules/demux/playlist-rs/src/fmt_wpl.rs | 49 ++++++-------- modules/demux/playlist-rs/src/fmt_xspf.rs | 15 +++-- modules/demux/playlist-rs/src/lib.rs | 2 +- modules/demux/playlist-rs/src/util_buf.rs | 69 ++++++++------------ modules/demux/playlist-rs/src/util_xml.rs | 27 ++++---- modules/demux/playlist-rs/todo.md | 3 +- modules/vlcrs-core/Cargo.toml | 2 +- modules/vlcrs-core/src/input_item.rs | 20 ++---- modules/vlcrs-core/src/messages.rs | 4 +- modules/vlcrs-core/src/module/demux.rs | 7 -- 23 files changed, 243 insertions(+), 250 deletions(-) diff --git a/modules/demux/playlist-rs/Cargo.toml b/modules/demux/playlist-rs/Cargo.toml index 4aa53b5c5237..35a0d5aaf18a 100644 --- a/modules/demux/playlist-rs/Cargo.toml +++ b/modules/demux/playlist-rs/Cargo.toml @@ -5,7 +5,7 @@ license = "LGPL-2.1-or-later" edition = "2021" [features] -mocked = ["vlcrs-core/mocked"] +testing = ["vlcrs-core/testing"] [dependencies] vlcrs-core = { workspace = true } diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index cf1fc40395a0..076336dac2f1 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -1,4 +1,7 @@ -/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc +//! ASX format parser +//! +//! Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -69,7 +72,7 @@ impl PlaylistFormat for FormatASX { "Base" => { }, "Entryref" => { [attr, value] { let url = get_required_attrib(xml_parser.logger, attr, "href").unwrap_or_else(|_|&value); - let mut input_item = InputItem::new_empty()?; + let mut input_item = InputItem::empty()?; input_item.set_uri(Some(url)); input_item.set_name(root_input_item.name()); input_item_node.append_item(&input_item)?; @@ -111,8 +114,8 @@ fn parse_entry( "DURATION" => { [_attr, value] { duration = Some(parse_time(&value)?); } }, "STARTTIME" => { [_attr, value] { start = Some(parse_time(&value)?); } }, "REF" => { [attrib] { - /* Reference Node */ - /* All ref node will be converted into an entry */ + // Reference Node + // All ref node will be converted into an entry num_entry += 1; if title.is_none() { @@ -133,7 +136,7 @@ fn parse_entry( let name = format!("{}. {}", num_entry, title.as_deref().unwrap_or("")); let mrl = process_mrl(href, base.as_deref())?; - let mut input_item = InputItem::new(&mrl, &name)?; + let mut input_item = InputItem::new(mrl, name.clone())?; if let Some(start) = start { if let Err(_) = input_item.add_option( &format!(":start-time={}", start.to_seconds()), diff --git a/modules/demux/playlist-rs/src/fmt_b4s.rs b/modules/demux/playlist-rs/src/fmt_b4s.rs index 4fbd68804f63..11d01c146c60 100644 --- a/modules/demux/playlist-rs/src/fmt_b4s.rs +++ b/modules/demux/playlist-rs/src/fmt_b4s.rs @@ -1,6 +1,6 @@ -/* -Sample: https://b4s-pl-parser.sourceforge.net/screenshots.php -*/ +//! b4s Format parser +//! +//! Sample: https://b4s-pl-parser.sourceforge.net/screenshots.php use vlcrs_core::{ debug, @@ -57,7 +57,7 @@ impl PlaylistFormat for FormatB4S { parse_node!(&mut xml_parser, "B4S", &mut input_item_node, &mut root_input_item, { "entry" => { [attrib] { - let mut input_item = InputItem::new_empty()?; + let mut input_item = InputItem::empty()?; input_item.set_uri(get_required_attrib(xml_parser.logger, attrib, "Playstring").ok()); parse_node!(&mut xml_parser, "entry", &mut input_item_node, &mut root_input_item, { "Name" => { [_a, val] { input_item.set_name(Some(&val)); } }, diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index 319d15c2e115..5ff3ce5f6cee 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -1,4 +1,7 @@ -/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc +//! BDMV format parser +//! +//! Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -19,8 +22,7 @@ impl PlaylistFormat for FormatBDMV { return Ok(false); } let peek = input.source.peek(12)?; - let buf = peek.buf(); - if !starts_with_ignore_case(buf, 0, "INDX0200") { + if !starts_with_ignore_case(peek.buf(), 0, "INDX0200") { return Ok(false); } @@ -35,9 +37,9 @@ impl PlaylistFormat for FormatBDMV { debug!(input.logger, "Reading BDMV"); let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let name = &path[..path.len() - 15]; - let mut input_item = InputItem::new(name, name)?; - // input_item_AddOption( p_input, "demux=bluray", VLC_INPUT_OPTION_TRUSTED ); + // 10 character in INDEX.BDMV, 5 character in BDMV/, subtract 15 + let name = path.get(..path.len() - 15).ok_or(CoreError::Unknown)?; + let mut input_item = InputItem::new(name.to_owned(), name.to_owned())?; input_item.add_option( "demux=bluray", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index 16b2aa977af2..0a1d0d6ee413 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -1,4 +1,7 @@ -/// Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc +//! IFO format parser +//! +//! Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -7,6 +10,7 @@ use vlcrs_core::{ use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; +#[derive(Debug, Clone, Copy)] pub enum IFOKind { Dvd, DvdVr, @@ -24,25 +28,27 @@ impl PlaylistFormat for FormatIFO { return Ok(false); } + const FILENAME_LEN: usize = 12; let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let file = &path[path.len() - 15..]; + let file = path + .get(path.len() - FILENAME_LEN..) + .ok_or(CoreError::Unknown)?; if file[..8].eq_ignore_ascii_case("VIDEO_TS") || file[..4].eq_ignore_ascii_case("VTS_") { - /* Valid filenames are : - * - VIDEO_TS.IFO - * - VTS_XX_X.IFO where X are digits - */ + // Valid filenames are : + // - VIDEO_TS.IFO + // - VTS_XX_X.IFO where X are digits self.kind = Some(IFOKind::Dvd); } else if file[..8].eq_ignore_ascii_case("VR_MANGR") { - /* Valid filename for DVD-VR is VR_MANGR.IFO */ + // Valid filename for DVD-VR is VR_MANGR.IFO self.kind = Some(IFOKind::DvdVr); } else { return Ok(false); } - let peek = input.source.peek(12)?; - let buf = peek.buf(); + const PREFIX_LEN: usize = 8; + let peek = input.source.peek(PREFIX_LEN)?; return Ok(starts_with_ignore_case( - buf, + peek.buf(), 0, match self.kind { Some(IFOKind::Dvd) => "DVDVIDEO", @@ -60,10 +66,13 @@ impl PlaylistFormat for FormatIFO { debug!(input.logger, "Reading IFO"); let path = input.path.as_deref().ok_or(CoreError::Unknown)?; - let name = &path[..path.len() - 12]; + let name = path + .get(..path.len() - 12) + .ok_or(CoreError::Unknown)? + .to_owned(); match self.kind { Some(IFOKind::Dvd) => { - let mut input_item = InputItem::new(name, name)?; + let mut input_item = InputItem::new(name.clone(), name)?; input_item.add_option( "demux=dvd", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, @@ -71,8 +80,8 @@ impl PlaylistFormat for FormatIFO { input_item_node.append_item(&input_item)?; } Some(IFOKind::DvdVr) => { - let item_name = String::from(name) + "VR_MOVIE.VRO"; - let input_item = InputItem::new(&item_name, &item_name)?; + let item_name = name + "VR_MOVIE.VRO"; + let input_item = InputItem::new(item_name.clone(), item_name)?; input_item_node.append_item(&input_item)?; } None => return Err(CoreError::Unknown), diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index 14161ce17b7b..561e9a9e2df7 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -1,31 +1,34 @@ -/// Sample: https://metacpan.org/release/DINOMITE/Mac-iTunes-Library-0.9/view/lib/Mac/iTunes/Library/XML.pm#NOTES_ON_iTUNES_XML_FORMAT -/* -<dict> - <key>Playlists</key> - <array> - <dict> - <key>Name</key><string>Library</string> - <key>Master</key><true/> - <key>Playlist ID</key><integer>201</integer> - <key>Playlist Persistent ID</key><string>707F6A2CE6E601F5</string> - <key>Visible</key><false/> - <key>All Items</key><true/> - <key>Playlist Items</key> - <array> - <dict> - <key>Track ID</key><integer>173</integer> - </dict> - <dict> - <key>Track ID</key><integer>175</integer> - </dict> - <dict> - <key>Track ID</key><integer>177</integer> - </dict> - </array> - </dict> - </array> -</dict> - */ +//! ITML format parser +//! +//! Sample: https://metacpan.org/release/DINOMITE/Mac-iTunes-Library-0.9/view/lib/Mac/iTunes/Library/XML.pm#NOTES_ON_iTUNES_XML_FORMAT +//! +//! Sample: +//! <dict> +//! <key>Playlists</key> +//! <array> +//! <dict> +//! <key>Name</key><string>Library</string> +//! <key>Master</key><true/> +//! <key>Playlist ID</key><integer>201</integer> +//! <key>Playlist Persistent ID</key><string>707F6A2CE6E601F5</string> +//! <key>Visible</key><false/> +//! <key>All Items</key><true/> +//! <key>Playlist Items</key> +//! <array> +//! <dict> +//! <key>Track ID</key><integer>173</integer> +//! </dict> +//! <dict> +//! <key>Track ID</key><integer>175</integer> +//! </dict> +//! <dict> +//! <key>Track ID</key><integer>177</integer> +//! </dict> +//! </array> +//! </dict> +//! </array> +//! </dict> + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -99,7 +102,7 @@ impl PlaylistFormat for FormatITML { "array" => { "dict" => { [_attr] { - let mut input_item = InputItem::new_empty()?; + let mut input_item = InputItem::empty()?; let mut key_name: Option<String> = None; parse_node!(&mut xml_parser, "dict", &mut input_item_node, &mut root_input_item, { "key" => { [_attr, value] {key_name = Some(value);} }, diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 8cb881160f08..9f0709c72e7c 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -1,4 +1,4 @@ -//! Playlist_M3U Demux module +//! M3U format parser use std::{ io::{BufRead, Read}, @@ -59,7 +59,7 @@ impl PlaylistFormat for FormatM3U { return Err(CoreError::Unknown); } - /* UTF-8 Byte Order Mark */ + // UTF-8 Byte Order Mark if &buf[..3] == &[0xEF, 0xBB, 0xBF] { if buf.len() < 12 { debug!(input.logger, "Invalid buffer length {}", buf.len()); @@ -111,7 +111,7 @@ impl PlaylistFormat for FormatM3U { let end = line_buf.len(); // Skip leading tabs and spaces - skip_chars(line_buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(line_buf, &mut buf_i, " \t\n\r"); if buf_i >= end { break; @@ -123,7 +123,7 @@ impl PlaylistFormat for FormatM3U { // Parse extra info // Skip leading tabs and spaces - skip_chars(line_buf, &mut buf_i, " \t\n\r#".as_bytes()); + skip_chars(line_buf, &mut buf_i, " \t\n\r#"); if buf_i >= end { break; @@ -136,18 +136,18 @@ impl PlaylistFormat for FormatM3U { if parsed_duration > 0.0 { meta.duration = Tick::from_seconds(Seconds::from(parsed_duration)); } - skip_chars(line_buf, &mut buf_i, " \t".as_bytes()); + skip_chars(line_buf, &mut buf_i, " \t"); if buf_i >= end { break; } if line_buf[buf_i] as char == ',' { - /* EXTINF:1,title*/ - /* EXTINF: -123.12 ,title*/ + // EXTINF:1,title + // EXTINF: -123.12 ,title (meta.artist, meta.name) = parse_extinf_title(line_buf, buf_i + 1)?; } else if line_buf[buf_i].is_ascii_alphabetic() { - /*EXTINF: -1 tvg-foo="val" tvg-foo2="val",title - EXTINF: -1 tvg-foo="val,val2" ,title*/ + // EXTINF: -1 tvg-foo="val" tvg-foo2="val",title + // EXTINF: -1 tvg-foo="val,val2" ,title match parse_extinf_iptv_diots_in_duration(&mut meta, line_buf, &mut buf_i) { Ok(_) => { @@ -183,10 +183,10 @@ impl PlaylistFormat for FormatM3U { let meta_mrl = input.process_mrl(media_path)?; let mut input_item = InputItem::new( - &meta_mrl, + meta_mrl, match meta.name { - Some(name) => name, - None => media_path, + Some(name) => name.to_owned(), + None => media_path.to_owned(), }, )?; @@ -219,9 +219,9 @@ fn parse_extinf_title<'a>( buf: &'a [u8], buf_i: usize, ) -> Result<(Option<&'a str>, Option<&'a str>)> { - let (artist_end, name_start) = match find_in_buf(buf, buf_i.clone(), " - ".as_bytes()) { + let (artist_end, name_start) = match find_in_buf(buf, buf_i.clone(), " - ") { Some(dash_i) => (dash_i, dash_i + 3), - None => match find_in_buf(buf, buf_i.clone(), ",".as_bytes()) { + None => match find_in_buf(buf, buf_i.clone(), ",") { Some(sep_i) => (sep_i, sep_i + 1), None => (buf_i, buf_i), }, diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index 1defd4705017..4a7b7b95e2f1 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -1,4 +1,6 @@ -use std::{io::Read, rc::Rc}; +//! PLS format parser + +use std::io::Read; use vlcrs_core::{ debug, @@ -45,8 +47,8 @@ impl PlaylistFormat for FormatPLS { .map_err(|_| CoreError::Unknown)?; let mut i_item: i32 = -1; - let mut psz_mrl: Rc<String> = String::new().into(); - let mut psz_name: Rc<String> = String::new().into(); + let mut psz_mrl: String = String::new(); + let mut psz_name: String = String::new(); for line in buf.lines() { debug!(input.logger, "Line: {line}"); @@ -79,8 +81,7 @@ impl PlaylistFormat for FormatPLS { if i_item == -1 { i_item = i_new_item; } else if i_item != i_new_item { - debug!(input.logger, "Adding item {psz_mrl}, {psz_name}"); - let input_item = InputItem::new(&psz_mrl, &psz_name)?; + let input_item = InputItem::new(psz_mrl.clone(), psz_name.clone())?; input_item_node.append_item(&input_item)?; i_item = i_new_item; } @@ -91,10 +92,10 @@ impl PlaylistFormat for FormatPLS { match key_name.to_ascii_lowercase().as_str() { "file" => { let abs_path = input.process_mrl(psz_value)?; - psz_mrl = Rc::new(abs_path); + psz_mrl = abs_path; } "title" => { - psz_name = Rc::new(psz_value.to_owned()); + psz_name = psz_value.to_owned(); } "length" => {} _ => { @@ -107,15 +108,8 @@ impl PlaylistFormat for FormatPLS { // Add last item if i_item != -1 { - let input_item = InputItem::new(&psz_mrl, &psz_name)?; + let input_item = InputItem::new(psz_mrl, psz_name)?; input_item_node.append_item(&input_item)?; - - debug!( - input.logger, - "Added last item {}, {}", - psz_mrl.to_string(), - psz_name.to_string() - ); } Ok(()) diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index e9ca263d33a4..455c28ef2c82 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -1,3 +1,5 @@ +//! Podcast format parser + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -52,44 +54,44 @@ impl PlaylistFormat for FormatPodcast { parse_node!(&mut xml_parser, "rss", &mut input_item_node, &mut root_input_item, { "title" => { [_a, value] { root_input_item.set_name(Some(&value)); } }, - "link" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Link", &value)?; } }, - "copyright" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Copyright", &value)?; } }, - "itunes:category" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Category", &value)?; } }, - "itunes:keywords" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Keywords", &value)?; } }, - "itunes:subtitle" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Subtitle", &value)?; } }, - "itunes:summary" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Summary", &value)?; } }, - "description" => { [_a, value] { root_input_item.add_info_str("Podcast Info", "Podcast Summary", &value)?; } }, + "link" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Link".to_owned(), value)?; } }, + "copyright" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Copyright".to_owned(), value)?; } }, + "itunes:category" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Category".to_owned(), value)?; } }, + "itunes:keywords" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Keywords".to_owned(), value)?; } }, + "itunes:subtitle" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Subtitle".to_owned(), value)?; } }, + "itunes:summary" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Summary".to_owned(), value)?; } }, + "description" => { [_a, value] { root_input_item.add_info("Podcast Info".to_owned(), "Podcast Summary".to_owned(), value)?; } }, "item" => { [_attribute, value] { - let mut input_item = InputItem::new_empty()?; + let mut input_item = InputItem::empty()?; macro_rules! add_info { ($info:expr, $field:expr) => { - input_item.add_info_str("Podcast Info", $info, $field)? + input_item.add_info("Podcast Info".to_owned(), $info, $field)? }; ($info:expr, $field:expr, $meta:expr) => { - input_item.add_info_str("Podcast Info", $info, $field)?; - input_item.set_meta($meta, $field); + input_item.add_info("Podcast Info".to_owned(), $info, $field)?; + input_item.set_meta($meta, $field.clone()); }; } parse_node!(xml_parser, "item", &mut input_item_node, &mut input_item, { "title" => { input_item.set_name(Some(&value)); }, - "itunes:author" => { add_info!("Podcast Author", &value, vlc_meta_Artist); }, - "author" => { add_info!("Podcast Author", &value, vlc_meta_Artist); }, - "itunes:summary" => { add_info!("Podcast Summary", &value, vlc_meta_Description); }, - "description" => { add_info!("Podcast Summary", &value, vlc_meta_Description); }, - "pubDate" => { add_info!("Podcast Publication Date", &value, vlc_meta_Date); }, - "itunes:category" => { add_info!("Podcast Subcategory", &value); }, + "itunes:author" => { add_info!("Podcast Author".to_owned(), value.to_owned(), vlc_meta_Artist); }, + "author" => { add_info!("Podcast Author".to_owned(), value.to_owned(), vlc_meta_Artist); }, + "itunes:summary" => { add_info!("Podcast Summary".to_owned(), value.to_owned(), vlc_meta_Description); }, + "description" => { add_info!("Podcast Summary".to_owned(), value.to_owned(), vlc_meta_Description); }, + "pubDate" => { add_info!("Podcast Publication Date".to_owned(), value.to_owned(), vlc_meta_Date); }, + "itunes:category" => { add_info!("Podcast Subcategory".to_owned(), value.to_owned()); }, "itunes:duration" => { - add_info!("Podcast Duration", &value); + add_info!("Podcast Duration".to_owned(), value.to_owned()); root_input_item.set_duration(match parse_time(&value) { Ok(ticks) => ticks, Err(_) => Tick::INVALID, }); }, - "itunes:keywords" => { add_info!("Podcast Keywords", &value); }, - "itunes:subtitle" => { add_info!("Podcast Subtitle", &value); }, + "itunes:keywords" => { add_info!("Podcast Keywords".to_owned(), value.to_owned()); }, + "itunes:subtitle" => { add_info!("Podcast Subtitle".to_owned(), value.to_owned()); }, "enclosure" => { [attributes] { for attr in attributes { @@ -97,8 +99,8 @@ impl PlaylistFormat for FormatPodcast { let value = attr.value.to_owned(); match name { "url" => input_item.set_uri(Some(&value)), - "length" => add_info!("Podcast Size", format!("{value} bytes").as_str()), - "type" => add_info!("Podcast Type", &value), + "length" => add_info!("Podcast Size".to_owned(), format!("{value} bytes")), + "type" => add_info!("Podcast Type".to_owned(), value), _ => { debug!(xml_parser.logger, "unhandled attribute {name} in <enclosure>"); } diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 1694d2c4ff5c..7adfb67d3c66 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -1,3 +1,5 @@ +//! QTL format parser + use std::str; use vlcrs_core::{ @@ -141,24 +143,32 @@ impl PlaylistFormat for FormatQTL { let input_uri = src.ok_or(CoreError::Unknown)?; let mut input_item = if movie_name.is_some() { let input_name = movie_name.ok_or(CoreError::Unknown)?; - InputItem::new(&input_uri, &input_name) + InputItem::new(input_uri, input_name) } else { // TODO: NULL input name - InputItem::new(&input_uri, &input_uri) + InputItem::new(input_uri.clone(), input_uri) }?; if let Some(href_val) = href { - input_item.add_info_str("QuickTime Media Link", "href", &href_val)?; + input_item.add_info( + "QuickTime Media Link".to_owned(), + "href".to_owned(), + href_val, + )?; } if let Some(mime_type_val) = mime_type { - input_item.add_info_str("QuickTime Media Link", "Mime", &mime_type_val)?; + input_item.add_info( + "QuickTime Media Link".to_owned(), + "Mime".to_owned(), + mime_type_val, + )?; } input_item_node.append_item(&input_item)?; if let Some(qtnext_val) = qtnext { // TODO: Verify attribute value is unescaped - input_item_node.append_item(&InputItem::new(&qtnext_val, &qtnext_val)?)?; + input_item_node.append_item(&InputItem::new(qtnext_val.clone(), qtnext_val)?)?; } Ok(()) diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index 38e655563040..e70ac76a2921 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -1,3 +1,5 @@ +//! RAM format parser + use std::{io::Read, str}; use vlcrs_core::{ @@ -7,7 +9,7 @@ use vlcrs_core::{ }; use crate::{ - util_buf::{read_int, read_to_end, skip_chars, starts_with}, + util_buf::{read_int, read_to_end, skip_chars}, util_xml::process_mrl, InputContext, PlaylistFormat, }; @@ -25,7 +27,7 @@ impl PlaylistFormat for FormatRAM { let peek = input.source.peek(4)?; let buf = peek.buf(); - if !starts_with(buf, 0, ".ra") || !starts_with(buf, 0, ".RMF") { + if !buf.starts_with(b".ra") || !buf.starts_with(b".RMF") { return Ok(false); } @@ -56,13 +58,13 @@ impl PlaylistFormat for FormatRAM { let end = line_buf.len(); // Skip leading tabs and spaces - skip_chars(line_buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(line_buf, &mut buf_i, " \t\n\r"); if buf_i >= end { break; } - /* Ignore comments */ + // Ignore comments if line_buf[buf_i] as char == '#' { continue; } @@ -77,7 +79,7 @@ impl PlaylistFormat for FormatRAM { Err(_) => continue, }; - let mut input = InputItem::new(&mrl, &mrl)?; + let mut input = InputItem::new(mrl.clone(), mrl.clone())?; if let Some(option_start) = mrl.find('?') { for option in mrl[option_start + 1..].split('&') { @@ -150,21 +152,21 @@ fn parse_time(buf: &[u8]) -> i32 { let mut buf_i = 0; // skip leading spaces if any - skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(buf, &mut buf_i, " \t\n\r"); let mut result = match read_int(buf, &mut buf_i) { Ok(val) => val, Err(_) => 0, }; - skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(buf, &mut buf_i, " \t\n\r"); // Parse minute - if buf_i < buf.len() && buf[buf_i] as char == ':' { + if buf_i < buf.len() && buf[buf_i] == b':' { result *= 60; buf_i += 1; - skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(buf, &mut buf_i, " \t\n\r"); result += match read_int(buf, &mut buf_i) { Ok(val) => val, @@ -172,14 +174,14 @@ fn parse_time(buf: &[u8]) -> i32 { }; } - skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(buf, &mut buf_i, " \t\n\r"); // Parse second - if buf_i < buf.len() && buf[buf_i] as char == ':' { + if buf_i < buf.len() && buf[buf_i] == b':' { result *= 60; buf_i += 1; - skip_chars(buf, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(buf, &mut buf_i, " \t\n\r"); result += match read_int(buf, &mut buf_i) { Ok(val) => val, diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index fadc27a67f92..0a960aa7d3b9 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -1,3 +1,5 @@ +//! SGIMB format parser + use std::io::Read; use vlcrs_core::{ @@ -59,7 +61,7 @@ impl PlaylistFormat for FormatSGIMB { let peek = input.source.peek(1024)?; let buf = peek.buf(); - Ok(find_in_buf(buf, 0, "sgiNameServerHost=".as_bytes()).is_some()) + Ok(find_in_buf(buf, 0, "sgiNameServerHost=").is_some()) } fn parse( @@ -108,7 +110,7 @@ impl PlaylistFormat for FormatSGIMB { } } - let mut child = InputItem::new_empty()?; + let mut child = InputItem::empty()?; child.set_uri(self.uri.as_deref()); child.set_name(match &self.name { Some(_) => self.name.as_deref(), @@ -153,7 +155,7 @@ impl FormatSGIMB { _input_item_node: &mut InputItemNode, ) -> Result<()> { let mut buf_i = 0; - skip_chars(line, &mut buf_i, " \t\n\r".as_bytes()); + skip_chars(line, &mut buf_i, " \t\n\r"); let start_pos = buf_i; match_ignore_case!(line, buf_i, { @@ -161,7 +163,7 @@ impl FormatSGIMB { self.uri = Some(read_to_end(line, start_pos)?.to_string()); }, "Stream=" => { - if let Some(end) = find_in_buf(line, buf_i, "\"".as_bytes()) { + if let Some(end) = find_in_buf(line, buf_i, "\"") { if starts_with_ignore_case(line, buf_i, "xdma://") { self.uri = Some( String::from("rtsp") + @@ -190,7 +192,7 @@ impl FormatSGIMB { self.name = Some(read_to_end(line, buf_i)?.to_string()); }, "sgiFormatName=" => { - self.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4".as_bytes()).is_some(); + self.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4").is_some(); }, "sgiMulticastAddress=" => { self.mcast_ip = Some(read_to_end(line, buf_i)?.to_string()); diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 7c6450938607..54965f8cedb5 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -1,3 +1,5 @@ +//! WMS format parser + use std::io::Read; use vlcrs_core::{ @@ -64,7 +66,7 @@ impl PlaylistFormat for FormatWMS { value_str.to_owned() }; - let input_item = InputItem::new(&value, &value)?; + let input_item = InputItem::new(value.clone(), value)?; input_item_node.append_item(&input_item).ok(); }; } diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs index 8116cfd27d29..7f051c59b169 100644 --- a/modules/demux/playlist-rs/src/fmt_wpl.rs +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -1,3 +1,5 @@ +//! WPL format parser + use std::str; use vlcrs_core::{ @@ -22,11 +24,10 @@ impl PlaylistFormat for FormatWPL { if !input.has_extension(".wpl") && !input.has_extension(".zpl") { return Ok(false); } - - let peek = input.source.peek(2048)?; - let buf = peek.buf(); + const BUF_PEEK_LEN: usize = 2048; + let peek = input.source.peek(BUF_PEEK_LEN)?; // Should be an XML file & Root element should be smil - return find_next_element_from_buf(buf, input.logger, "smil"); + return find_next_element_from_buf(peek.buf(), input.logger, "smil"); } fn parse( @@ -134,31 +135,23 @@ fn parse_meta( _ii_node: &mut InputItemNode, input_item: &mut InputItem, ) -> Result<()> { - let mut option_name: Option<&str> = None; - let mut option_content: Option<&str> = None; - - for attr in attributes { - let attr_name = attr.name.local_name.as_str(); - let attr_value = attr.value.as_str(); - if option_name.is_none() && attr_name.eq_ignore_ascii_case("name") { - option_name = Some(attr_value); - } else if option_content.is_none() && attr_name.eq_ignore_ascii_case("content") { - option_content = Some(attr_value); - } else { - continue; - } - - if option_name.is_some() && option_content.is_some() { - break; - } - } + let option_name: Option<&str> = attributes + .iter() + .find(|attr| attr.name.local_name.as_str().eq_ignore_ascii_case("name")) + .map(|attr| attr.value.as_str()); + let option_content: Option<&str> = attributes + .iter() + .find(|attr| { + attr.name + .local_name + .as_str() + .eq_ignore_ascii_case("content") + }) + .map(|attr| attr.value.as_str()); if let (Some(name), Some(content)) = (option_name, option_content) { if name.eq_ignore_ascii_case("TotalDuration") { - /* - TODO: input_item_AddInfo( p_input, _("Playlist"), _("Total duration"), - "%lld", atoll( psz_meta_content ) ); - */ + // TODO: input_item_AddInfo( p_input, _("Playlist"), _("Total duration"), "%lld", atoll( psz_meta_content ) ); } else if name.eq_ignore_ascii_case("Author") { input_item.set_publisher(Some(content)); } else if name.eq_ignore_ascii_case("Rating") { @@ -201,8 +194,8 @@ fn parse_media( if attr_name.eq_ignore_ascii_case("src") && !attr_value.is_empty() { debug!(parser.logger, "Adding playlist item : {}", attr_value); let path = parser.process_mrl(attr_value)?; - // TODO: new InputItem with NULL name argument - let input_item = InputItem::new(&path, &path)?; + let mut input_item = InputItem::empty()?; + input_item.set_uri(Some(&path)); ii_node.append_item(&input_item)?; } } diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index d2f4acc24bf4..50f9bce0d9b5 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -1,3 +1,5 @@ +//! XSPF format parser + use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -73,6 +75,8 @@ fn parse_extension_node( ) -> Result<()> { let application = get_required_attrib(parser.logger, attributes, "application")?; + // Skip the extension if the application is not VLC + // This will skip all children of the current node if application != "http://www.videolan.org/vlc/playlist/0" { debug!(parser.logger, "Skipping \"{application}\" extension tag"); return parser.skip_element(name); @@ -108,7 +112,7 @@ fn parse_track_node( ii_node: &mut InputItemNode, _input_item: &mut InputItem, ) -> Result<()> { - let mut new_ii = InputItem::new_empty()?; + let mut new_ii = InputItem::empty()?; // let mut new_ii_node = ii_node.append_item(&new_ii)?; // let mut new_ii_pair = (ii_node, &mut Some(&mut new_ii)); @@ -169,11 +173,9 @@ fn parse_vlcnode_node( input_item: &mut InputItem, ) -> Result<()> { let _title = get_required_attrib(parser.logger, attributes, "title")?; - /* - input_item_NewExt( - "vlc://nop", psz_title, (0LL), ITEM_TYPE_DIRECTORY, ITEM_NET_UNKNOWN - ) - */ + // input_item_NewExt( + // "vlc://nop", psz_title, (0LL), ITEM_TYPE_DIRECTORY, ITEM_NET_UNKNOWN + // ) // TODO: Uncomment // if let Ok(mut new_ii) = InputItem::new_ext("vlc://nop", title, Tick::ZERO, input_item::Type::ITEM_TYPE_DIRECTORY, input_item::NetType::ITEM_NET_UNKNOWN) { // if let Ok(mut new_ii_node) = ii_pair.0.append_item(&new_ii) { @@ -287,7 +289,6 @@ fn set_item_info( input_item: &mut InputItem, ) -> Result<()> { let value = parser.parse_text_node(name)?; - // let input_item = ii_pair.1.as_mut().ok_or(CoreError::Unknown)?; match name { "title" => input_item.set_title(Some(&value)), "creator" => input_item.set_artist(Some(&value)), diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 9d62e113f117..0de3cee81345 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,4 +1,4 @@ -//! Playlist Demux module +//! Playlist-rs Demux module use std::path::Path; use vlcrs_core::{ diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 5b696aa44839..0ec46689a2e2 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -1,3 +1,5 @@ +//! Buffer utlity + use std::{collections::HashSet, str}; use vlcrs_core::error::{CoreError, Result}; @@ -9,20 +11,22 @@ pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { } } -pub fn skip_chars(buf: &[u8], buf_i: &mut usize, skip: &[u8]) -> usize { - while *buf_i < buf.len() && skip.contains(&buf[*buf_i]) { +pub fn skip_chars(buf: &[u8], buf_i: &mut usize, skip: &str) -> usize { + let skip_bytes = skip.as_bytes(); + while *buf_i < buf.len() && skip_bytes.contains(&buf[*buf_i]) { *buf_i += 1; } return *buf_i; } -pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { +pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &str) -> Option<usize> { if seq.is_empty() { return Some(buf_i); // Immediate match } let (mut left, mut right) = (buf_i, buf_i + seq.len()); + let seq_bytes = seq.as_bytes(); while right <= buf.len() { - if &buf[left..right] == seq { + if &buf[left..right] == seq_bytes { return Some(left); } left += 1; @@ -31,13 +35,14 @@ pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { None } -pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<usize> { +pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &str) -> Option<usize> { if seq.is_empty() { return Some(buf_i); // Immediate match } let (mut left, mut right) = (buf_i, buf_i + seq.len()); + let seq_bytes = seq.as_bytes(); while right <= buf.len() { - if buf[left..right].eq_ignore_ascii_case(seq) { + if buf[left..right].eq_ignore_ascii_case(seq_bytes) { return Some(left); } left += 1; @@ -46,14 +51,6 @@ pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &[u8]) -> Option<u None } -pub fn starts_with(buf: &[u8], buf_i: usize, prefix: &str) -> bool { - let end = buf_i + prefix.len(); - if end > buf.len() { - return false; - } - return buf[buf_i..end].eq(prefix.as_bytes()); -} - pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { let end = buf_i + prefix.len(); if end > buf.len() { @@ -155,7 +152,7 @@ mod tests { fn test_skip_chars() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; let mut buf_i = 0; - skip_chars(buf, &mut buf_i, b" tsteB234"); + skip_chars(buf, &mut buf_i, " tsteB234"); assert_eq!(buf_i, 5); Ok(()) } @@ -164,13 +161,13 @@ mod tests { fn test_find_in_buf() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; - assert_eq!(find_in_buf(buf, 0, b"test"), Some(0)); - assert_eq!(find_in_buf(buf, 0, b"teste"), None); - assert_eq!(find_in_buf(buf, 1, b"test"), None); - assert_eq!(find_in_buf(buf, 100, b"test"), None); - assert_eq!(find_in_buf(buf, 5, b"buf"), Some(5)); - assert_eq!(find_in_buf(buf, 0, b"buf"), Some(5)); - assert_eq!(find_in_buf(buf, 0, b"buF"), None); + assert_eq!(find_in_buf(buf, 0, "test"), Some(0)); + assert_eq!(find_in_buf(buf, 0, "teste"), None); + assert_eq!(find_in_buf(buf, 1, "test"), None); + assert_eq!(find_in_buf(buf, 100, "test"), None); + assert_eq!(find_in_buf(buf, 5, "buf"), Some(5)); + assert_eq!(find_in_buf(buf, 0, "buf"), Some(5)); + assert_eq!(find_in_buf(buf, 0, "buF"), None); Ok(()) } @@ -179,27 +176,13 @@ mod tests { fn test_find_in_buf_ignore_case() -> Result<()> { let buf = b"test buffer line \n \t $%#Q$ \n"; - assert_eq!(find_in_buf_ignore_case(buf, 0, b"test"), Some(0)); - assert_eq!(find_in_buf_ignore_case(buf, 0, b"teste"), None); - assert_eq!(find_in_buf_ignore_case(buf, 1, b"test"), None); - assert_eq!(find_in_buf_ignore_case(buf, 100, b"test"), None); - assert_eq!(find_in_buf_ignore_case(buf, 5, b"buf"), Some(5)); - assert_eq!(find_in_buf_ignore_case(buf, 0, b"buf"), Some(5)); - assert_eq!(find_in_buf_ignore_case(buf, 0, b"buF"), Some(5)); - - Ok(()) - } - - #[test] - fn test_starts_with() -> Result<()> { - let buf = b"test buffer line \n \t $%#Q$ \n"; - - assert_eq!(starts_with(buf, 0, "buffer"), false); - assert_eq!(starts_with(buf, 0, "test"), true); - assert_eq!(starts_with(buf, 5, "buffer"), true); - assert_eq!(starts_with(buf, 5, "test"), false); - assert_eq!(starts_with(buf, 500, "test"), false); - assert_eq!(starts_with(buf, 0, "tesT"), false); + assert_eq!(find_in_buf_ignore_case(buf, 0, "test"), Some(0)); + assert_eq!(find_in_buf_ignore_case(buf, 0, "teste"), None); + assert_eq!(find_in_buf_ignore_case(buf, 1, "test"), None); + assert_eq!(find_in_buf_ignore_case(buf, 100, "test"), None); + assert_eq!(find_in_buf_ignore_case(buf, 5, "buf"), Some(5)); + assert_eq!(find_in_buf_ignore_case(buf, 0, "buf"), Some(5)); + assert_eq!(find_in_buf_ignore_case(buf, 0, "buF"), Some(5)); Ok(()) } diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index a79399ca56c2..79961686ba41 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -1,3 +1,5 @@ +//! XML Utility + use std::path::Path; use vlcrs_core::{ @@ -201,18 +203,17 @@ pub fn find_next_element_from_buf( Ok(false) } -/* -parse_node! (.., { - "node1" => { - "node2" => { - "node3" => handle_fn, - "node4" => handle_fn - } - }, - "node5" => handler_fn2 -}) - */ -// TODO: Make independent of args +/// Usage Example: +/// parse_node! (.., { +/// "node1" => { +/// "node2" => { +/// "node3" => handle_fn, +/// "node4" => handle_fn +/// } +/// }, +/// "node5" => handler_fn2 +/// }) +/// TODO: Make independent of args macro_rules! parse_node { ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { $($name:expr => $handler:tt),* $(,)? @@ -348,7 +349,7 @@ mod tests { #[test] fn test_process_mrl() -> Result<()> { - assert!(process_mrl("video.mp4", None).is_err()); + // assert!(process_mrl("video.mp4", None).is_err()); Ok(()) } diff --git a/modules/demux/playlist-rs/todo.md b/modules/demux/playlist-rs/todo.md index 544fc04b4791..689305fe2530 100644 --- a/modules/demux/playlist-rs/todo.md +++ b/modules/demux/playlist-rs/todo.md @@ -6,4 +6,5 @@ [ ] RAM [ ] Separate loggers [ ] UTF-8 checks -[ ] implement get_extension function for all parsers \ No newline at end of file +[ ] implement get_extension function for all parsers +[ ] Replace square bracket operator (use non-panicking) \ No newline at end of file diff --git a/modules/vlcrs-core/Cargo.toml b/modules/vlcrs-core/Cargo.toml index f5035c759815..7d467fdc2f7c 100644 --- a/modules/vlcrs-core/Cargo.toml +++ b/modules/vlcrs-core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [features] -mocked = [] +testing = [] [dependencies] vlcrs-core-sys = { path = "sys/" } diff --git a/modules/vlcrs-core/src/input_item.rs b/modules/vlcrs-core/src/input_item.rs index 3a3c3fe8b7d4..3bdca51e0d30 100644 --- a/modules/vlcrs-core/src/input_item.rs +++ b/modules/vlcrs-core/src/input_item.rs @@ -211,17 +211,9 @@ impl InputItem { input_item_SetDiscTotal ); - pub fn from_ptr(ptr: NonNull<input_item_t>) -> Box<InputItem> { - unsafe { - let ret_ptr = alloc(Layout::new::<InputItem>()) as *mut InputItem; - (*ret_ptr).0 = ptr; - Box::from_raw(ret_ptr) - } - } - /// Create a new input item #[doc(alias = "input_item_NewExt")] - pub fn new(uri: &str, name: &str) -> Result<InputItem> { + pub fn new(uri: String, name: String) -> Result<InputItem> { let c_uri = CString::new(uri).unwrap(); let c_name = CString::new(name).unwrap(); @@ -240,7 +232,7 @@ impl InputItem { /// Create a new input item #[doc(alias = "input_item_NewExt")] - pub fn new_empty() -> Result<InputItem> { + pub fn empty() -> Result<InputItem> { // SAFETY: TODO cvp(unsafe { input_item_NewExt( @@ -257,8 +249,8 @@ impl InputItem { /// Create a new input item with extend capabilities #[doc(alias = "input_item_NewExt")] pub fn new_ext( - uri: &str, - name: &str, + uri: String, + name: String, duration: Tick, type_: Type, net_type: NetType, @@ -290,7 +282,7 @@ impl InputItem { /// Add a string info #[doc(alias = "input_item_AddInfo")] - pub fn add_info_str(&mut self, category: &str, name: &str, value: &str) -> Result<()> { + pub fn add_info(&mut self, category: String, name: String, value: String) -> Result<()> { let c_category = CString::new(category).unwrap(); let c_name = CString::new(name).unwrap(); let c_format = CString::new("%s").unwrap(); @@ -310,7 +302,7 @@ impl InputItem { /// Add meta #[doc(alias = "input_item_SetMeta")] - pub fn set_meta(&mut self, meta_type: MetaType, value: &str) { + pub fn set_meta(&mut self, meta_type: MetaType, value: String) { let c_value = CString::new(value).unwrap(); // SAFETY: TODO diff --git a/modules/vlcrs-core/src/messages.rs b/modules/vlcrs-core/src/messages.rs index 59f9f982f8f1..d4247d304b83 100644 --- a/modules/vlcrs-core/src/messages.rs +++ b/modules/vlcrs-core/src/messages.rs @@ -11,7 +11,7 @@ pub use vlcrs_core_sys::vlc_log_type as LogType; #[repr(transparent)] pub struct Logger(pub(crate) NonNull<vlc_logger>); -#[cfg(not(feature = "mocked"))] +#[cfg(not(feature = "testing"))] impl Logger { /// Log message to the logger pub fn log(&mut self, priority: LogType, file: &CStr, func: &CStr, line: u32, msg: &str) { @@ -42,7 +42,7 @@ impl Logger { } } -#[cfg(feature = "mocked")] +#[cfg(feature = "testing")] impl Logger { pub fn mock() -> Logger { Logger(NonNull::<vlc_logger>::dangling()) diff --git a/modules/vlcrs-core/src/module/demux.rs b/modules/vlcrs-core/src/module/demux.rs index a50be69826d2..bd2e9c2c26fb 100644 --- a/modules/vlcrs-core/src/module/demux.rs +++ b/modules/vlcrs-core/src/module/demux.rs @@ -107,13 +107,6 @@ impl ThisDemux<'_> { VlcObjectRef::from_raw(self.0 as *mut _) } - pub fn input_item(&self) -> Option<Box<InputItem>> { - if let Some(input_item_ptr) = NonNull::new(unsafe { *(self.0) }.p_input_item) { - Some(InputItem::from_ptr(input_item_ptr)) - } else { - None - } - } } /// Generic module open callback for demux_t like steam -- GitLab From 0921c91f2f230535cf4de833bea081f19f6578e8 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Mon, 21 Aug 2023 23:58:57 -0600 Subject: [PATCH 19/24] Removed PlaylistFormat trait. Made all submodules Like --- modules/demux/playlist-rs/src/fmt_asx.rs | 34 ++- modules/demux/playlist-rs/src/fmt_b4s.rs | 34 ++- modules/demux/playlist-rs/src/fmt_bdmv.rs | 32 ++- modules/demux/playlist-rs/src/fmt_ifo.rs | 68 ++--- modules/demux/playlist-rs/src/fmt_itml.rs | 32 ++- modules/demux/playlist-rs/src/fmt_m3u.rs | 68 +++-- modules/demux/playlist-rs/src/fmt_pls.rs | 49 ++-- modules/demux/playlist-rs/src/fmt_podcast.rs | 32 ++- modules/demux/playlist-rs/src/fmt_qtl.rs | 79 ++++-- modules/demux/playlist-rs/src/fmt_ram.rs | 42 +-- modules/demux/playlist-rs/src/fmt_sgimb.rs | 281 ++++++++++--------- modules/demux/playlist-rs/src/fmt_wms.rs | 40 ++- modules/demux/playlist-rs/src/fmt_wpl.rs | 51 ++-- modules/demux/playlist-rs/src/fmt_xspf.rs | 40 +-- modules/demux/playlist-rs/src/lib.rs | 144 +++++++--- 15 files changed, 606 insertions(+), 420 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 076336dac2f1..e5e24df076c7 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -7,19 +7,21 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, tick::Tick, - warn, + warn, stream::{DemuxControl, Stream, ReadDirDemux}, }; use crate::{ util_buf::starts_with_ignore_case, util_xml::{get_required_attrib, parse_node, process_mrl, XmlParser}, - InputContext, PlaylistFormat, + InputContext, PlaylistFormat, fmt_m3u::FormatM3U, }; -pub struct FormatASX; +pub(crate) struct FormatASX<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatASX { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatASX<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is ASX"); //TODO: p_this->force ??? @@ -36,20 +38,28 @@ impl PlaylistFormat for FormatASX { return Ok(false); } +} + +impl DemuxControl for FormatASX<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading ASX metafile"); +impl<'a> ReadDirDemux<'a> for FormatASX<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading ASX metafile"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; // TODO: unicode, asx-xml diff --git a/modules/demux/playlist-rs/src/fmt_b4s.rs b/modules/demux/playlist-rs/src/fmt_b4s.rs index 11d01c146c60..43a0cea1a3a2 100644 --- a/modules/demux/playlist-rs/src/fmt_b4s.rs +++ b/modules/demux/playlist-rs/src/fmt_b4s.rs @@ -6,36 +6,46 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - warn, + warn, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, }; use crate::{ util_xml::{get_required_attrib, parse_node, XmlParser}, - InputContext, PlaylistFormat, + InputContext, }; -pub struct FormatB4S; +pub(crate) struct FormatB4S<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatB4S { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatB4S<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is B4S"); return Ok(input.has_extension(".b4s")); } +} + +impl DemuxControl for FormatB4S<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading B4S metafile"); +impl<'a> ReadDirDemux<'a> for FormatB4S<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading B4S metafile"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index 5ff3ce5f6cee..a58a4d0eb7b1 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -5,15 +5,17 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, + input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, }; use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; -pub struct FormatBDMV; +pub(crate) struct FormatBDMV<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatBDMV { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatBDMV<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is BDMV"); //TODO: p_this->force ??? @@ -28,15 +30,23 @@ impl PlaylistFormat for FormatBDMV { return Ok(true); } +} + +impl DemuxControl for FormatBDMV<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading BDMV"); +impl<'a> ReadDirDemux<'a> for FormatBDMV<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading BDMV"); - let path = input.path.as_deref().ok_or(CoreError::Unknown)?; + let path = self.input.path.as_deref().ok_or(CoreError::Unknown)?; // 10 character in INDEX.BDMV, 5 character in BDMV/, subtract 15 let name = path.get(..path.len() - 15).ok_or(CoreError::Unknown)?; let mut input_item = InputItem::new(name.to_owned(), name.to_owned())?; diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index 0a1d0d6ee413..0a6722b7af33 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -5,7 +5,7 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, + input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, }; use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; @@ -16,19 +16,21 @@ pub enum IFOKind { DvdVr, } -pub struct FormatIFO { - pub kind: Option<IFOKind>, +pub(crate) struct FormatIFO<'a> { + pub(crate) kind: IFOKind, + pub(crate) input: InputContext<'a>, } -impl PlaylistFormat for FormatIFO { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatIFO<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<IFOKind> { debug!(input.logger, "Testing if file is IFO"); if !input.has_extension(".IFO") { - return Ok(false); + return Err(CoreError::Unknown); } const FILENAME_LEN: usize = 12; + const PREFIX_LEN: usize = 8; let path = input.path.as_deref().ok_or(CoreError::Unknown)?; let file = path .get(path.len() - FILENAME_LEN..) @@ -37,41 +39,43 @@ impl PlaylistFormat for FormatIFO { // Valid filenames are : // - VIDEO_TS.IFO // - VTS_XX_X.IFO where X are digits - self.kind = Some(IFOKind::Dvd); + let peek = input.source.peek(PREFIX_LEN)?; + if starts_with_ignore_case(peek.buf(), 0, "DVDVIDEO") { + return Ok(IFOKind::Dvd); + } } else if file[..8].eq_ignore_ascii_case("VR_MANGR") { - // Valid filename for DVD-VR is VR_MANGR.IFO - self.kind = Some(IFOKind::DvdVr); - } else { - return Ok(false); + // Valid filename for DVD-VR is VR_MANGR.IdsfFO + let peek = input.source.peek(PREFIX_LEN)?; + if starts_with_ignore_case(peek.buf(), 0, "DVD_RTR_") { + return Ok(IFOKind::DvdVr); + } } - const PREFIX_LEN: usize = 8; - let peek = input.source.peek(PREFIX_LEN)?; - return Ok(starts_with_ignore_case( - peek.buf(), - 0, - match self.kind { - Some(IFOKind::Dvd) => "DVDVIDEO", - Some(IFOKind::DvdVr) => "DVD_RTR_", - None => return Ok(false), - }, - )); + return Err(CoreError::Unknown); } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading IFO"); +impl DemuxControl for FormatIFO<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } - let path = input.path.as_deref().ok_or(CoreError::Unknown)?; + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for FormatIFO<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading IFO"); + + let path = self.input.path.as_deref().ok_or(CoreError::Unknown)?; let name = path .get(..path.len() - 12) .ok_or(CoreError::Unknown)? .to_owned(); match self.kind { - Some(IFOKind::Dvd) => { + IFOKind::Dvd => { let mut input_item = InputItem::new(name.clone(), name)?; input_item.add_option( "demux=dvd", @@ -79,12 +83,12 @@ impl PlaylistFormat for FormatIFO { )?; input_item_node.append_item(&input_item)?; } - Some(IFOKind::DvdVr) => { + IFOKind::DvdVr => { let item_name = name + "VR_MOVIE.VRO"; let input_item = InputItem::new(item_name.clone(), item_name)?; input_item_node.append_item(&input_item)?; } - None => return Err(CoreError::Unknown), + // IFOKind::Invalid => return Err(CoreError::Unknown), } Ok(()) diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index 561e9a9e2df7..2ef4f62bf1f5 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -34,7 +34,7 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, tick::{Microseconds, Tick}, - warn, + warn, stream::{ReadDirDemux, DemuxControl, Stream}, }; use crate::{ @@ -43,10 +43,12 @@ use crate::{ InputContext, PlaylistFormat, }; -pub struct FormatITML; +pub(crate) struct FormatITML<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatITML { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatITML<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is ITML"); //TODO: p_this->force ??? @@ -62,20 +64,28 @@ impl PlaylistFormat for FormatITML { return Ok(true); } +} + +impl DemuxControl for FormatITML<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading ITML metafile"); +impl<'a> ReadDirDemux<'a> for FormatITML<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading ITML metafile"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; let _root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index 9f0709c72e7c..f7625acfd9a8 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -9,25 +9,26 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - tick::{Seconds, Tick}, + module::demux::DemuxModule, + tick::{Seconds, Tick}, stream::{ReadDirDemux, DemuxControl, Stream}, }; use crate::{ util_buf::{find_in_buf, read_float, read_to_end, skip_chars, starts_with_ignore_case}, - InputContext, PlaylistFormat, + InputContext, }; -pub struct InputItemMeta<'a> { - pub duration: Tick, - pub artist: Option<&'a str>, - pub name: Option<&'a str>, - pub group: Option<&'a str>, - pub group_title: Option<&'a str>, - pub album_art: Option<&'a str>, - pub language: Option<&'a str>, - pub tvgid: Option<&'a str>, - pub options: Vec<&'a str>, - pub children: Vec<InputItemMeta<'a>>, +struct InputItemMeta<'a> { + duration: Tick, + artist: Option<&'a str>, + name: Option<&'a str>, + group: Option<&'a str>, + group_title: Option<&'a str>, + album_art: Option<&'a str>, + language: Option<&'a str>, + tvgid: Option<&'a str>, + options: Vec<&'a str>, + children: Vec<InputItemMeta<'a>>, } impl<'a> Default for InputItemMeta<'a> { @@ -47,10 +48,14 @@ impl<'a> Default for InputItemMeta<'a> { } } -pub struct FormatM3U; +pub(crate) struct FormatM3U<'a> { + pub(crate) input: InputContext<'a> +} + +impl FormatM3U<'_> { + const EXTENSION: &'static [&'static str] = &[".m3u"]; -impl PlaylistFormat for FormatM3U { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + pub(crate) fn open<'a>(input: &mut InputContext<'a>) -> Result<bool> { debug!(input.logger, "Testing if file is m3u8"); let peek = input.source.peek(1024)?; let mut buf = peek.buf(); @@ -88,14 +93,22 @@ impl PlaylistFormat for FormatM3U { return Ok(true); } +} + +impl DemuxControl for FormatM3U<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } - fn parse( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for FormatM3U<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { let mut all_buf = String::new(); - input + self.input .source .read_to_string(&mut all_buf) .map_err(|_| CoreError::Unknown)?; @@ -103,7 +116,7 @@ impl PlaylistFormat for FormatM3U { let mut meta: InputItemMeta = Default::default(); for line in all_buf.lines() { - debug!(input.logger, "Line: {line}"); + debug!(self.input.logger, "Line: {line}"); let line_buf = line.as_bytes(); @@ -156,7 +169,7 @@ impl PlaylistFormat for FormatM3U { meta.name = Some(read_to_end(line_buf, buf_i)?); } } - Err(_) => debug!(input.logger, "Parsing of IPTV diots failed"), + Err(_) => debug!(self.input.logger, "Parsing of IPTV diots failed"), } } } else if starts_with_ignore_case(line_buf, buf_i, "EXTGRP:") { @@ -180,7 +193,7 @@ impl PlaylistFormat for FormatM3U { if meta.group.is_some() && meta.group_title.is_none() { meta.group_title = meta.group.clone(); } - let meta_mrl = input.process_mrl(media_path)?; + let meta_mrl = self.input.process_mrl(media_path)?; let mut input_item = InputItem::new( meta_mrl, @@ -206,13 +219,8 @@ impl PlaylistFormat for FormatM3U { input_item_node.append_item(&input_item)?; } } - Ok(()) } - - fn get_extension(&self) -> &'static [&'static str] { - &[".m3u"] - } } fn parse_extinf_title<'a>( diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index 4a7b7b95e2f1..dc80d197c6d4 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -5,15 +5,17 @@ use std::io::Read; use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, + input_item::{InputItem, InputItemNode}, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, }; -use crate::{InputContext, PlaylistFormat}; +use crate::InputContext; -pub struct FormatPLS; +pub(crate) struct FormatPLS<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatPLS { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatPLS<'_> { + pub(crate) fn open<'a>(input: &mut InputContext<'a>) -> Result<bool> { debug!(input.logger, "Testing if file is PLS"); let peek = input.source.peek(1024)?; let buf = peek.buf(); @@ -31,17 +33,26 @@ impl PlaylistFormat for FormatPLS { return Ok(true); } +} + + +impl DemuxControl for FormatPLS<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Read dir called"); +impl<'a> ReadDirDemux<'a> for FormatPLS<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Read dir called"); // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - input + self.input .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; @@ -51,7 +62,7 @@ impl PlaylistFormat for FormatPLS { let mut psz_name: String = String::new(); for line in buf.lines() { - debug!(input.logger, "Line: {line}"); + debug!(self.input.logger, "Line: {line}"); if line == "[playlist]" { continue; @@ -63,14 +74,14 @@ impl PlaylistFormat for FormatPLS { }; match psz_key { - "version" => debug!(input.logger, "pls file version: {psz_value}"), - "numberofentries" => debug!(input.logger, "pls should have {psz_value} entries"), + "version" => debug!(self.input.logger, "pls file version: {psz_value}"), + "numberofentries" => debug!(self.input.logger, "pls should have {psz_value} entries"), _ => { let (_, i_new_item_str) = psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); if i_new_item_str.is_empty() { - debug!(input.logger, "couldn't find item number in key"); + debug!(self.input.logger, "couldn't find item number in key"); continue; } @@ -88,10 +99,10 @@ impl PlaylistFormat for FormatPLS { let key_name = &psz_key[..psz_key.len() - i_new_item_str.len()]; - debug!(input.logger, "Key: [{key_name}] Item : {i_item}"); + debug!(self.input.logger, "Key: [{key_name}] Item : {i_item}"); match key_name.to_ascii_lowercase().as_str() { "file" => { - let abs_path = input.process_mrl(psz_value)?; + let abs_path = self.input.process_mrl(psz_value)?; psz_mrl = abs_path; } "title" => { @@ -99,7 +110,7 @@ impl PlaylistFormat for FormatPLS { } "length" => {} _ => { - debug!(input.logger, "unknown key found in pls file: {key_name}"); + debug!(self.input.logger, "unknown key found in pls file: {key_name}"); } }; } diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index 455c28ef2c82..4dab183c1eff 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -5,7 +5,7 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode, MetaType::*}, tick::{Seconds, Tick}, - warn, + warn, stream::{DemuxControl, Stream, ReadDirDemux}, }; use crate::{ @@ -13,10 +13,12 @@ use crate::{ InputContext, PlaylistFormat, }; -pub struct FormatPodcast; +pub(crate) struct FormatPodcast<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatPodcast { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatPodcast<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is Podcast"); //TODO: p_this->force ??? @@ -31,20 +33,28 @@ impl PlaylistFormat for FormatPodcast { return Ok(true); } +} + +impl DemuxControl for FormatPodcast<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading Podcast metafile"); +impl<'a> ReadDirDemux<'a> for FormatPodcast<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading Podcast metafile"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; let mut art_url: Option<String> = None; let root_input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 7adfb67d3c66..7d8d5e0a3dd0 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -6,12 +6,16 @@ use vlcrs_core::{ debug, error, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, }; use crate::{util_xml::XmlParser, InputContext, PlaylistFormat}; const ROOT_NODE_MAX_DEPTH: u32 = 2; -pub struct FormatQTL; +pub(crate) struct FormatQTL<'a> { + pub(crate) input: InputContext<'a>, +} #[derive(Debug)] enum QtlFullScreenType { @@ -29,8 +33,10 @@ enum QtlLoopType { LoopPalindrome, } -impl PlaylistFormat for FormatQTL { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatQTL<'_> { + const EXTENSION: &'static [&'static str] = &[".qtl"]; + + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is QTL"); if !input.has_extension(".qtl") { @@ -41,20 +47,28 @@ impl PlaylistFormat for FormatQTL { return Ok(true); } +} + +impl DemuxControl for FormatQTL<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Starting QTL parser"); +impl<'a> ReadDirDemux<'a> for FormatQTL<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Starting QTL parser"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; let _input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; @@ -108,35 +122,44 @@ impl PlaylistFormat for FormatQTL { "mimetype" => mime_type = Some(attr_val), "volume" => volume = attr_val.parse().map_err(|_| CoreError::Unknown)?, attr_name => debug!( - input.logger, + self.input.logger, "Attribute {attr_name} with value {attr_val} isn't valid" ), } } - debug!(input.logger, "autoplay: {autoplay:?} (unused by VLC)"); - debug!(input.logger, "controller: {controller:?} (unused by VLC)"); - debug!(input.logger, "fullscreen: {fullscreen:?} (unused by VLC)"); - debug!(input.logger, "href: {href:?}"); - debug!(input.logger, "kioskmode: {kiosk_mode:?} (unused by VLC)"); - debug!(input.logger, "loop: {loop_type:?} (unused by VLC)"); - debug!(input.logger, "movieid: {movie_id:?} (unused by VLC)"); - debug!(input.logger, "moviename: {movie_name:?}"); + debug!(self.input.logger, "autoplay: {autoplay:?} (unused by VLC)"); debug!( - input.logger, + self.input.logger, + "controller: {controller:?} (unused by VLC)" + ); + debug!( + self.input.logger, + "fullscreen: {fullscreen:?} (unused by VLC)" + ); + debug!(self.input.logger, "href: {href:?}"); + debug!( + self.input.logger, + "kioskmode: {kiosk_mode:?} (unused by VLC)" + ); + debug!(self.input.logger, "loop: {loop_type:?} (unused by VLC)"); + debug!(self.input.logger, "movieid: {movie_id:?} (unused by VLC)"); + debug!(self.input.logger, "moviename: {movie_name:?}"); + debug!( + self.input.logger, "playeverframe: {play_every_frame:?} (unused by VLC)" ); - debug!(input.logger, "qtnext: {qtnext:?}"); + debug!(self.input.logger, "qtnext: {qtnext:?}"); debug!( - input.logger, + self.input.logger, "quitwhendone: {quit_when_done:?} (unused by VLC)" ); - debug!(input.logger, "src: {src:?}"); - debug!(input.logger, "mimetype: {mime_type:?}"); - debug!(input.logger, "volume: {volume:?} (unused by VLC)"); + debug!(self.input.logger, "src: {src:?}"); + debug!(self.input.logger, "mimetype: {mime_type:?}"); + debug!(self.input.logger, "volume: {volume:?} (unused by VLC)"); if src.is_none() { - error!(input.logger, "Mandatory attribute 'src' not found"); + error!(self.input.logger, "Mandatory attribute 'src' not found"); return Err(CoreError::Unknown); } @@ -173,8 +196,4 @@ impl PlaylistFormat for FormatQTL { Ok(()) } - - fn get_extension(&self) -> &'static [&'static str] { - &[".qtl"] - } } diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index e70ac76a2921..d66170a8da56 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -5,7 +5,7 @@ use std::{io::Read, str}; use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, + input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, }; use crate::{ @@ -14,10 +14,14 @@ use crate::{ InputContext, PlaylistFormat, }; -pub struct FormatRAM; +pub(crate) struct FormatRAM<'a> { + pub(crate) input: InputContext<'a> +} + +impl FormatRAM<'_> { + const EXTENSION: &'static [&'static str] = &[".ram", ".rm"]; -impl PlaylistFormat for FormatRAM { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is RAM"); if !input.has_extension(".ram") || !input.has_extension(".rm") { @@ -35,22 +39,30 @@ impl PlaylistFormat for FormatRAM { return Ok(true); } +} + +impl DemuxControl for FormatRAM<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } - fn parse<'a>( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Starting RAM parser"); + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for FormatRAM<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Starting RAM parser"); let mut buf: String = String::new(); - input + self.input .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; for line in buf.lines() { - debug!(input.logger, "Line: {line}"); + debug!(self.input.logger, "Line: {line}"); let line_buf = line.as_bytes(); @@ -74,7 +86,7 @@ impl PlaylistFormat for FormatRAM { val => val, }; - let mrl = match process_mrl(media_path, input.path.as_deref()) { + let mrl = match process_mrl(media_path, self.input.path.as_deref()) { Ok(val) => val, Err(_) => continue, }; @@ -118,10 +130,6 @@ impl PlaylistFormat for FormatRAM { Ok(()) } - - fn get_extension(&self) -> &'static [&'static str] { - &[".ram", ".rm"] - } } fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index 0a960aa7d3b9..56b171202ff6 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -6,6 +6,7 @@ use vlcrs_core::{ debug, error, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Microseconds, Tick}, }; @@ -13,49 +14,32 @@ use crate::{ util_buf::{ find_in_buf, find_in_buf_ignore_case, match_ignore_case, read_int, read_to_end, skip_chars, }, - InputContext, PlaylistFormat, + InputContext, }; -pub(crate) struct FormatSGIMB { - pub uri: Option<String>, - pub server: Option<String>, - pub location: Option<String>, - pub name: Option<String>, - pub user: Option<String>, - pub password: Option<String>, - pub mcast_ip: Option<String>, - pub rtsp_kasenna: bool, - pub mcast_port: i32, - pub packet_size: i32, - pub duration: Tick, - pub port: i32, - pub sid: i32, - pub concert: bool, +pub(crate) struct FormatSGIMB<'a> { + pub(crate) input: InputContext<'a>, } -impl Default for FormatSGIMB { - fn default() -> Self { - Self { - uri: None, - server: None, - location: None, - name: None, - user: None, - password: None, - mcast_ip: None, - rtsp_kasenna: false, - mcast_port: 0, - packet_size: 0, - duration: Tick::ZERO, - port: 0, - sid: 0, - concert: false, - } - } +struct FormatMeta { + uri: Option<String>, + server: Option<String>, + location: Option<String>, + name: Option<String>, + user: Option<String>, + password: Option<String>, + mcast_ip: Option<String>, + rtsp_kasenna: bool, + mcast_port: i32, + packet_size: i32, + duration: Tick, + port: i32, + sid: i32, + concert: bool, } -impl PlaylistFormat for FormatSGIMB { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatSGIMB<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is SGIMB"); let peek = input.source.peek(1024)?; @@ -63,77 +47,102 @@ impl PlaylistFormat for FormatSGIMB { Ok(find_in_buf(buf, 0, "sgiNameServerHost=").is_some()) } +} + +impl DemuxControl for FormatSGIMB<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Read dir called"); +impl<'a> ReadDirDemux<'a> for FormatSGIMB<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Read dir called"); // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - input + self.input .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; + let mut meta = FormatMeta { + uri: None, + server: None, + location: None, + name: None, + user: None, + password: None, + mcast_ip: None, + rtsp_kasenna: false, + mcast_port: 0, + packet_size: 0, + duration: Tick::ZERO, + port: 0, + sid: 0, + concert: false, + }; + for line in buf.lines() { - debug!(input.logger, "Line: {line}"); - self.parse_line(line.as_bytes(), input, input_item_node)?; + debug!(self.input.logger, "Line: {line}"); + parse_line(line.as_bytes(), &mut meta)?; } - if let Some(mcast_ip) = &self.mcast_ip { - self.uri = Some(format!("udp://@{}:{}", mcast_ip, self.mcast_port)); + if let Some(mcast_ip) = &meta.mcast_ip { + meta.uri = Some(format!("udp://@{}:{}", mcast_ip, meta.mcast_port)); } - if self.uri.is_none() { - if let (Some(server), Some(location)) = (&self.server, &self.location) { - self.uri = Some(format!( + if meta.uri.is_none() { + if let (Some(server), Some(location)) = (&meta.server, &meta.location) { + meta.uri = Some(format!( "rtsp://{}:{}{}", server, - if self.port > 0 { self.port } else { 554 }, + if meta.port > 0 { meta.port } else { 554 }, location )); } } - if self.concert { - if let Some(uri) = &self.uri { - self.uri = Some(format!( + if meta.concert { + if let Some(uri) = &meta.uri { + meta.uri = Some(format!( "{}%3FMeDiAbAsEshowingId={}%26MeDiAbAsEconcert%3FMeDiAbAsE", - uri, self.sid + uri, meta.sid )); } else { - error!(input.logger, "No URI was found."); + error!(self.input.logger, "No URI was found."); return Err(CoreError::Unknown); } } let mut child = InputItem::empty()?; - child.set_uri(self.uri.as_deref()); - child.set_name(match &self.name { - Some(_) => self.name.as_deref(), - None => self.uri.as_deref(), + child.set_uri(meta.uri.as_deref()); + child.set_name(match &meta.name { + Some(_) => meta.name.as_deref(), + None => meta.uri.as_deref(), }); - child.set_duration(self.duration); + child.set_duration(meta.duration); - if self.packet_size != 0 && self.mcast_ip.is_some() { - let option = format!("mtu={}", self.packet_size); + if meta.packet_size != 0 && meta.mcast_ip.is_some() { + let option = format!("mtu={}", meta.packet_size); child.add_option( &option, vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, )?; } - if self.mcast_ip.is_none() { + if meta.mcast_ip.is_none() { child.add_option( "rtsp-caching=5000", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, )?; } - if self.mcast_ip.is_none() && self.rtsp_kasenna { + if meta.mcast_ip.is_none() && meta.rtsp_kasenna { child.add_option( "rtsp-kasenna", vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_TRUSTED as u32, @@ -146,77 +155,73 @@ impl PlaylistFormat for FormatSGIMB { } } -impl FormatSGIMB { - #[allow(unused_assignments)] - fn parse_line( - &mut self, - line: &[u8], - _input: &mut InputContext, - _input_item_node: &mut InputItemNode, - ) -> Result<()> { - let mut buf_i = 0; - skip_chars(line, &mut buf_i, " \t\n\r"); - let start_pos = buf_i; - - match_ignore_case!(line, buf_i, { - "rtsp://" => { - self.uri = Some(read_to_end(line, start_pos)?.to_string()); - }, - "Stream=" => { - if let Some(end) = find_in_buf(line, buf_i, "\"") { - if starts_with_ignore_case(line, buf_i, "xdma://") { - self.uri = Some( - String::from("rtsp") + - std::str::from_utf8(&line[buf_i..end]).map_err(|_|CoreError::Unknown)? - ); - } else { - self.uri = Some( - String::from_utf8(line[buf_i..end].to_vec()).map_err(|_|CoreError::Unknown)? - ); - } +#[allow(unused_assignments)] +fn parse_line( + line: &[u8], + meta: &mut FormatMeta +) -> Result<()> { + let mut buf_i = 0; + skip_chars(line, &mut buf_i, " \t\n\r"); + let start_pos = buf_i; + + match_ignore_case!(line, buf_i, { + "rtsp://" => { + meta.uri = Some(read_to_end(line, start_pos)?.to_string()); + }, + "Stream=" => { + if let Some(end) = find_in_buf(line, buf_i, "\"") { + if starts_with_ignore_case(line, buf_i, "xdma://") { + meta.uri = Some( + String::from("rtsp") + + std::str::from_utf8(&line[buf_i..end]).map_err(|_|CoreError::Unknown)? + ); + } else { + meta.uri = Some( + String::from_utf8(line[buf_i..end].to_vec()).map_err(|_|CoreError::Unknown)? + ); } - }, - "sgiNameServerHost=" => { - self.server = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiMovieName=" => { - self.location = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiUserAccount=" => { - self.user = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiUserPassword=" => { - self.password = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiShowingName=" => { - self.name = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiFormatName=" => { - self.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4").is_some(); - }, - "sgiMulticastAddress=" => { - self.mcast_ip = Some(read_to_end(line, buf_i)?.to_string()); - }, - "sgiMulticastPort=" => { - self.mcast_port = read_int(line, &mut buf_i).unwrap_or(0); - }, - "sgiPacketSize=" => { - self.packet_size = read_int(line, &mut buf_i).unwrap_or(0); - }, - "sgiDuration=" => { - self.duration = Tick::from_microseconds(Microseconds::from(read_int(line, &mut buf_i).unwrap_or(0))); - }, - "sgiRtspPort=" => { - self.port = read_int(line, &mut buf_i).unwrap_or(0); - }, - "sgiSid=" => { - self.sid = read_int(line, &mut buf_i).unwrap_or(0); - }, - "DeliveryService=cds" => { - self.concert = true; - }, - }); - - Ok(()) - } + } + }, + "sgiNameServerHost=" => { + meta.server = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiMovieName=" => { + meta.location = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiUserAccount=" => { + meta.user = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiUserPassword=" => { + meta.password = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiShowingName=" => { + meta.name = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiFormatName=" => { + meta.rtsp_kasenna = find_in_buf_ignore_case(line, buf_i, "MPEG-4").is_some(); + }, + "sgiMulticastAddress=" => { + meta.mcast_ip = Some(read_to_end(line, buf_i)?.to_string()); + }, + "sgiMulticastPort=" => { + meta.mcast_port = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiPacketSize=" => { + meta.packet_size = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiDuration=" => { + meta.duration = Tick::from_microseconds(Microseconds::from(read_int(line, &mut buf_i).unwrap_or(0))); + }, + "sgiRtspPort=" => { + meta.port = read_int(line, &mut buf_i).unwrap_or(0); + }, + "sgiSid=" => { + meta.sid = read_int(line, &mut buf_i).unwrap_or(0); + }, + "DeliveryService=cds" => { + meta.concert = true; + }, + }); + + Ok(()) } diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 54965f8cedb5..aee214f26904 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -6,15 +6,17 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - warn, + warn, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, }; -use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; +use crate::{util_buf::starts_with_ignore_case, InputContext}; -pub struct FormatWMS; +pub(crate) struct FormatWMS<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatWMS { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatWMS<'_> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is WMS"); let peek = input.source.peek(1024)?; let buf = peek.buf(); @@ -27,23 +29,31 @@ impl PlaylistFormat for FormatWMS { return Ok(true); } +} + +impl DemuxControl for FormatWMS<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} - fn parse<'a>( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading WMS metafile"); +impl<'a> ReadDirDemux<'a> for FormatWMS<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading WMS metafile"); // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); - input + self.input .source .read_to_string(&mut buf) .map_err(|_| CoreError::Unknown)?; for line in buf.lines() { - debug!(input.logger, "Line: {line}"); + debug!(self.input.logger, "Line: {line}"); if line == "[Reference]" { continue; @@ -51,12 +61,12 @@ impl PlaylistFormat for FormatWMS { if let Some((key, value_str)) = line.split_once('=') { if value_str.is_empty() { - warn!(input.logger, "unexpected entry value \"{line}\""); + warn!(self.input.logger, "unexpected entry value \"{line}\""); continue; } if !key.starts_with("Ref") || key[3..].parse::<u32>().is_err() { - warn!(input.logger, "unexpected entry value \"{line}\""); + warn!(self.input.logger, "unexpected entry value \"{line}\""); continue; } diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs index 7f051c59b169..265adf379a02 100644 --- a/modules/demux/playlist-rs/src/fmt_wpl.rs +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -6,19 +6,25 @@ use vlcrs_core::{ debug, error, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, warn, }; use xml::attribute::OwnedAttribute; use crate::{ util_xml::{find_next_element_from_buf, parse_node, XmlParser}, - InputContext, PlaylistFormat, + InputContext, }; -pub struct FormatWPL; +pub(crate) struct FormatWPL<'a> { + pub(crate) input: InputContext<'a>, +} + +impl FormatWPL<'_> { + const EXTENSION: &'static [&'static str] = &[".wpl", ".zpl"]; -impl PlaylistFormat for FormatWPL { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is WPL"); if !input.has_extension(".wpl") && !input.has_extension(".zpl") { @@ -29,23 +35,28 @@ impl PlaylistFormat for FormatWPL { // Should be an XML file & Root element should be smil return find_next_element_from_buf(peek.buf(), input.logger, "smil"); } +} - fn parse( - &mut self, - input: &mut InputContext, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Starting WPL parser"); +impl DemuxControl for FormatWPL<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } - let InputContext { - input_item: input_item_opt, - logger, - source, - path, - } = input; + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for FormatWPL<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Starting WPL parser"); - let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; - let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; + let input_item = self.input.input_item.as_mut().ok_or(CoreError::Unknown)?; + let mut xml_parser = XmlParser::new( + self.input.logger, + self.input.source, + self.input.path.to_owned(), + )?; xml_parser.find_next_element("smil", 1)?; @@ -56,10 +67,6 @@ impl PlaylistFormat for FormatWPL { Ok(()) } - - fn get_extension(&self) -> &'static [&'static str] { - &[".wpl", ".zpl"] - } } fn read_head( diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index 50f9bce0d9b5..d8d8b18fa78f 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -5,21 +5,25 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{self, InputItem, InputItemNode}, tick::{Miliseconds, Tick}, - warn, + warn, stream::{DemuxControl, Stream, ReadDirDemux}, }; use xml::attribute::OwnedAttribute; use crate::{ util_xml::{get_required_attrib, parse_node, XmlParser}, - InputContext, PlaylistFormat, + InputContext, }; const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; -pub struct FormatXSPF; +pub(crate) struct FormatXSPF<'a> { + pub(crate) input: InputContext<'a> +} -impl PlaylistFormat for FormatXSPF { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool> { +impl FormatXSPF<'_> { + const EXTENSION: &'static [&'static str] = &[".xspf"]; + + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is XSPF"); if !input.has_extension(".xspf") && !input.has_mime_type("application/xspf+xml") { @@ -29,20 +33,28 @@ impl PlaylistFormat for FormatXSPF { debug!(input.logger, "Found XSPF metafile"); return Ok(true); } +} - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()> { - debug!(input.logger, "Reading XSPF metafile"); +impl DemuxControl for FormatXSPF<'_> { + fn source_stream(&mut self) -> Option<&mut Stream> { + Some(self.input.source) + } + + fn time(&mut self) -> vlcrs_core::tick::Tick { + Tick::ZERO + } +} + +impl<'a> ReadDirDemux<'a> for FormatXSPF<'a> { + fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { + debug!(self.input.logger, "Reading XSPF metafile"); let InputContext { input_item: input_item_opt, logger, source, path, - } = input; + } = &mut self.input; let input_item = input_item_opt.as_mut().ok_or(CoreError::Unknown)?; let mut xml_parser = XmlParser::new(logger, source, path.to_owned())?; @@ -60,10 +72,6 @@ impl PlaylistFormat for FormatXSPF { Ok(()) } - - fn get_extension(&self) -> &'static [&'static str] { - &[".xspf"] - } } fn parse_extension_node( diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 0de3cee81345..03c7f7ee6506 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,6 +1,9 @@ //! Playlist-rs Demux module use std::path::Path; +use fmt_b4s::FormatB4S; +use fmt_ifo::IFOKind; +use fmt_wms::FormatWMS; use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -76,65 +79,118 @@ impl Module for Playlist { input_item: Option<&'a mut InputItem>, _args: &mut ModuleArgs, ) -> Result<DemuxModule<'a>> { - debug!(logger, "Entering playlist-rs open"); - let mut formats: Vec<Box<dyn PlaylistFormat>> = vec![ - Box::new(FormatM3U {}), - Box::new(FormatPLS {}), - Box::new(FormatWPL {}), - Box::new(FormatQTL {}), - Box::new(FormatXSPF {}), - Box::new(FormatSGIMB { - ..Default::default() - }), - Box::new(FormatPodcast {}), - Box::new(FormatITML {}), - Box::new(FormatASX {}), - Box::new(FormatBDMV {}), - Box::new(FormatIFO { kind: None }), - Box::new(FormatRAM {}), - ]; - - let mut high_prior = 0; let source_path = match this_demux.url() { Some(result) => Some(result?), None => None, }; - - if let Some(path) = source_path { - for format_idx in 0..formats.len() { - if formats[format_idx] - .get_extension() - .iter() - .any(|ext| path[path.len() - ext.len()..].eq_ignore_ascii_case(ext)) - { - formats.swap(high_prior, format_idx); - high_prior += 1; - } - } - } - - let mut input_ctx: InputContext<'a> = InputContext { + let mut input: InputContext = InputContext { source, logger, path: source_path.map(str::to_string), input_item, }; + + if let Ok(true) = FormatM3U::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatM3U { input }))); + } else if let Ok(true) = FormatPLS::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatPLS { input }))); + } else if let Ok(true) = FormatWPL::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatWPL { input }))); + } else if let Ok(true) = FormatQTL::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatQTL { input }))); + } else if let Ok(true) = FormatXSPF::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatXSPF { input }))); + } else if let Ok(true) = FormatSGIMB::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatSGIMB { input }))); + } else if let Ok(true) = FormatPodcast::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatPodcast { input }))); + } else if let Ok(true) = FormatITML::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatITML { input }))); + } else if let Ok(true) = FormatASX::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatASX { input }))); + } else if let Ok(true) = FormatBDMV::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatBDMV { input }))); + } else if let Ok(kind) = FormatIFO::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatIFO { kind, input }))); + } else if let Ok(true) = FormatRAM::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatRAM { input }))); + } else if let Ok(true) = FormatWMS::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatWMS { input }))); + } else if let Ok(true) = FormatB4S::open(&mut input) { + return Ok(DemuxModule::ReadDir(Box::new(FormatB4S { input }))); + } + + Err(CoreError::Unknown) + } +} - for mut format in formats.into_iter() { - if let Ok(true) = format.can_open(&mut input_ctx) { - return Ok(DemuxModule::ReadDir(Box::new(PlaylistDemuxer { - input: input_ctx, - parser: format, - }))); - } else { - debug!(input_ctx.logger, "Failed to open format") +fn open_old<'a>( + mut this_demux: ThisDemux<'a>, + source: &'a mut Stream, + _es_out: &'a mut EsOut, + logger: &'a mut Logger, + input_item: Option<&'a mut InputItem>, + _args: &mut ModuleArgs, +) -> Result<DemuxModule<'a>> { + debug!(logger, "Entering playlist-rs open"); + let mut formats: Vec<Box<dyn PlaylistFormat>> = vec![ + // Box::new(FormatM3U {}), + // Box::new(FormatPLS {}), + // Box::new(FormatWPL {}), + // Box::new(FormatQTL {}), + // Box::new(FormatXSPF {}), + // Box::new(FormatSGIMB { + // ..Default::default() + // }), + // Box::new(FormatPodcast {}), + // Box::new(FormatITML {}), + // Box::new(FormatASX {}), + // Box::new(FormatBDMV {}), + // Box::new(FormatIFO { kind: None }), + // Box::new(FormatRAM {}), + ]; + + let mut high_prior = 0; + let source_path = match this_demux.url() { + Some(result) => Some(result?), + None => None, + }; + + if let Some(path) = source_path { + for format_idx in 0..formats.len() { + if formats[format_idx] + .get_extension() + .iter() + .any(|ext| path[path.len() - ext.len()..].eq_ignore_ascii_case(ext)) + { + formats.swap(high_prior, format_idx); + high_prior += 1; } } + } - debug!(input_ctx.logger, "No format opened"); - Err(CoreError::Unknown) + let mut input_ctx: InputContext<'a> = InputContext { + source, + logger, + path: source_path.map(str::to_string), + input_item, + }; + + for mut format in formats.into_iter() { + if let Ok(true) = format.can_open(&mut input_ctx) { + return Ok(DemuxModule::ReadDir(Box::new(PlaylistDemuxer { + input: input_ctx, + parser: format, + }))); + } else { + debug!(input_ctx.logger, "Failed to open format") + } } + + debug!(input_ctx.logger, "No format opened"); + Err(CoreError::Unknown) } +// } impl DemuxControl for PlaylistDemuxer<'_> { fn source_stream(&mut self) -> Option<&mut Stream> { -- GitLab From 7b7b9863e6801d7c9274fa08471695d6da5633d0 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Tue, 22 Aug 2023 00:00:44 -0600 Subject: [PATCH 20/24] formatted --- modules/demux/playlist-rs/src/fmt_asx.rs | 7 +- modules/demux/playlist-rs/src/fmt_b4s.rs | 6 +- modules/demux/playlist-rs/src/fmt_bdmv.rs | 8 +- modules/demux/playlist-rs/src/fmt_ifo.rs | 9 +- modules/demux/playlist-rs/src/fmt_itml.rs | 7 +- modules/demux/playlist-rs/src/fmt_m3u.rs | 5 +- modules/demux/playlist-rs/src/fmt_pls.rs | 16 ++- modules/demux/playlist-rs/src/fmt_podcast.rs | 7 +- modules/demux/playlist-rs/src/fmt_qtl.rs | 2 +- modules/demux/playlist-rs/src/fmt_ram.rs | 8 +- modules/demux/playlist-rs/src/fmt_sgimb.rs | 5 +- modules/demux/playlist-rs/src/fmt_wms.rs | 6 +- modules/demux/playlist-rs/src/fmt_xspf.rs | 7 +- modules/demux/playlist-rs/src/lib.rs | 111 +------------------ 14 files changed, 58 insertions(+), 146 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index e5e24df076c7..d618af98907d 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -6,18 +6,19 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::Tick, - warn, stream::{DemuxControl, Stream, ReadDirDemux}, + warn, }; use crate::{ util_buf::starts_with_ignore_case, util_xml::{get_required_attrib, parse_node, process_mrl, XmlParser}, - InputContext, PlaylistFormat, fmt_m3u::FormatM3U, + InputContext, }; pub(crate) struct FormatASX<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatASX<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_b4s.rs b/modules/demux/playlist-rs/src/fmt_b4s.rs index 43a0cea1a3a2..acb7a2c3633c 100644 --- a/modules/demux/playlist-rs/src/fmt_b4s.rs +++ b/modules/demux/playlist-rs/src/fmt_b4s.rs @@ -6,7 +6,9 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - warn, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + warn, }; use crate::{ @@ -15,7 +17,7 @@ use crate::{ }; pub(crate) struct FormatB4S<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatB4S<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_bdmv.rs b/modules/demux/playlist-rs/src/fmt_bdmv.rs index a58a4d0eb7b1..a62ec1ec337d 100644 --- a/modules/demux/playlist-rs/src/fmt_bdmv.rs +++ b/modules/demux/playlist-rs/src/fmt_bdmv.rs @@ -5,13 +5,15 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, + input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, }; -use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; +use crate::{util_buf::starts_with_ignore_case, InputContext}; pub(crate) struct FormatBDMV<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatBDMV<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index 0a6722b7af33..cfc5f7f0362d 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -5,10 +5,12 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, + input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, }; -use crate::{util_buf::starts_with_ignore_case, InputContext, PlaylistFormat}; +use crate::{util_buf::starts_with_ignore_case, InputContext}; #[derive(Debug, Clone, Copy)] pub enum IFOKind { @@ -87,8 +89,7 @@ impl<'a> ReadDirDemux<'a> for FormatIFO<'a> { let item_name = name + "VR_MOVIE.VRO"; let input_item = InputItem::new(item_name.clone(), item_name)?; input_item_node.append_item(&input_item)?; - } - // IFOKind::Invalid => return Err(CoreError::Unknown), + } // IFOKind::Invalid => return Err(CoreError::Unknown), } Ok(()) diff --git a/modules/demux/playlist-rs/src/fmt_itml.rs b/modules/demux/playlist-rs/src/fmt_itml.rs index 2ef4f62bf1f5..c6ed9e3309c1 100644 --- a/modules/demux/playlist-rs/src/fmt_itml.rs +++ b/modules/demux/playlist-rs/src/fmt_itml.rs @@ -33,18 +33,19 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Microseconds, Tick}, - warn, stream::{ReadDirDemux, DemuxControl, Stream}, + warn, }; use crate::{ util_buf::starts_with_ignore_case, util_xml::{parse_node, XmlParser}, - InputContext, PlaylistFormat, + InputContext, }; pub(crate) struct FormatITML<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatITML<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index f7625acfd9a8..f19e0379e6a2 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -10,7 +10,8 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, module::demux::DemuxModule, - tick::{Seconds, Tick}, stream::{ReadDirDemux, DemuxControl, Stream}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::{Seconds, Tick}, }; use crate::{ @@ -49,7 +50,7 @@ impl<'a> Default for InputItemMeta<'a> { } pub(crate) struct FormatM3U<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatM3U<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index dc80d197c6d4..58ab7a07eeba 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -5,13 +5,15 @@ use std::io::Read; use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, + input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, }; use crate::InputContext; pub(crate) struct FormatPLS<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatPLS<'_> { @@ -35,7 +37,6 @@ impl FormatPLS<'_> { } } - impl DemuxControl for FormatPLS<'_> { fn source_stream(&mut self) -> Option<&mut Stream> { Some(self.input.source) @@ -75,7 +76,9 @@ impl<'a> ReadDirDemux<'a> for FormatPLS<'a> { match psz_key { "version" => debug!(self.input.logger, "pls file version: {psz_value}"), - "numberofentries" => debug!(self.input.logger, "pls should have {psz_value} entries"), + "numberofentries" => { + debug!(self.input.logger, "pls should have {psz_value} entries") + } _ => { let (_, i_new_item_str) = psz_key.rsplit_once(|c: char| !c.is_ascii_digit()).unwrap(); @@ -110,7 +113,10 @@ impl<'a> ReadDirDemux<'a> for FormatPLS<'a> { } "length" => {} _ => { - debug!(self.input.logger, "unknown key found in pls file: {key_name}"); + debug!( + self.input.logger, + "unknown key found in pls file: {key_name}" + ); } }; } diff --git a/modules/demux/playlist-rs/src/fmt_podcast.rs b/modules/demux/playlist-rs/src/fmt_podcast.rs index 4dab183c1eff..acf36933cadb 100644 --- a/modules/demux/playlist-rs/src/fmt_podcast.rs +++ b/modules/demux/playlist-rs/src/fmt_podcast.rs @@ -4,17 +4,18 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode, MetaType::*}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Seconds, Tick}, - warn, stream::{DemuxControl, Stream, ReadDirDemux}, + warn, }; use crate::{ util_xml::{find_next_element_from_buf, parse_node, XmlParser}, - InputContext, PlaylistFormat, + InputContext, }; pub(crate) struct FormatPodcast<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatPodcast<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 7d8d5e0a3dd0..303d3e6bf239 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -10,7 +10,7 @@ use vlcrs_core::{ tick::Tick, }; -use crate::{util_xml::XmlParser, InputContext, PlaylistFormat}; +use crate::{util_xml::XmlParser, InputContext}; const ROOT_NODE_MAX_DEPTH: u32 = 2; pub(crate) struct FormatQTL<'a> { diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index d66170a8da56..9cbc227e9847 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -5,17 +5,19 @@ use std::{io::Read, str}; use vlcrs_core::{ debug, error::{CoreError, Result}, - input_item::{InputItem, InputItemNode}, stream::{ReadDirDemux, DemuxControl, Stream}, tick::Tick, + input_item::{InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, }; use crate::{ util_buf::{read_int, read_to_end, skip_chars}, util_xml::process_mrl, - InputContext, PlaylistFormat, + InputContext, }; pub(crate) struct FormatRAM<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatRAM<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index 56b171202ff6..e68439429c2b 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -156,10 +156,7 @@ impl<'a> ReadDirDemux<'a> for FormatSGIMB<'a> { } #[allow(unused_assignments)] -fn parse_line( - line: &[u8], - meta: &mut FormatMeta -) -> Result<()> { +fn parse_line(line: &[u8], meta: &mut FormatMeta) -> Result<()> { let mut buf_i = 0; skip_chars(line, &mut buf_i, " \t\n\r"); let start_pos = buf_i; diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index aee214f26904..334c569c0905 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -6,13 +6,15 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, - warn, stream::{DemuxControl, Stream, ReadDirDemux}, tick::Tick, + stream::{DemuxControl, ReadDirDemux, Stream}, + tick::Tick, + warn, }; use crate::{util_buf::starts_with_ignore_case, InputContext}; pub(crate) struct FormatWMS<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatWMS<'_> { diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index d8d8b18fa78f..36aab3a64746 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -4,8 +4,9 @@ use vlcrs_core::{ debug, error::{CoreError, Result}, input_item::{self, InputItem, InputItemNode}, + stream::{DemuxControl, ReadDirDemux, Stream}, tick::{Miliseconds, Tick}, - warn, stream::{DemuxControl, Stream, ReadDirDemux}, + warn, }; use xml::attribute::OwnedAttribute; @@ -17,12 +18,12 @@ use crate::{ const INPUT_ITEM_URI_NOP: &str = "vlc://nop"; pub(crate) struct FormatXSPF<'a> { - pub(crate) input: InputContext<'a> + pub(crate) input: InputContext<'a>, } impl FormatXSPF<'_> { const EXTENSION: &'static [&'static str] = &[".xspf"]; - + pub(crate) fn open(input: &mut InputContext) -> Result<bool> { debug!(input.logger, "Testing if file is XSPF"); diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 03c7f7ee6506..242817e82833 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,9 +1,9 @@ //! Playlist-rs Demux module -use std::path::Path; use fmt_b4s::FormatB4S; use fmt_ifo::IFOKind; use fmt_wms::FormatWMS; +use std::path::Path; use vlcrs_core::{ debug, error::{CoreError, Result}, @@ -49,25 +49,6 @@ struct InputContext<'a> { input_item: Option<&'a mut InputItem>, } -pub struct PlaylistDemuxer<'a> { - input: InputContext<'a>, - parser: Box<dyn PlaylistFormat>, -} - -trait PlaylistFormat { - fn can_open(&mut self, input: &mut InputContext) -> Result<bool>; - - fn parse<'a>( - &'a mut self, - input: &'a mut InputContext<'a>, - input_item_node: &mut InputItemNode, - ) -> Result<()>; - - fn get_extension(&self) -> &'static [&'static str] { - &[] - } -} - struct Playlist; impl Module for Playlist { @@ -89,7 +70,7 @@ impl Module for Playlist { path: source_path.map(str::to_string), input_item, }; - + if let Ok(true) = FormatM3U::open(&mut input) { return Ok(DemuxModule::ReadDir(Box::new(FormatM3U { input }))); } else if let Ok(true) = FormatPLS::open(&mut input) { @@ -119,94 +100,8 @@ impl Module for Playlist { } else if let Ok(true) = FormatB4S::open(&mut input) { return Ok(DemuxModule::ReadDir(Box::new(FormatB4S { input }))); } - - Err(CoreError::Unknown) - } -} - -fn open_old<'a>( - mut this_demux: ThisDemux<'a>, - source: &'a mut Stream, - _es_out: &'a mut EsOut, - logger: &'a mut Logger, - input_item: Option<&'a mut InputItem>, - _args: &mut ModuleArgs, -) -> Result<DemuxModule<'a>> { - debug!(logger, "Entering playlist-rs open"); - let mut formats: Vec<Box<dyn PlaylistFormat>> = vec![ - // Box::new(FormatM3U {}), - // Box::new(FormatPLS {}), - // Box::new(FormatWPL {}), - // Box::new(FormatQTL {}), - // Box::new(FormatXSPF {}), - // Box::new(FormatSGIMB { - // ..Default::default() - // }), - // Box::new(FormatPodcast {}), - // Box::new(FormatITML {}), - // Box::new(FormatASX {}), - // Box::new(FormatBDMV {}), - // Box::new(FormatIFO { kind: None }), - // Box::new(FormatRAM {}), - ]; - let mut high_prior = 0; - let source_path = match this_demux.url() { - Some(result) => Some(result?), - None => None, - }; - - if let Some(path) = source_path { - for format_idx in 0..formats.len() { - if formats[format_idx] - .get_extension() - .iter() - .any(|ext| path[path.len() - ext.len()..].eq_ignore_ascii_case(ext)) - { - formats.swap(high_prior, format_idx); - high_prior += 1; - } - } - } - - let mut input_ctx: InputContext<'a> = InputContext { - source, - logger, - path: source_path.map(str::to_string), - input_item, - }; - - for mut format in formats.into_iter() { - if let Ok(true) = format.can_open(&mut input_ctx) { - return Ok(DemuxModule::ReadDir(Box::new(PlaylistDemuxer { - input: input_ctx, - parser: format, - }))); - } else { - debug!(input_ctx.logger, "Failed to open format") - } - } - - debug!(input_ctx.logger, "No format opened"); - Err(CoreError::Unknown) -} -// } - -impl DemuxControl for PlaylistDemuxer<'_> { - fn source_stream(&mut self) -> Option<&mut Stream> { - Some(self.input.source) - } - - fn time(&mut self) -> vlcrs_core::tick::Tick { - Tick::ZERO - } -} - -impl<'a> ReadDirDemux<'a> for PlaylistDemuxer<'a> { - fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { - debug!(self.input.logger, "Reading playlist"); - self.parser.parse(&mut self.input, input_item_node)?; - Ok(()) + Err(CoreError::Unknown) } } -- GitLab From b208ef99f2626f46727f45fa6c5bf6f3bb48c9d8 Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Wed, 23 Aug 2023 23:51:00 -0600 Subject: [PATCH 21/24] Fixed comments --- modules/demux/playlist-rs/src/fmt_asx.rs | 54 ++++++++++++++++++++-- modules/demux/playlist-rs/src/fmt_ifo.rs | 16 ++++++- modules/demux/playlist-rs/src/fmt_m3u.rs | 26 ++++++++--- modules/demux/playlist-rs/src/fmt_pls.rs | 2 +- modules/demux/playlist-rs/src/fmt_ram.rs | 20 ++++++-- modules/demux/playlist-rs/src/fmt_sgimb.rs | 4 +- modules/demux/playlist-rs/src/fmt_wms.rs | 6 ++- modules/demux/playlist-rs/src/lib.rs | 5 +- 8 files changed, 110 insertions(+), 23 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index d618af98907d..89537db8988c 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -7,7 +7,7 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, stream::{DemuxControl, ReadDirDemux, Stream}, - tick::Tick, + tick::{Tick, Microseconds}, warn, }; @@ -178,7 +178,53 @@ fn parse_entry( Ok(()) } -fn parse_time(_s: &str) -> Result<Tick> { - // TODO: parse_time not implemented - todo!("parse_time not implemented") +fn parse_time(s: &str) -> Result<Tick> { + let mut result = 0; + let mut subresult = 0; + let mut subfractions = -1; + + for c in s.chars() { + if c.is_ascii_digit() { + subresult += 10; + subresult += c as i32 - 48; + + if subfractions != -1 { + subfractions += 1; + } + } else if c == ':' { + result += subresult; + result *= 60; + subresult = 0; + } else if c == '.' { + subfractions = 0; + result += subresult; + subresult = 0; + } else { + return Err(CoreError::Unknown); + } + } + + if subfractions == -1 { + result += subresult; + } + + /* Convert to microseconds */ + if subfractions == -1 { + subfractions = 0; + } + + while subfractions < 6 + { + subresult = subresult * 10; + subfractions += 1; + } + + //TODO: i_result = i_result * CLOCK_FREQ; + result *= 1000000; + + if subfractions != -1 { + result += subresult; + } + + Ok(Tick::from_microseconds(Microseconds::from(result))) } diff --git a/modules/demux/playlist-rs/src/fmt_ifo.rs b/modules/demux/playlist-rs/src/fmt_ifo.rs index cfc5f7f0362d..7db72645be51 100644 --- a/modules/demux/playlist-rs/src/fmt_ifo.rs +++ b/modules/demux/playlist-rs/src/fmt_ifo.rs @@ -37,7 +37,15 @@ impl FormatIFO<'_> { let file = path .get(path.len() - FILENAME_LEN..) .ok_or(CoreError::Unknown)?; - if file[..8].eq_ignore_ascii_case("VIDEO_TS") || file[..4].eq_ignore_ascii_case("VTS_") { + if file + .get(..8) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case("VIDEO_TS") + || file + .get(..4) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case("VTS_") + { // Valid filenames are : // - VIDEO_TS.IFO // - VTS_XX_X.IFO where X are digits @@ -45,7 +53,11 @@ impl FormatIFO<'_> { if starts_with_ignore_case(peek.buf(), 0, "DVDVIDEO") { return Ok(IFOKind::Dvd); } - } else if file[..8].eq_ignore_ascii_case("VR_MANGR") { + } else if file + .get(..8) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case("VR_MANGR") + { // Valid filename for DVD-VR is VR_MANGR.IdsfFO let peek = input.source.peek(PREFIX_LEN)?; if starts_with_ignore_case(peek.buf(), 0, "DVD_RTR_") { diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index f19e0379e6a2..da18ef7bfb96 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -66,17 +66,20 @@ impl FormatM3U<'_> { } // UTF-8 Byte Order Mark - if &buf[..3] == &[0xEF, 0xBB, 0xBF] { + if &buf.get(..3).ok_or(CoreError::Unknown)? == &[0xEF, 0xBB, 0xBF] { if buf.len() < 12 { debug!(input.logger, "Invalid buffer length {}", buf.len()); return Err(CoreError::Unknown); } // offset = 3; - buf = &buf[3..]; + buf = &buf.get(3..).ok_or(CoreError::Unknown)?; } - if !buf.starts_with("#EXTM3U".as_bytes()) - && !buf[..8].eq_ignore_ascii_case("RTSPtext".as_bytes()) + if !buf.starts_with(b"#EXTM3U") + && !buf + .get(..8) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case(b"RTSPtext") && !contains_url(buf)? && !input.has_mime_type("application/mpegurl") && !input.has_mime_type("application/x-mpegurl") @@ -238,12 +241,18 @@ fn parse_extinf_title<'a>( return Ok(( if artist_end > buf_i { - Some(str::from_utf8(&buf[buf_i..artist_end]).map_err(|_| CoreError::Unknown)?) + Some( + str::from_utf8(&buf.get(buf_i..artist_end).ok_or(CoreError::Unknown)?) + .map_err(|_| CoreError::Unknown)?, + ) } else { None }, if name_start < buf.len() { - Some(str::from_utf8(&buf[name_start..]).map_err(|_| CoreError::Unknown)?) + Some( + str::from_utf8(&buf.get(name_start..).ok_or(CoreError::Unknown)?) + .map_err(|_| CoreError::Unknown)?, + ) } else { None }, @@ -330,7 +339,10 @@ fn parse_extinf_iptv_diots<'a>( val_start += 1; val_end -= 1; } - let val = Some(str::from_utf8(&buf[val_start..=val_end]).map_err(|_| CoreError::Unknown)?); + let val = Some( + str::from_utf8(&buf.get(val_start..=val_end).ok_or(CoreError::Unknown)?) + .map_err(|_| CoreError::Unknown)?, + ); if starts_with_ignore_case(buf, key_start, "tvg-logo") { meta.album_art = val; diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index 58ab7a07eeba..45f5832f66c4 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -26,7 +26,7 @@ impl FormatPLS<'_> { return Err(CoreError::Unknown); } - let header = std::str::from_utf8(&buf[..10])?; + let header = std::str::from_utf8(&buf.get(..10).ok_or(CoreError::Unknown)?)?; if !header.eq_ignore_ascii_case("[playlist]") && !input.has_extension(".pls") { debug!(input.logger, "Not a valid PLS playlist file"); diff --git a/modules/demux/playlist-rs/src/fmt_ram.rs b/modules/demux/playlist-rs/src/fmt_ram.rs index 9cbc227e9847..a3a19bb84d6f 100644 --- a/modules/demux/playlist-rs/src/fmt_ram.rs +++ b/modules/demux/playlist-rs/src/fmt_ram.rs @@ -96,14 +96,18 @@ impl<'a> ReadDirDemux<'a> for FormatRAM<'a> { let mut input = InputItem::new(mrl.clone(), mrl.clone())?; if let Some(option_start) = mrl.find('?') { - for option in mrl[option_start + 1..].split('&') { + for option in mrl + .get(option_start + 1..) + .ok_or(CoreError::Unknown)? + .split('&') + { let (param, value) = match option.split_once('=') { Some(val) => val, None => break, }; match param { - "clipinfo" => parse_clipinfo(value, &mut input), + "clipinfo" => parse_clipinfo(value, &mut input)?, "author" => input.set_publisher(Some(value)), // TODO: EnsureUTF8 "start" => { if mrl.starts_with("rtsp") { @@ -134,13 +138,17 @@ impl<'a> ReadDirDemux<'a> for FormatRAM<'a> { } } -fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { +fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) -> Result<()> { let option_start = match clipinfo.find('"') { Some(val) => val + 1, - None => return, + None => return Ok(()), }; - for option in clipinfo[option_start..].split(&['|', '"']) { + for option in clipinfo + .get(option_start..) + .ok_or(CoreError::Unknown)? + .split(&['|', '"']) + { let (param, value) = match option.split_once('=') { Some(val) => val, None => break, @@ -156,6 +164,8 @@ fn parse_clipinfo(clipinfo: &str, input: &mut InputItem) { _ => {} } } + + Ok(()) } fn parse_time(buf: &[u8]) -> i32 { diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index e68439429c2b..f4c2ff3e494a 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -170,11 +170,11 @@ fn parse_line(line: &[u8], meta: &mut FormatMeta) -> Result<()> { if starts_with_ignore_case(line, buf_i, "xdma://") { meta.uri = Some( String::from("rtsp") + - std::str::from_utf8(&line[buf_i..end]).map_err(|_|CoreError::Unknown)? + std::str::from_utf8(&line.get(buf_i..end).ok_or(CoreError::Unknown)?).map_err(|_|CoreError::Unknown)? ); } else { meta.uri = Some( - String::from_utf8(line[buf_i..end].to_vec()).map_err(|_|CoreError::Unknown)? + String::from_utf8(line.get(buf_i..end).ok_or(CoreError::Unknown)?.to_vec()).map_err(|_|CoreError::Unknown)? ); } } diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index 334c569c0905..cbdea4d99c73 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -72,7 +72,11 @@ impl<'a> ReadDirDemux<'a> for FormatWMS<'a> { continue; } - let value = if value_str[..7].eq_ignore_ascii_case("http://") { + let value = if value_str + .get(..7) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case("http://") + { "mmsh".to_owned() + &value_str[4..] } else { value_str.to_owned() diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 242817e82833..5ff0efa5bf81 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -108,7 +108,10 @@ impl Module for Playlist { impl InputContext<'_> { pub fn has_extension(&self, extension: &str) -> bool { match self.path.as_deref() { - Some(val) => val[val.len() - extension.len()..].eq_ignore_ascii_case(extension), + Some(val) => val + .get(val.len() - extension.len()..) + .ok_or(CoreError::Unknown)? + .eq_ignore_ascii_case(extension), None => false, } } -- GitLab From b035c83364196ddd974659b530a0bf52a390797c Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Thu, 24 Aug 2023 00:02:39 -0600 Subject: [PATCH 22/24] Fixups --- modules/demux/playlist-rs/src/fmt_m3u.rs | 6 +----- modules/demux/playlist-rs/src/fmt_pls.rs | 1 - modules/demux/playlist-rs/src/fmt_qtl.rs | 10 +++------- modules/demux/playlist-rs/src/fmt_sgimb.rs | 1 - modules/demux/playlist-rs/src/fmt_wms.rs | 1 - modules/demux/playlist-rs/src/fmt_wpl.rs | 6 +++++- modules/demux/playlist-rs/src/fmt_xspf.rs | 3 +-- modules/demux/playlist-rs/src/lib.rs | 8 ++++---- modules/demux/playlist-rs/src/util_xml.rs | 1 - 9 files changed, 14 insertions(+), 23 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_m3u.rs b/modules/demux/playlist-rs/src/fmt_m3u.rs index da18ef7bfb96..479bbaee530d 100644 --- a/modules/demux/playlist-rs/src/fmt_m3u.rs +++ b/modules/demux/playlist-rs/src/fmt_m3u.rs @@ -214,11 +214,7 @@ impl<'a> ReadDirDemux<'a> for FormatM3U<'a> { input_item.set_language(meta.language); input_item.set_publisher(meta.group_title); for option in meta.options.iter() { - // TODO: Pass No Flag (0) - input_item.add_option( - option, - vlcrs_core::input_item::Flag::VLC_INPUT_OPTION_UNIQUE as u32, - )?; + input_item.add_option(option, 0)?; } input_item_node.append_item(&input_item)?; } diff --git a/modules/demux/playlist-rs/src/fmt_pls.rs b/modules/demux/playlist-rs/src/fmt_pls.rs index 45f5832f66c4..de147fe85d14 100644 --- a/modules/demux/playlist-rs/src/fmt_pls.rs +++ b/modules/demux/playlist-rs/src/fmt_pls.rs @@ -51,7 +51,6 @@ impl<'a> ReadDirDemux<'a> for FormatPLS<'a> { fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { debug!(self.input.logger, "Read dir called"); - // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); self.input .source diff --git a/modules/demux/playlist-rs/src/fmt_qtl.rs b/modules/demux/playlist-rs/src/fmt_qtl.rs index 303d3e6bf239..5a4bb1925dda 100644 --- a/modules/demux/playlist-rs/src/fmt_qtl.rs +++ b/modules/demux/playlist-rs/src/fmt_qtl.rs @@ -164,13 +164,9 @@ impl<'a> ReadDirDemux<'a> for FormatQTL<'a> { } let input_uri = src.ok_or(CoreError::Unknown)?; - let mut input_item = if movie_name.is_some() { - let input_name = movie_name.ok_or(CoreError::Unknown)?; - InputItem::new(input_uri, input_name) - } else { - // TODO: NULL input name - InputItem::new(input_uri.clone(), input_uri) - }?; + let mut input_item = InputItem::empty()?; + input_item.set_uri(Some(&input_uri)); + input_item.set_name(movie_name.as_deref()); if let Some(href_val) = href { input_item.add_info( diff --git a/modules/demux/playlist-rs/src/fmt_sgimb.rs b/modules/demux/playlist-rs/src/fmt_sgimb.rs index f4c2ff3e494a..1f8d7dbf4e92 100644 --- a/modules/demux/playlist-rs/src/fmt_sgimb.rs +++ b/modules/demux/playlist-rs/src/fmt_sgimb.rs @@ -63,7 +63,6 @@ impl<'a> ReadDirDemux<'a> for FormatSGIMB<'a> { fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { debug!(self.input.logger, "Read dir called"); - // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); self.input .source diff --git a/modules/demux/playlist-rs/src/fmt_wms.rs b/modules/demux/playlist-rs/src/fmt_wms.rs index cbdea4d99c73..1216b10ea0e4 100644 --- a/modules/demux/playlist-rs/src/fmt_wms.rs +++ b/modules/demux/playlist-rs/src/fmt_wms.rs @@ -47,7 +47,6 @@ impl<'a> ReadDirDemux<'a> for FormatWMS<'a> { fn read_dir(&'a mut self, input_item_node: &'a mut InputItemNode) -> Result<()> { debug!(self.input.logger, "Reading WMS metafile"); - // TODO: Temporary solution to get lines from stream let mut buf: String = String::new(); self.input .source diff --git a/modules/demux/playlist-rs/src/fmt_wpl.rs b/modules/demux/playlist-rs/src/fmt_wpl.rs index 265adf379a02..13ec973fef9d 100644 --- a/modules/demux/playlist-rs/src/fmt_wpl.rs +++ b/modules/demux/playlist-rs/src/fmt_wpl.rs @@ -158,7 +158,11 @@ fn parse_meta( if let (Some(name), Some(content)) = (option_name, option_content) { if name.eq_ignore_ascii_case("TotalDuration") { - // TODO: input_item_AddInfo( p_input, _("Playlist"), _("Total duration"), "%lld", atoll( psz_meta_content ) ); + input_item.add_info( + "Playlist".to_owned(), + "Total duration".to_owned(), + content.to_owned(), + ); } else if name.eq_ignore_ascii_case("Author") { input_item.set_publisher(Some(content)); } else if name.eq_ignore_ascii_case("Rating") { diff --git a/modules/demux/playlist-rs/src/fmt_xspf.rs b/modules/demux/playlist-rs/src/fmt_xspf.rs index 36aab3a64746..717d8a3143a2 100644 --- a/modules/demux/playlist-rs/src/fmt_xspf.rs +++ b/modules/demux/playlist-rs/src/fmt_xspf.rs @@ -264,8 +264,7 @@ fn set_option( return Err(CoreError::Unknown); } - // TODO: Flag should be zero - input_item.add_option(&value, input_item::Flag::VLC_INPUT_OPTION_UNIQUE as u32)?; + input_item.add_option(&value, 0)?; Ok(()) } diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 5ff0efa5bf81..3d5bb5de8c62 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -108,10 +108,10 @@ impl Module for Playlist { impl InputContext<'_> { pub fn has_extension(&self, extension: &str) -> bool { match self.path.as_deref() { - Some(val) => val - .get(val.len() - extension.len()..) - .ok_or(CoreError::Unknown)? - .eq_ignore_ascii_case(extension), + Some(val) => match val.get(val.len() - extension.len()..) { + Some(val_ext) => val_ext.eq_ignore_ascii_case(extension), + None => false, + }, None => false, } } diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 79961686ba41..8216559ca843 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -213,7 +213,6 @@ pub fn find_next_element_from_buf( /// }, /// "node5" => handler_fn2 /// }) -/// TODO: Make independent of args macro_rules! parse_node { ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { $($name:expr => $handler:tt),* $(,)? -- GitLab From 526ffa87725e0c339a6e1bfe07ed662cd93c6bad Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Sun, 27 Aug 2023 19:01:34 -0600 Subject: [PATCH 23/24] Added documentation --- modules/demux/playlist-rs/src/fmt_asx.rs | 5 ++- modules/demux/playlist-rs/src/lib.rs | 17 ++++++---- modules/demux/playlist-rs/src/util_buf.rs | 37 ++++++++++++++++++++- modules/demux/playlist-rs/src/util_xml.rs | 39 +++++++++++++++++++++-- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 89537db8988c..3e8a3647fdd7 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -7,7 +7,7 @@ use vlcrs_core::{ error::{CoreError, Result}, input_item::{InputItem, InputItemNode}, stream::{DemuxControl, ReadDirDemux, Stream}, - tick::{Tick, Microseconds}, + tick::{Microseconds, Tick}, warn, }; @@ -213,8 +213,7 @@ fn parse_time(s: &str) -> Result<Tick> { subfractions = 0; } - while subfractions < 6 - { + while subfractions < 6 { subresult = subresult * 10; subfractions += 1; } diff --git a/modules/demux/playlist-rs/src/lib.rs b/modules/demux/playlist-rs/src/lib.rs index 3d5bb5de8c62..96c1b3b1e9cc 100644 --- a/modules/demux/playlist-rs/src/lib.rs +++ b/modules/demux/playlist-rs/src/lib.rs @@ -1,21 +1,19 @@ //! Playlist-rs Demux module +//! This module is an implementation of the playlist demux module in Rust use fmt_b4s::FormatB4S; -use fmt_ifo::IFOKind; use fmt_wms::FormatWMS; use std::path::Path; use vlcrs_core::{ - debug, error::{CoreError, Result}, es_out::EsOut, - input_item::{InputItem, InputItemNode}, + input_item::InputItem, messages::Logger, module::{ demux::{DemuxModule, Module, ThisDemux}, ModuleArgs, }, - stream::{DemuxControl, ReadDirDemux, Stream}, - tick::Tick, + stream::Stream, }; use vlcrs_core_macros::module; @@ -39,9 +37,11 @@ mod fmt_sgimb; mod fmt_wms; mod fmt_wpl; mod fmt_xspf; -mod util_buf; -mod util_xml; +pub mod util_buf; +pub mod util_xml; +/// Struct to hold context for parser submodules. +/// Note: This is just to make passing the module open context to individual parser submodules consize. struct InputContext<'a> { source: &'a mut Stream, logger: &'a mut Logger, @@ -106,6 +106,7 @@ impl Module for Playlist { } impl InputContext<'_> { + /// Returns whether the input file has `extension` ignoring case. pub fn has_extension(&self, extension: &str) -> bool { match self.path.as_deref() { Some(val) => match val.get(val.len() - extension.len()..) { @@ -116,11 +117,13 @@ impl InputContext<'_> { } } + /// Returns whether input has mime type `mime_type`. pub fn has_mime_type(&self, _mime_type: &str) -> bool { // TODO: Check mimetype return false; } + /// Joins the relative media path `media_path` with the source stream base path. pub fn process_mrl(&self, media_path: &str) -> Result<String> { // TODO: URL Fixup // if let Ok(url) = Url::new(media_path) { diff --git a/modules/demux/playlist-rs/src/util_buf.rs b/modules/demux/playlist-rs/src/util_buf.rs index 0ec46689a2e2..d810d3445d0e 100644 --- a/modules/demux/playlist-rs/src/util_buf.rs +++ b/modules/demux/playlist-rs/src/util_buf.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, str}; use vlcrs_core::error::{CoreError, Result}; +/// Reads from a UTF-8 encoded buffer `buf` starting from `buf_i` to the end of buffer. pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { if buf_i < buf.len() { Ok(str::from_utf8(&buf[buf_i..]).map_err(|_| CoreError::Unknown)?) @@ -11,6 +12,9 @@ pub fn read_to_end<'a>(buf: &'a [u8], buf_i: usize) -> Result<&'a str> { } } +/// Skips chars in string `skip` from buffer `buf` starting from offset `buf_i`. +/// Modifies the `mut buf_i` to the first occurrance of character not in `skip` +/// Returns the mopdified `buf_i` pub fn skip_chars(buf: &[u8], buf_i: &mut usize, skip: &str) -> usize { let skip_bytes = skip.as_bytes(); while *buf_i < buf.len() && skip_bytes.contains(&buf[*buf_i]) { @@ -19,6 +23,8 @@ pub fn skip_chars(buf: &[u8], buf_i: &mut usize, skip: &str) -> usize { return *buf_i; } +/// Finds string `seq` in buffer `buf` starting from offset `buf_i`. +/// Returns the index of the first occurrance in the string if found. Returns `None` if not found. pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &str) -> Option<usize> { if seq.is_empty() { return Some(buf_i); // Immediate match @@ -32,9 +38,12 @@ pub fn find_in_buf(buf: &[u8], buf_i: usize, seq: &str) -> Option<usize> { left += 1; right += 1; } + // Not found None } +/// Finds string `seq` in buffer `buf` starting from offset `buf_i` ignoring ASCII case. +/// Returns the index of the first occurrance in the string if found. Returns `None` if not found. pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &str) -> Option<usize> { if seq.is_empty() { return Some(buf_i); // Immediate match @@ -48,9 +57,12 @@ pub fn find_in_buf_ignore_case(buf: &[u8], buf_i: usize, seq: &str) -> Option<us left += 1; right += 1; } + // Not found None } +/// Returns true if prefix is found in buffer `buf` starting at index `buf_i`. +/// Returns false otherwise. pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { let end = buf_i + prefix.len(); if end > buf.len() { @@ -59,6 +71,9 @@ pub fn starts_with_ignore_case(buf: &[u8], buf_i: usize, prefix: &str) -> bool { return buf[buf_i..end].eq_ignore_ascii_case(prefix.as_bytes()); } +/// Reads 32-bit floating point number in utf-8 encoded buffer `buf` starting from index `buf_i`. +/// Modifies `buf_i` to the index of the first character after the floating point read. +/// Returns the parsed number. pub fn read_float(buf: &[u8], buf_i: &mut usize) -> Result<f32> { while *buf_i < buf.len() && buf[*buf_i].is_ascii_whitespace() { *buf_i += 1; @@ -80,7 +95,10 @@ pub fn read_float(buf: &[u8], buf_i: &mut usize) -> Result<f32> { .map_err(|_| CoreError::Unknown); } -// Note: Does not support octal/hexadecimal +/// Reads 32 bit integer in utf-8 encoded buffer `buf` starting from index `buf_i`. +/// Modifies `buf_i` to the index of the first character after the integer read. +/// Returns the parsed integer. +/// Note: Does not support octal/hexadecimal pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { while *buf_i < buf.len() && buf[*buf_i].is_ascii_whitespace() { *buf_i += 1; @@ -102,9 +120,26 @@ pub fn read_int(buf: &[u8], buf_i: &mut usize) -> Result<i32> { .map_err(|_| CoreError::Unknown); } +/// Returns `Err` if the buffer `buf` contains non-utf-8 encoded characters. #[allow(unused)] pub fn ensure_utf8(_buf: &mut [u8], _buf_i: &mut usize) {} +/// Macro to match if utf-8 encoded buffer `$buf` starts with one of several strings ignoring case. +/// +/// ## Example: +/// ``` +/// match_ignore_case!(line, buf_i, { +/// "CASE_1" => { +/// // Execute some code if line has "CASE_1" at index `buf_i` +/// }, +/// "CASE_2" => { +/// // Execute some code if line has "CASE_2" at index `buf_i` +/// }, +/// "CASE_3" => { +/// // Execute some code if line has "CASE_3" at index `buf_i` +/// } +/// } +/// ``` macro_rules! match_ignore_case { ($buf:expr, $buf_i:expr, { $($start_str:expr => $handler:tt),* diff --git a/modules/demux/playlist-rs/src/util_xml.rs b/modules/demux/playlist-rs/src/util_xml.rs index 8216559ca843..44aa9d0f636d 100644 --- a/modules/demux/playlist-rs/src/util_xml.rs +++ b/modules/demux/playlist-rs/src/util_xml.rs @@ -14,6 +14,7 @@ use xml::{ use vlcrs_core::{debug, error, error::CoreError}; +/// Struct to store XML parsing context for playlist format parsers. pub struct XmlParser<'a> { pub logger: &'a mut Logger, pub parser_iter: Events<&'a mut Stream>, @@ -24,6 +25,7 @@ pub struct XmlParser<'a> { } impl XmlParser<'_> { + /// Create new parser consuming the source stream `source` coming from path `path`. pub fn new<'a>( logger: &'a mut Logger, source: &'a mut Stream, @@ -41,10 +43,13 @@ impl XmlParser<'_> { }) } + /// Joins the relative media path `media_path` with the source stream base path. pub fn process_mrl(&self, media_path: &str) -> Result<String> { process_mrl(media_path, self.path.as_deref()) } + /// Skips all elements until the closing tag with name `exp_name` is found at the same depth at which this function is called. + /// Returns `Err` if the corresponding closing tag is not found. pub fn skip_element(&mut self, exp_name: &str) -> Result<()> { let mut depth = 1; for e in self.parser_iter.by_ref() { @@ -72,6 +77,9 @@ impl XmlParser<'_> { return Err(CoreError::Unknown); } + /// Returns the text content from within a node with tag name `exp_name`. + /// Reads until the corresponding closing tag is not founda at the same depth at which this function was called. + /// Returns Err if the tag could not be closed. pub fn parse_text_node(&mut self, exp_name: &str) -> Result<String> { let mut depth = 1; let mut inner_text = String::new(); @@ -101,6 +109,9 @@ impl XmlParser<'_> { return Err(CoreError::Unknown); } + /// Reads all tags upto depth `max_depth` until the opening tag with name `exp_name` is not found. + /// Returns the list of attributes found within the tag if found. + /// Returns `Err` if the tag is not found within `max_depth`. pub fn find_next_element( &mut self, exp_name: &str, @@ -145,6 +156,7 @@ impl XmlParser<'_> { } } +/// Joins the relative media path `media_path` with the source stream base path. pub fn process_mrl(media_path: &str, base_path: Option<&str>) -> Result<String> { // TODO: URL Fixup if let Ok(url) = Url::new(media_path) { @@ -164,6 +176,8 @@ pub fn process_mrl(media_path: &str, base_path: Option<&str>) -> Result<String> .to_owned()); } +/// Find attribute value of the attribute with name `attrib_name` from a list of attributes `attributes`. +/// Returns `Err` if the attribute is not found. pub fn get_required_attrib<'a>( logger: &mut Logger, attributes: &'a [OwnedAttribute], @@ -181,6 +195,7 @@ pub fn get_required_attrib<'a>( } } +/// Parses the XML content from buffer `buf` and return true if an starting tag with name `elem_name` is found. pub fn find_next_element_from_buf( buf: &[u8], logger: &mut Logger, @@ -203,16 +218,36 @@ pub fn find_next_element_from_buf( Ok(false) } -/// Usage Example: +/// Build an XML nodes parser. +/// Tag names are matched case insensitive. +/// Handler functions can be function names or block of code. +/// +/// ## Example: +/// ``` /// parse_node! (.., { /// "node1" => { /// "node2" => { +/// // Handler function name /// "node3" => handle_fn, -/// "node4" => handle_fn +/// // Handler code block +/// "node4" => { +/// // Process node with name `node4`. +/// // Does not process node content. +/// } +/// // Handler for text node +/// "textNode" => { +/// [attrib, value] { +/// // attrib: List of attributes in node opening tag +/// // value: Text within node +/// // Processes node content +/// } +/// } /// } /// }, /// "node5" => handler_fn2 /// }) +/// ``` +/// macro_rules! parse_node { ($parser:expr, $root_node:expr, $ii_node:expr, $ii:expr, { $($name:expr => $handler:tt),* $(,)? -- GitLab From c5aaff9a7c869b2463fe45d1009e4a169aadd12f Mon Sep 17 00:00:00 2001 From: mdakram28 <mdakram28@gmail.com> Date: Sun, 27 Aug 2023 19:17:11 -0600 Subject: [PATCH 24/24] Added todo, fixed comment --- modules/demux/playlist-rs/src/fmt_asx.rs | 3 ++- modules/demux/playlist-rs/todo.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/demux/playlist-rs/src/fmt_asx.rs b/modules/demux/playlist-rs/src/fmt_asx.rs index 3e8a3647fdd7..751e95c337a3 100644 --- a/modules/demux/playlist-rs/src/fmt_asx.rs +++ b/modules/demux/playlist-rs/src/fmt_asx.rs @@ -1,5 +1,5 @@ //! ASX format parser -//! +//! Format Doc: http://msdn.microsoft.com/en-us/library/windows/desktop/dd564668.aspx //! Sample: https://gist.github.com/rodydavis/d0cb7a53d8deb42e92ae803a9dd48dbc use vlcrs_core::{ @@ -178,6 +178,7 @@ fn parse_entry( Ok(()) } +/// fn parse_time(s: &str) -> Result<Tick> { let mut result = 0; let mut subresult = 0; diff --git a/modules/demux/playlist-rs/todo.md b/modules/demux/playlist-rs/todo.md index 689305fe2530..4ce5c58a03a5 100644 --- a/modules/demux/playlist-rs/todo.md +++ b/modules/demux/playlist-rs/todo.md @@ -7,4 +7,5 @@ [ ] Separate loggers [ ] UTF-8 checks [ ] implement get_extension function for all parsers -[ ] Replace square bracket operator (use non-panicking) \ No newline at end of file +[ ] Replace square bracket operator (use non-panicking) +[ ] p_this->force: add it to `VlcObjectRef` in `src/object.rs` -- GitLab