From 12f8326cb3d424d50fd9d11a9f6b49858b6cd5c4 Mon Sep 17 00:00:00 2001 From: euzu Date: Fri, 12 Dec 2025 16:49:13 +0100 Subject: [PATCH] xtream and m3u parsing with streaming --- backend/src/model/xtream.rs | 2 +- backend/src/processing/parser/xtream.rs | 3 +++ shared/src/utils/serde_utils.rs | 25 +++++++++++++++---------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/backend/src/model/xtream.rs b/backend/src/model/xtream.rs index f740b2eb9..ef747262e 100644 --- a/backend/src/model/xtream.rs +++ b/backend/src/model/xtream.rs @@ -31,7 +31,7 @@ impl XtreamCategory { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct XtreamStream { #[serde(default, deserialize_with = "deserialize_as_string")] pub name: String, diff --git a/backend/src/processing/parser/xtream.rs b/backend/src/processing/parser/xtream.rs index 40e8074f9..ab07cc403 100644 --- a/backend/src/processing/parser/xtream.rs +++ b/backend/src/processing/parser/xtream.rs @@ -237,6 +237,9 @@ mod tests { let reader = Box::pin(async_file_reader(tokio::fs::File::open("/tmp/vod_streams.json").await?)); match map_to_xtream_streams(XtreamCluster::Video, reader).await { Ok(_streams) => { + println!("{:?}", _streams.get(1)); + println!("{:?}", _streams.get(100)); + println!("{:?}", _streams.get(200)); assert!(true); }, Err(err) => { diff --git a/shared/src/utils/serde_utils.rs b/shared/src/utils/serde_utils.rs index 6fddf11c9..840a0d2e4 100644 --- a/shared/src/utils/serde_utils.rs +++ b/shared/src/utils/serde_utils.rs @@ -1,6 +1,5 @@ use crate::error::to_io_error; use chrono::{NaiveDateTime, ParseError, TimeZone, Utc}; -use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer}; use serde_json::Value; use std::io; @@ -59,7 +58,7 @@ where pub fn deserialize_number_from_string<'de, D, T>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, - T: DeserializeOwned + std::str::FromStr, + T: std::str::FromStr, { let raw: Value = Value::deserialize(deserializer)?; @@ -70,9 +69,10 @@ where // its a number Value::Number(n) => { let s = n.to_string(); - match s.parse::() { - Ok(v) => Ok(Some(v)), - Err(_) => Ok(None), // Ignore parse errors, return None + if let Ok(r) = s.parse::() { + Ok(Some(r)) + } else { + Ok(None) } } @@ -83,23 +83,28 @@ where return Ok(None); } - // Find first digit; include optional sign immediately before it (ignoring whitespace). + // Find first digit OR a '.' that is immediately followed by a digit; + // include optional sign immediately before it (ignoring whitespace). let mut last_non_ws: Option<(usize, char)> = None; - let mut digit_pos: Option = None; + let mut num_pos: Option = None; for (i, c) in s.char_indices() { if c.is_ascii_digit() { - digit_pos = Some(i); + num_pos = Some(i); + break; + } + if c == '.' && s[i + 1..].chars().next().is_some_and(|n| n.is_ascii_digit()) { + num_pos = Some(i); break; } if !c.is_whitespace() { last_non_ws = Some((i, c)); } } - let Some(digit_i) = digit_pos else { return Ok(None); }; + let Some(num_i) = num_pos else { return Ok(None); }; let start = match last_non_ws { Some((i, '-')) | Some((i, '+')) => i, - _ => digit_i, + _ => num_i, }; let mut it = s[start..].chars().peekable();