mirror of
https://github.com/rutgersc/m3u8-rs.git
synced 2024-11-15 21:21:08 +00:00
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.
This commit is contained in:
parent
7f322675eb
commit
e3b6390186
4 changed files with 102 additions and 2 deletions
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
|
26
src/lib.rs
26
src/lib.rs
|
@ -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::*;
|
||||
|
|
|
@ -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) => {
|
||||
|
@ -884,7 +889,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)?;
|
||||
|
|
32
tests/lib.rs
32
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 {
|
||||
|
@ -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