1
0
Fork 0
mirror of https://github.com/sile/hls_m3u8.git synced 2024-11-25 08:31:00 +00:00

implement Float and UFloat

This commit is contained in:
Luro02 2020-02-10 13:13:41 +01:00
parent 101878a083
commit 90ff18e2b3
No known key found for this signature in database
GPG key ID: B66FD4F74501A9CF
7 changed files with 409 additions and 310 deletions

View file

@ -1,18 +1,35 @@
use std::fmt;
use std::str::FromStr;
use shorthand::ShortHand;
use crate::attribute::AttributePairs;
use crate::types::{ProtocolVersion, SignedDecimalFloatingPoint};
use crate::types::{Float, ProtocolVersion};
use crate::utils::{parse_yes_or_no, tag};
use crate::{Error, RequiredVersion};
/// # [4.3.5.2. EXT-X-START]
///
/// [4.3.5.2. EXT-X-START]: https://tools.ietf.org/html/rfc8216#section-4.3.5.2
#[derive(PartialOrd, Debug, Clone, Copy, PartialEq)]
#[derive(ShortHand, PartialOrd, Debug, Clone, Copy, PartialEq)]
#[shorthand(enable(must_use))]
pub struct ExtXStart {
time_offset: SignedDecimalFloatingPoint,
precise: bool,
#[shorthand(enable(skip))]
time_offset: Float,
/// Returns whether clients should not render media stream whose
/// presentation times are prior to the specified time offset.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXStart;
/// let mut start = ExtXStart::new(20.123456);
/// # assert_eq!(start.is_precise(), false);
/// start.set_is_precise(true);
///
/// assert_eq!(start.is_precise(), true);
/// ```
is_precise: bool,
}
impl ExtXStart {
@ -20,9 +37,9 @@ impl ExtXStart {
/// Makes a new [`ExtXStart`] tag.
///
/// # Panic
/// # Panics
///
/// Panics if the time_offset value is infinite.
/// Panics if the `time_offset` is infinite or [`NaN`].
///
/// # Example
///
@ -30,30 +47,34 @@ impl ExtXStart {
/// # use hls_m3u8::tags::ExtXStart;
/// let start = ExtXStart::new(20.123456);
/// ```
pub fn new(time_offset: f64) -> Self {
///
/// [`NaN`]: core::f64::NAN
pub fn new(time_offset: f32) -> Self {
Self {
time_offset: SignedDecimalFloatingPoint::new(time_offset),
precise: false,
time_offset: Float::new(time_offset),
is_precise: false,
}
}
/// Makes a new [`ExtXStart`] tag with the given `precise` flag.
///
/// # Panic
/// # Panics
///
/// Panics if the time_offset value is infinite.
/// Panics if the `time_offset` is infinite or [`NaN`].
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXStart;
/// let start = ExtXStart::with_precise(20.123456, true);
/// assert_eq!(start.precise(), true);
/// assert_eq!(start.is_precise(), true);
/// ```
pub fn with_precise(time_offset: f64, precise: bool) -> Self {
///
/// [`NaN`]: core::f64::NAN
pub fn with_precise(time_offset: f32, is_precise: bool) -> Self {
Self {
time_offset: SignedDecimalFloatingPoint::new(time_offset),
precise,
time_offset: Float::new(time_offset),
is_precise,
}
}
@ -64,9 +85,10 @@ impl ExtXStart {
/// ```
/// # use hls_m3u8::tags::ExtXStart;
/// let start = ExtXStart::new(20.123456);
///
/// assert_eq!(start.time_offset(), 20.123456);
/// ```
pub const fn time_offset(&self) -> f64 { self.time_offset.as_f64() }
pub const fn time_offset(self) -> f32 { self.time_offset.as_f32() }
/// Sets the time offset of the media segments in the playlist.
///
@ -81,38 +103,8 @@ impl ExtXStart {
///
/// assert_eq!(start.time_offset(), 1.0);
/// ```
pub fn set_time_offset(&mut self, value: f64) -> &mut Self {
self.time_offset = SignedDecimalFloatingPoint::new(value);
self
}
/// Returns whether clients should not render media stream whose
/// presentation times are prior to the specified time offset.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXStart;
/// let start = ExtXStart::with_precise(20.123456, true);
/// assert_eq!(start.precise(), true);
/// ```
pub const fn precise(&self) -> bool { self.precise }
/// Sets the `precise` flag.
///
/// # Example
///
/// ```
/// # use hls_m3u8::tags::ExtXStart;
/// let mut start = ExtXStart::new(20.123456);
/// # assert_eq!(start.precise(), false);
///
/// start.set_precise(true);
///
/// assert_eq!(start.precise(), true);
/// ```
pub fn set_precise(&mut self, value: bool) -> &mut Self {
self.precise = value;
pub fn set_time_offset(&mut self, value: f32) -> &mut Self {
self.time_offset = Float::new(value);
self
}
}
@ -126,9 +118,11 @@ impl fmt::Display for ExtXStart {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", Self::PREFIX)?;
write!(f, "TIME-OFFSET={}", self.time_offset)?;
if self.precise {
if self.is_precise {
write!(f, ",PRECISE=YES")?;
}
Ok(())
}
}
@ -140,12 +134,12 @@ impl FromStr for ExtXStart {
let input = tag(input, Self::PREFIX)?;
let mut time_offset = None;
let mut precise = false;
let mut is_precise = false;
for (key, value) in AttributePairs::new(input) {
match key {
"TIME-OFFSET" => time_offset = Some((value.parse())?),
"PRECISE" => precise = (parse_yes_or_no(value))?,
"TIME-OFFSET" => time_offset = Some(value.parse()?),
"PRECISE" => is_precise = parse_yes_or_no(value)?,
_ => {
// [6.3.1. General Client Responsibilities]
// > ignore any attribute/value pair with an unrecognized
@ -154,11 +148,11 @@ impl FromStr for ExtXStart {
}
}
let time_offset = time_offset.ok_or_else(|| Error::missing_value("EXT-X-TIME-OFFSET"))?;
let time_offset = time_offset.ok_or_else(|| Error::missing_value("TIME-OFFSET"))?;
Ok(Self {
time_offset,
precise,
is_precise,
})
}
}

View file

@ -1,137 +0,0 @@
use core::str::FromStr;
use derive_more::{Deref, Display};
use crate::Error;
/// Non-negative decimal floating-point number.
///
/// See: [4.2. Attribute Lists]
///
/// [4.2. Attribute Lists]:
/// https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-05#section-4.2
#[derive(Deref, Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display)]
pub(crate) struct DecimalFloatingPoint(f64);
impl DecimalFloatingPoint {
/// Makes a new [`DecimalFloatingPoint`] instance.
///
/// # Errors
///
/// The given value must have a positive sign and be finite,
/// otherwise this function will return an error that has the kind
/// `ErrorKind::InvalidInput`.
pub fn new(value: f64) -> crate::Result<Self> {
if value.is_sign_negative() || value.is_infinite() || value.is_nan() {
return Err(Error::invalid_input());
}
Ok(Self(value))
}
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
/// Converts [`DecimalFloatingPoint`] to [`f64`].
pub const fn as_f64(self) -> f64 { self.0 }
}
// this trait is implemented manually, so it doesn't construct a
// [`DecimalFloatingPoint`], with a negative value.
impl FromStr for DecimalFloatingPoint {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input.parse().map_err(Error::parse_float)?)
}
}
#[doc(hidden)]
impl From<f64> for DecimalFloatingPoint {
fn from(value: f64) -> Self {
let mut result = value;
// guard against the unlikely case of an infinite value...
if result.is_infinite() || result.is_nan() {
result = 0.0;
}
Self(result.abs())
}
}
#[doc(hidden)]
impl From<f32> for DecimalFloatingPoint {
fn from(value: f32) -> Self { f64::from(value).into() }
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
macro_rules! test_from {
( $($input:expr),* ) => {
use ::core::convert::From;
#[test]
fn test_from() {
$(
assert_eq!(
DecimalFloatingPoint::from($input),
DecimalFloatingPoint::new(1.0).unwrap(),
);
)*
}
}
}
test_from![1_u8, 1_u16, 1_u32, 1.0_f32, -1.0_f32, 1.0_f64, -1.0_f64];
#[test]
pub fn test_display() {
let decimal_floating_point = DecimalFloatingPoint::new(22.0).unwrap();
assert_eq!(decimal_floating_point.to_string(), "22".to_string());
let decimal_floating_point = DecimalFloatingPoint::new(4.1).unwrap();
assert_eq!(decimal_floating_point.to_string(), "4.1".to_string());
}
#[test]
pub fn test_parser() {
assert_eq!(
DecimalFloatingPoint::new(22.0).unwrap(),
"22".parse::<DecimalFloatingPoint>().unwrap()
);
assert_eq!(
DecimalFloatingPoint::new(4.1).unwrap(),
"4.1".parse::<DecimalFloatingPoint>().unwrap()
);
assert!("1#".parse::<DecimalFloatingPoint>().is_err());
assert!("-1.0".parse::<DecimalFloatingPoint>().is_err());
}
#[test]
fn test_new() {
assert!(DecimalFloatingPoint::new(::std::f64::INFINITY).is_err());
assert!(DecimalFloatingPoint::new(-1.0).is_err());
}
#[test]
fn test_as_f64() {
assert_eq!(DecimalFloatingPoint::new(1.0).unwrap().as_f64(), 1.0);
}
#[test]
fn test_from_inf() {
assert_eq!(
DecimalFloatingPoint::from(::std::f64::INFINITY),
DecimalFloatingPoint::new(0.0).unwrap()
);
}
#[test]
fn test_deref() {
assert_eq!(DecimalFloatingPoint::from(0.1).floor(), 0.0);
}
}

173
src/types/float.rs Normal file
View file

@ -0,0 +1,173 @@
use core::convert::TryFrom;
use core::str::FromStr;
use derive_more::{Deref, Display};
use crate::Error;
/// This is a wrapper type around an [`f32`] that can not be constructed
/// with [`NaN`], [`INFINITY`] or [`NEG_INFINITY`].
///
/// [`NaN`]: core::f32::NAN
/// [`INFINITY`]: core::f32::INFINITY
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
#[derive(Deref, Default, Debug, Copy, Clone, PartialEq, PartialOrd, Display)]
pub struct Float(f32);
impl Float {
/// Makes a new [`Float`] from an [`f32`].
///
/// # Panics
///
/// If the given float is infinite or [`NaN`].
///
/// # Examples
///
/// ```
/// use hls_m3u8::types::Float;
///
/// let float = Float::new(1.0);
/// ```
///
/// This would panic:
///
/// ```should_panic
/// use core::f32::NAN;
/// use hls_m3u8::types::Float;
///
/// let float = Float::new(NAN);
/// ```
///
/// [`NaN`]: core::f32::NAN
pub fn new(float: f32) -> Self {
if float.is_infinite() {
panic!("float must be finite: `{}`", float);
}
if float.is_nan() {
panic!("float must not be `NaN`");
}
Self(float)
}
/// Returns the underlying [`f32`].
pub const fn as_f32(self) -> f32 { self.0 }
}
impl FromStr for Float {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let float = f32::from_str(input).map_err(Error::parse_float)?;
Self::try_from(float)
}
}
impl TryFrom<f32> for Float {
type Error = Error;
fn try_from(float: f32) -> Result<Self, Self::Error> {
if float.is_infinite() {
return Err(Error::custom(format!("float must be finite: `{}`", float)));
}
if float.is_nan() {
return Err(Error::custom("float must not be `NaN`"));
}
Ok(Self(float))
}
}
macro_rules! implement_from {
( $( $type:tt ),+ ) => {
$(
impl ::core::convert::From<$type> for Float {
fn from(value: $type) -> Self {
Self(value as f32)
}
}
)+
}
}
implement_from!(i16, u16, i8, u8);
// convenience implementation to compare f32 with a Float.
impl PartialEq<f32> for Float {
fn eq(&self, other: &f32) -> bool { &self.0 == other }
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_display() {
assert_eq!(Float::new(22.0).to_string(), "22".to_string());
assert_eq!(
Float::new(3.14159265359).to_string(),
"3.1415927".to_string()
);
assert_eq!(
Float::new(-3.14159265359).to_string(),
"-3.1415927".to_string()
);
}
#[test]
fn test_parser() {
assert_eq!(Float::new(22.0), Float::from_str("22").unwrap());
assert_eq!(Float::new(-22.0), Float::from_str("-22").unwrap());
assert_eq!(
Float::new(3.14159265359),
Float::from_str("3.14159265359").unwrap()
);
assert!(Float::from_str("1#").is_err());
assert!(Float::from_str("NaN").is_err());
assert!(Float::from_str("inf").is_err());
assert!(Float::from_str("-inf").is_err());
}
#[test]
#[should_panic = "float must be finite: `inf`"]
fn test_new_infinite() { Float::new(::core::f32::INFINITY); }
#[test]
#[should_panic = "float must be finite: `-inf`"]
fn test_new_neg_infinite() { Float::new(::core::f32::NEG_INFINITY); }
#[test]
#[should_panic = "float must not be `NaN`"]
fn test_new_nan() { Float::new(::core::f32::NAN); }
#[test]
fn test_partial_eq() {
assert_eq!(Float::new(1.1), 1.1);
}
#[test]
fn test_as_f32() {
assert_eq!(Float::new(1.1).as_f32(), 1.1_f32);
}
#[test]
fn test_from() {
assert_eq!(Float::from(-1_i8), Float::new(-1.0));
assert_eq!(Float::from(1_u8), Float::new(1.0));
assert_eq!(Float::from(-1_i16), Float::new(-1.0));
assert_eq!(Float::from(1_u16), Float::new(1.0));
}
#[test]
fn test_try_from() {
assert_eq!(Float::try_from(1.1_f32).unwrap(), Float::new(1.1));
assert_eq!(Float::try_from(-1.1_f32).unwrap(), Float::new(-1.1));
assert!(Float::try_from(::core::f32::INFINITY).is_err());
assert!(Float::try_from(::core::f32::NAN).is_err());
assert!(Float::try_from(::core::f32::NEG_INFINITY).is_err());
}
}

View file

@ -2,7 +2,6 @@
mod byte_range;
mod channels;
mod closed_captions;
mod decimal_floating_point;
mod decryption_key;
mod encryption_method;
mod hdcp_level;
@ -12,14 +11,15 @@ mod key_format_versions;
mod media_type;
mod protocol_version;
mod resolution;
mod signed_decimal_floating_point;
mod stream_inf;
mod value;
mod float;
mod ufloat;
pub use byte_range::*;
pub use channels::*;
pub use closed_captions::*;
pub(crate) use decimal_floating_point::*;
pub use decryption_key::*;
pub use encryption_method::*;
pub use hdcp_level::*;
@ -29,6 +29,8 @@ pub use key_format_versions::*;
pub use media_type::*;
pub use protocol_version::*;
pub use resolution::*;
pub(crate) use signed_decimal_floating_point::*;
pub use stream_inf::*;
pub use value::*;
pub use float::Float;
pub use ufloat::UFloat;

View file

@ -1,94 +0,0 @@
use derive_more::Deref;
use derive_more::{Display, FromStr};
/// Signed decimal floating-point number.
///
/// See: [4.2. Attribute Lists]
///
/// [4.2. Attribute Lists]: https://tools.ietf.org/html/rfc8216#section-4.2
#[derive(Deref, Default, Debug, Clone, Copy, PartialEq, PartialOrd, Display, FromStr)]
pub(crate) struct SignedDecimalFloatingPoint(f64);
impl SignedDecimalFloatingPoint {
/// Makes a new [`SignedDecimalFloatingPoint`] instance.
///
/// # Panics
///
/// The given value must be finite, otherwise this function will panic!
pub fn new(value: f64) -> Self {
if value.is_infinite() || value.is_nan() {
panic!("Floating point value must be finite and not NaN!");
}
Self(value)
}
pub(crate) const fn from_f64_unchecked(value: f64) -> Self { Self(value) }
/// Converts [`DecimalFloatingPoint`] to [`f64`].
pub const fn as_f64(self) -> f64 { self.0 }
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
macro_rules! test_from {
( $( $input:expr => $output:expr ),* ) => {
use ::core::convert::From;
#[test]
fn test_from() {
$(
assert_eq!(
$input,
$output,
);
)*
}
}
}
test_from![
SignedDecimalFloatingPoint::from(1_u8) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1_i8) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1_u16) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1_i16) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1_u32) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1_i32) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1.0_f32) => SignedDecimalFloatingPoint::new(1.0),
SignedDecimalFloatingPoint::from(1.0_f64) => SignedDecimalFloatingPoint::new(1.0)
];
#[test]
fn test_display() {
assert_eq!(
SignedDecimalFloatingPoint::new(1.0).to_string(),
1.0_f64.to_string()
);
}
#[test]
#[should_panic]
fn test_new_panic() { SignedDecimalFloatingPoint::new(::std::f64::INFINITY); }
#[test]
fn test_parser() {
assert_eq!(
SignedDecimalFloatingPoint::new(1.0),
"1.0".parse::<SignedDecimalFloatingPoint>().unwrap()
);
assert!("garbage".parse::<SignedDecimalFloatingPoint>().is_err());
}
#[test]
fn test_as_f64() {
assert_eq!(SignedDecimalFloatingPoint::new(1.0).as_f64(), 1.0);
}
#[test]
fn test_deref() {
assert_eq!(SignedDecimalFloatingPoint::from(0.1).floor(), 0.0);
}
}

