libminau 0.1.0

A library version of Minau, a music player built directly on top of Symphonia and CPAL.
Documentation
use url::Url;

use crate::{
    play_url,
};
use std::{fs, path::Path, process::exit};

struct M3uEntry {
    path: String,
    title: Option<String>,
    #[allow(dead_code)]
    duration: Option<i32>,
}

fn parse(m3u: &str) -> Vec<M3uEntry> {
    let mut entries = Vec::new();
    let mut current_title = None;
    let mut current_duration = None;

    if !m3u.lines().next().unwrap_or("").starts_with("#EXTM3U") {
        return m3u
            .lines()
            .map(|line| line.trim())
            .filter(|line| !line.is_empty() && !line.starts_with('#'))
            .map(|line| M3uEntry {
                path: line.to_string(),
                title: None,
                duration: None,
            })
            .collect();
    }

    for line in m3u.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) {
        if line.starts_with("#EXTINF:") {
            let info = line.strip_prefix("#EXTINF:").unwrap_or("").trim();
            let mut parts = info.splitn(2, ',');
            current_duration = parts.next().and_then(|s| s.parse::<i32>().ok());
            current_title = parts.next().map(String::from);
        } else if !line.starts_with('#') {
            entries.push(M3uEntry {
                path: line.to_string(),
                title: current_title.take(),
                duration: current_duration.take(),
            });
        }
    }
    entries
}


pub struct Playlist {
    entries: Vec<PlaylistEntry>,
}

pub struct PlaylistEntry {
    pub path: String,
    pub title: Option<String>,
    pub duration: Option<i32>,
}

impl Playlist {
    /// Load from m3u
    pub fn from_file<P: AsRef<Path>>(path: P) -> crate::Result<Self> {
        let content = std::fs::read_to_string(path)?;
        Ok(Self::from_string(&content))
    }

    /// Analyze from string
    pub fn from_string(content: &str) -> Self {
        let entries = crate::m3u::parse(content)
            .into_iter()
            .map(|e| PlaylistEntry {
                path: e.path,
                title: e.title,
                duration: e.duration,
            })
            .collect();
        
        Self { entries }
    }

    /// List of Entries
    pub fn entries(&self) -> &[PlaylistEntry] {
        &self.entries
    }

    /// Length of Entries
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// check entries is empty
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}