Compare commits

...

8 commits

Author SHA1 Message Date
Sebastian Dröge 853cd81dac
Merge f66356ba7b into 381ac7732f 2024-02-17 16:40:01 +03:00
rutgersc 381ac7732f
Merge pull request #72 from ant1eicher/master
Support AWS Elemental MediaConvert decimal format.
2024-02-14 18:59:35 +01:00
Anton Eicher e3b6390186 EXTINF tags need to be in floating-point format to work with AWS Elemental MediaConvert
AWS Elemental MediaConvert rejects playlists with EXTINF tags that are not in floating point format. When m3u8 MediaSegment self.duration is an exact number without trailing decimals, writeln cuts off the decimal places and prints it like an integer.

This change adds support for fixed length floating point numbers.
2024-02-14 16:29:47 +02:00
rutgersc 7f322675eb
Merge pull request #73 from rutgersc/fix/targetduration
#EXT-X-TARGETDURATION:<s> is supposed to be a decimal-integer
2024-01-30 20:57:38 +01:00
Rutger Schoorstra c5cceeb4f6 Update version to 6.0.0 2024-01-26 18:56:34 +01:00
Rutger Schoorstra 5109753b96 #EXT-X-TARGETDURATION:<s> is supposed to be a decimal-integer
https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.1
2024-01-26 18:55:39 +01:00
Sebastian Dröge f66356ba7b Write out unknown tags for MediaPlaylist too
Fixes https://github.com/rutgersc/m3u8-rs/issues/55
2022-10-28 11:18:36 +03:00
Sebastian Dröge f9b5420c0a Fix clippy warning
warning: useless conversion to the same type: `std::string::String`
   --> src/playlist.rs:415:77
    |
415 |             QuotedOrUnquoted::Unquoted(s) => Ok(ClosedCaptionGroupId::Other(String::from(s))),
    |                                                                             ^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `s`
    |
    = note: `#[warn(clippy::useless_conversion)]` on by default
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
2022-10-28 11:04:01 +03:00
6 changed files with 113 additions and 9 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "m3u8-rs"
version = "5.0.5"
version = "6.0.0"
authors = ["Rutger"]
readme = "README.md"
repository = "https://github.com/rutgersc/m3u8-rs"

View 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

View file

@ -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::*;

View file

@ -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(

View file

@ -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>,
@ -764,6 +769,10 @@ impl MediaPlaylist {
if let Some(ref start) = self.start {
start.write_to(w)?;
}
for unknown_tag in &self.unknown_tags {
writeln!(w, "{}", unknown_tag)?;
}
for segment in &self.segments {
segment.write_to(w)?;
}
@ -884,7 +893,14 @@ impl MediaSegment {
writeln!(w, "{}", unknown_tag)?;
}
write!(w, "#EXTINF:{},", self.duration)?;
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)?;

View file

@ -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![],
});