182
src/types/ufloat.rs Normal file
View file

@ -0,0 +1,182 @@
use core::convert::TryFrom;
use core::str::FromStr;
use derive_more::{Deref, Display};
use crate::Error;
/// This is a wrapper type around an [`f32`] that can not be constructed
/// with a negative float (ex. `-1.1`), [`NaN`], [`INFINITY`] or
/// [`NEG_INFINITY`].
///
/// [`NaN`]: core::f32::NaN
/// [`INFINITY`]: core::f32::INFINITY
/// [`NEG_INFINITY`]: core::f32::NEG_INFINITY
#[derive(Deref, Default, Debug, Copy, Clone, PartialEq, PartialOrd, Display)]
pub struct UFloat(f32);
impl UFloat {
/// Makes a new [`UFloat`] from an [`f32`].
///
/// # Panics
///
/// If the given float is negative, infinite or [`NaN`].
///
/// # Examples
///
/// ```
/// use hls_m3u8::types::UFloat;
///
/// let float = UFloat::new(1.0);
/// ```
///
/// This would panic:
///
/// ```should_panic
/// use hls_m3u8::types::UFloat;
///
/// let float = UFloat::new(-1.0);
/// ```
///
/// [`NaN`]: core::f32::NAN
pub fn new(float: f32) -> Self {
if float.is_infinite() {
panic!("float must be finite: `{}`", float);
}
if float.is_nan() {
panic!("float must not be `NaN`");
}
if float.is_sign_negative() {
panic!("float must be positive: `{}`", float);
}
Self(float)
}
/// Returns the underlying [`f32`].
pub const fn as_f32(self) -> f32 { self.0 }
}
impl FromStr for UFloat {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let float = f32::from_str(input).map_err(Error::parse_float)?;
Self::try_from(float)
}
}
impl TryFrom<f32> for UFloat {
type Error = Error;
fn try_from(float: f32) -> Result<Self, Self::Error> {
if float.is_infinite() {
return Err(Error::custom(format!("float must be finite: `{}`", float)));
}
if float.is_nan() {
return Err(Error::custom("float must not be `NaN`"));
}
if float.is_sign_negative() {
return Err(Error::custom(format!(
"float must be positive: `{}`",
float
)));
}
Ok(Self(float))
}
}
macro_rules! implement_from {
( $( $type:tt ),+ ) => {
$(
impl ::core::convert::From<$type> for UFloat {
fn from(value: $type) -> Self {
Self(value as f32)
}
}
)+
}
}
implement_from!(u16, u8);
// convenience implementation to compare f32 with a Float.
impl PartialEq<f32> for UFloat {
fn eq(&self, other: &f32) -> bool { &self.0 == other }
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_display() {
assert_eq!(UFloat::new(22.0).to_string(), "22".to_string());
assert_eq!(
UFloat::new(3.14159265359).to_string(),
"3.1415927".to_string()
);
}
#[test]
fn test_parser() {
assert_eq!(UFloat::new(22.0), UFloat::from_str("22").unwrap());
assert_eq!(
UFloat::new(3.14159265359),
UFloat::from_str("3.14159265359").unwrap()
);
assert!(UFloat::from_str("1#").is_err());
assert!(UFloat::from_str("-1.0").is_err());
assert!(UFloat::from_str("NaN").is_err());
assert!(UFloat::from_str("inf").is_err());
assert!(UFloat::from_str("-inf").is_err());
}
#[test]
#[should_panic = "float must be positive: `-1.1`"]
fn test_new_negative() { UFloat::new(-1.1); }
#[test]
#[should_panic = "float must be finite: `inf`"]
fn test_new_infinite() { UFloat::new(::core::f32::INFINITY); }
#[test]
#[should_panic = "float must be finite: `-inf`"]
fn test_new_neg_infinite() { UFloat::new(::core::f32::NEG_INFINITY); }
#[test]
#[should_panic = "float must not be `NaN`"]
fn test_new_nan() { UFloat::new(::core::f32::NAN); }
#[test]
fn test_partial_eq() {
assert_eq!(UFloat::new(1.1), 1.1);
}
#[test]
fn test_as_f32() {
assert_eq!(UFloat::new(1.1).as_f32(), 1.1_f32);
}
#[test]
fn test_from() {
assert_eq!(UFloat::from(1_u8), UFloat::new(1.0));
assert_eq!(UFloat::from(1_u16), UFloat::new(1.0));
}
#[test]
fn test_try_from() {
assert_eq!(UFloat::try_from(1.1_f32).unwrap(), UFloat::new(1.1));
assert!(UFloat::try_from(-1.1_f32).is_err());
assert!(UFloat::try_from(::core::f32::INFINITY).is_err());
assert!(UFloat::try_from(::core::f32::NAN).is_err());
assert!(UFloat::try_from(::core::f32::NEG_INFINITY).is_err());
}
}

View file

@ -11,27 +11,6 @@ macro_rules! required_version {
}
}
macro_rules! impl_from {
( $($( $type:tt ),* => $target:path ),* ) => {
use ::core::convert::From;
$( // repeat $target
$( // repeat $type
impl From<$type> for $target {
fn from(value: $type) -> Self {
Self::from_f64_unchecked(value.into())
}
}
)*
)*
};
}
impl_from![
u8, u16, u32 => crate::types::DecimalFloatingPoint,
u8, i8, u16, i16, u32, i32, f32, f64 => crate::types::SignedDecimalFloatingPoint
];
pub(crate) fn parse_iv_from_str(input: &str) -> crate::Result<[u8; 16]> {
if !(input.starts_with("0x") || input.starts_with("0X")) {
return Err(Error::invalid_input());