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