mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2024-11-15 21:21:08 +00:00
Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
|
381ac7732f | ||
|
e3b6390186 | ||
|
7f322675eb | ||
|
c5cceeb4f6 | ||
|
5109753b96 | ||
|
487d63da4d | ||
|
f6af8acbfe | ||
|
46622345d1 | ||
|
d8e0283ddb | ||
|
b9cf88b7ec | ||
|
a1970192ff | ||
|
ae31a2741f | ||
|
e7a6cf943c |
6 changed files with 135 additions and 22 deletions
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "m3u8-rs"
|
||||
version = "5.0.3"
|
||||
version = "6.0.0"
|
||||
authors = ["Rutger"]
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rutgersc/m3u8-rs"
|
||||
|
|
30
sample-playlists/media-playlist-zero-decimal.m3u8
Normal file
30
sample-playlists/media-playlist-zero-decimal.m3u8
Normal file
|
@ -0,0 +1,30 @@
|
|||
#EXTM3U
|
||||
#EXT-X-TARGETDURATION:11
|
||||
#EXT-X-VERSION:4
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXTINF:9.00000,
|
||||
#EXT-X-BYTERANGE:86920@0
|
||||
main.aac
|
||||
#EXTINF:10.00000,
|
||||
#EXT-X-BYTERANGE:136595@86920
|
||||
main.aac
|
||||
#EXTINF:9.00000,
|
||||
#EXT-X-BYTERANGE:136567@223515
|
||||
main.aac
|
||||
#EXTINF:10.00000,
|
||||
#EXT-X-BYTERANGE:136954@360082
|
||||
main.aac
|
||||
#EXTINF:10.00000,
|
||||
#EXT-X-BYTERANGE:137116@497036
|
||||
main.aac
|
||||
#EXTINF:9.00000,
|
||||
#EXT-X-BYTERANGE:136770@634152
|
||||
main.aac
|
||||
#EXTINF:10.00000,
|
||||
#EXT-X-BYTERANGE:137219@770922
|
||||
main.aac
|
||||
#EXTINF:10.00000,
|
||||
#EXT-X-BYTERANGE:137132@908141
|
||||
main.acc
|
||||
#EXT-X-ENDLIST
|
28
src/lib.rs
28
src/lib.rs
|
@ -42,7 +42,7 @@
|
|||
//!
|
||||
//! let playlist = MediaPlaylist {
|
||||
//! version: Some(6),
|
||||
//! target_duration: 3.0,
|
||||
//! target_duration: 3,
|
||||
//! media_sequence: 338559,
|
||||
//! discontinuity_sequence: 1234,
|
||||
//! end_list: true,
|
||||
|
@ -64,6 +64,32 @@
|
|||
//! //let mut file = std::fs::File::open("playlist.m3u8").unwrap();
|
||||
//! //playlist.write_to(&mut file).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Controlling the output precision for floats, such as #EXTINF (default is unset)
|
||||
//!
|
||||
//! ```
|
||||
//! use std::sync::atomic::Ordering;
|
||||
//! use m3u8_rs::{WRITE_OPT_FLOAT_PRECISION, MediaPlaylist, MediaSegment};
|
||||
//!
|
||||
//! WRITE_OPT_FLOAT_PRECISION.store(5, Ordering::Relaxed);
|
||||
//!
|
||||
//! let playlist = MediaPlaylist {
|
||||
//! target_duration: 3,
|
||||
//! segments: vec![
|
||||
//! MediaSegment {
|
||||
//! duration: 2.9,
|
||||
//! title: Some("title".into()),
|
||||
//! ..Default::default()
|
||||
//! },
|
||||
//! ],
|
||||
//! ..Default::default()
|
||||
//! };
|
||||
//!
|
||||
//! let mut v: Vec<u8> = Vec::new();
|
||||
//!
|
||||
//! playlist.write_to(&mut v).unwrap();
|
||||
//! let m3u8_str: &str = std::str::from_utf8(&v).unwrap();
|
||||
//! assert!(m3u8_str.contains("#EXTINF:2.90000,title"));
|
||||
|
||||
mod playlist;
|
||||
pub use playlist::*;
|
||||
|
|
|
@ -218,7 +218,7 @@ enum MasterPlaylistTag {
|
|||
SessionKey(SessionKey),
|
||||
Start(Start),
|
||||
IndependentSegments,
|
||||
Comment(String),
|
||||
Comment(Option<String>),
|
||||
Uri(String),
|
||||
Unknown(ExtTag),
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ fn parse_media_playlist_tags(i: &[u8]) -> IResult<&[u8], Vec<MediaPlaylistTag>>
|
|||
enum MediaPlaylistTag {
|
||||
Version(usize),
|
||||
Segment(SegmentTag),
|
||||
TargetDuration(f32),
|
||||
TargetDuration(u64),
|
||||
MediaSequence(u64),
|
||||
DiscontinuitySequence(u64),
|
||||
EndList,
|
||||
|
@ -361,7 +361,7 @@ fn media_playlist_tag(i: &[u8]) -> IResult<&[u8], MediaPlaylistTag> {
|
|||
alt((
|
||||
map(version_tag, MediaPlaylistTag::Version),
|
||||
map(
|
||||
pair(tag("#EXT-X-TARGETDURATION:"), float),
|
||||
pair(tag("#EXT-X-TARGETDURATION:"), number),
|
||||
|(_, duration)| MediaPlaylistTag::TargetDuration(duration),
|
||||
),
|
||||
map(
|
||||
|
@ -487,7 +487,7 @@ enum SegmentTag {
|
|||
ProgramDateTime(chrono::DateTime<chrono::FixedOffset>),
|
||||
DateRange(DateRange),
|
||||
Unknown(ExtTag),
|
||||
Comment(String),
|
||||
Comment(Option<String>),
|
||||
Uri(String),
|
||||
}
|
||||
|
||||
|
@ -555,13 +555,16 @@ fn extmap(i: &[u8]) -> IResult<&[u8], Map> {
|
|||
}
|
||||
None => Err("URI is empty"),
|
||||
}?;
|
||||
let byte_range = attrs
|
||||
.remove("BYTERANGE")
|
||||
.map(|range| match byte_range_val(range.to_string().as_bytes()) {
|
||||
IResult::Ok((_, range)) => Ok(range),
|
||||
let byte_range = match attrs.remove("BYTERANGE") {
|
||||
Some(QuotedOrUnquoted::Quoted(s)) => match byte_range_val(s.as_bytes()) {
|
||||
IResult::Ok((_, range)) => Ok(Some(range)),
|
||||
IResult::Err(_) => Err("Invalid byte range"),
|
||||
})
|
||||
.transpose()?;
|
||||
},
|
||||
Some(QuotedOrUnquoted::Unquoted(_)) => {
|
||||
Err("Can't create BYTERANGE attribute from unquoted string")
|
||||
}
|
||||
None => Ok(None),
|
||||
}?;
|
||||
|
||||
Ok(Map {
|
||||
uri,
|
||||
|
@ -606,10 +609,10 @@ fn ext_tag(i: &[u8]) -> IResult<&[u8], ExtTag> {
|
|||
)(i)
|
||||
}
|
||||
|
||||
fn comment_tag(i: &[u8]) -> IResult<&[u8], String> {
|
||||
fn comment_tag(i: &[u8]) -> IResult<&[u8], Option<String>> {
|
||||
map(
|
||||
pair(
|
||||
preceded(char('#'), map_res(is_not("\r\n"), from_utf8_slice)),
|
||||
preceded(char('#'), opt(map_res(is_not("\r\n"), from_utf8_slice))),
|
||||
take(1usize),
|
||||
),
|
||||
|(text, _)| text,
|
||||
|
@ -926,10 +929,15 @@ mod tests {
|
|||
fn comment() {
|
||||
assert_eq!(
|
||||
comment_tag(b"#Hello\nxxx"),
|
||||
Result::Ok(("xxx".as_bytes(), "Hello".to_string()))
|
||||
Result::Ok(("xxx".as_bytes(), Some("Hello".to_string())))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_comment() {
|
||||
assert_eq!(comment_tag(b"#\nxxx"), Result::Ok(("xxx".as_bytes(), None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotes() {
|
||||
assert_eq!(
|
||||
|
|
|
@ -6,11 +6,16 @@
|
|||
use crate::QuotedOrUnquoted;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::f32;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::usize::MAX;
|
||||
use std::{f32, usize};
|
||||
|
||||
/// The output precision for floats, such as #EXTINF (default is unset)
|
||||
pub static WRITE_OPT_FLOAT_PRECISION: AtomicUsize = AtomicUsize::new(MAX);
|
||||
|
||||
macro_rules! write_some_attribute_quoted {
|
||||
($w:expr, $tag:expr, $o:expr) => {
|
||||
|
@ -713,7 +718,7 @@ impl SessionData {
|
|||
pub struct MediaPlaylist {
|
||||
pub version: Option<usize>,
|
||||
/// `#EXT-X-TARGETDURATION:<s>`
|
||||
pub target_duration: f32,
|
||||
pub target_duration: u64,
|
||||
/// `#EXT-X-MEDIA-SEQUENCE:<number>`
|
||||
pub media_sequence: u64,
|
||||
pub segments: Vec<MediaSegment>,
|
||||
|
@ -869,7 +874,11 @@ impl MediaSegment {
|
|||
writeln!(w)?;
|
||||
}
|
||||
if let Some(ref v) = self.program_date_time {
|
||||
writeln!(w, "#EXT-X-PROGRAM-DATE-TIME:{}", v.to_rfc3339())?;
|
||||
writeln!(
|
||||
w,
|
||||
"#EXT-X-PROGRAM-DATE-TIME:{}",
|
||||
v.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
|
||||
)?;
|
||||
}
|
||||
if let Some(ref v) = self.daterange {
|
||||
write!(w, "#EXT-X-DATERANGE:")?;
|
||||
|
@ -880,7 +889,14 @@ impl MediaSegment {
|
|||
writeln!(w, "{}", unknown_tag)?;
|
||||
}
|
||||
|
||||
match WRITE_OPT_FLOAT_PRECISION.load(Ordering::Relaxed) {
|
||||
MAX => {
|
||||
write!(w, "#EXTINF:{},", self.duration)?;
|
||||
}
|
||||
n => {
|
||||
write!(w, "#EXTINF:{:.*},", n, self.duration)?;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ref v) = self.title {
|
||||
writeln!(w, "{}", v)?;
|
||||
|
@ -1003,8 +1019,9 @@ impl Map {
|
|||
pub fn write_attributes_to<T: Write>(&self, w: &mut T) -> std::io::Result<()> {
|
||||
write!(w, "URI=\"{}\"", self.uri)?;
|
||||
if let Some(ref byte_range) = self.byte_range {
|
||||
write!(w, ",BYTERANGE=")?;
|
||||
write!(w, ",BYTERANGE=\"")?;
|
||||
byte_range.write_value_to(w)?;
|
||||
write!(w, "\"")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
36
tests/lib.rs
36
tests/lib.rs
|
@ -8,6 +8,7 @@ use std::collections::HashMap;
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{fs, io};
|
||||
|
||||
fn all_sample_m3u_playlists() -> Vec<path::PathBuf> {
|
||||
|
@ -198,6 +199,36 @@ fn create_and_parse_master_playlist_empty() {
|
|||
assert_eq!(playlist_original, playlist_parsed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_segment_float_inf() {
|
||||
let playlist = Playlist::MediaPlaylist(MediaPlaylist {
|
||||
version: Some(6),
|
||||
target_duration: 3,
|
||||
media_sequence: 338559,
|
||||
discontinuity_sequence: 1234,
|
||||
end_list: true,
|
||||
playlist_type: Some(MediaPlaylistType::Vod),
|
||||
segments: vec![MediaSegment {
|
||||
uri: "20140311T113819-01-338559live.ts".into(),
|
||||
duration: 2.000f32,
|
||||
title: Some("title".into()),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut v: Vec<u8> = Vec::new();
|
||||
playlist.write_to(&mut v).unwrap();
|
||||
let m3u8_str: &str = std::str::from_utf8(&v).unwrap();
|
||||
assert!(m3u8_str.contains("#EXTINF:2,title"));
|
||||
|
||||
WRITE_OPT_FLOAT_PRECISION.store(5, Ordering::Relaxed);
|
||||
|
||||
playlist.write_to(&mut v).unwrap();
|
||||
let m3u8_str: &str = std::str::from_utf8(&v).unwrap();
|
||||
assert!(m3u8_str.contains("#EXTINF:2.00000,title"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_and_parse_master_playlist_full() {
|
||||
let mut playlist_original = Playlist::MasterPlaylist(MasterPlaylist {
|
||||
|
@ -304,7 +335,7 @@ fn create_and_parse_media_playlist_empty() {
|
|||
#[test]
|
||||
fn create_and_parse_media_playlist_single_segment() {
|
||||
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
|
||||
target_duration: 2.0,
|
||||
target_duration: 2,
|
||||
segments: vec![MediaSegment {
|
||||
uri: "20140311T113819-01-338559live.ts".into(),
|
||||
duration: 2.002,
|
||||
|
@ -321,7 +352,7 @@ fn create_and_parse_media_playlist_single_segment() {
|
|||
fn create_and_parse_media_playlist_full() {
|
||||
let mut playlist_original = Playlist::MediaPlaylist(MediaPlaylist {
|
||||
version: Some(4),
|
||||
target_duration: 3.0,
|
||||
target_duration: 3,
|
||||
media_sequence: 338559,
|
||||
discontinuity_sequence: 1234,
|
||||
end_list: true,
|
||||
|
@ -382,6 +413,7 @@ fn create_and_parse_media_playlist_full() {
|
|||
tag: "X-CUE-OUT".into(),
|
||||
rest: Some("DURATION=2.002".into()),
|
||||
}],
|
||||
..Default::default()
|
||||
}],
|
||||
unknown_tags: vec![],
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue