mirror of
https://github.com/sile/hls_m3u8.git
synced 2024-11-15 20:01:01 +00:00
Add media_playlist
module
This commit is contained in:
parent
f5393cb209
commit
2111d047c3
5 changed files with 258 additions and 4 deletions
|
@ -5,3 +5,6 @@ authors = ["Takeru Ohta <phjgt308@gmail.com>"]
|
|||
|
||||
[dependencies]
|
||||
trackable = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = "2"
|
|
@ -1,18 +1,36 @@
|
|||
extern crate clap;
|
||||
extern crate hls_m3u8;
|
||||
#[macro_use]
|
||||
extern crate trackable;
|
||||
|
||||
use std::io::{self, Read};
|
||||
use clap::{App, Arg};
|
||||
use hls_m3u8::media_playlist::MediaPlaylist;
|
||||
use trackable::error::Failure;
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("parse")
|
||||
.arg(
|
||||
Arg::with_name("M3U8_TYPE")
|
||||
.long("m3u8-type")
|
||||
.takes_value(true)
|
||||
.default_value("media")
|
||||
.possible_values(&["media", "master"]),
|
||||
)
|
||||
.get_matches();
|
||||
let mut m3u8 = String::new();
|
||||
track_try_unwrap!(
|
||||
io::stdin()
|
||||
.read_to_string(&mut m3u8)
|
||||
.map_err(Failure::from_error)
|
||||
);
|
||||
for line in hls_m3u8::line::Lines::new(&m3u8) {
|
||||
println!("{:?}", track_try_unwrap!(line));
|
||||
|
||||
match matches.value_of("M3U8_TYPE").unwrap() {
|
||||
"media" => {
|
||||
let playlist: MediaPlaylist = track_try_unwrap!(m3u8.parse());
|
||||
println!("{}", playlist);
|
||||
}
|
||||
"master" => unimplemented!(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
extern crate trackable;
|
||||
|
||||
// pub mod playlist;
|
||||
// pub mod media_playlist;
|
||||
// pub mod master_playlist;
|
||||
pub use error::{Error, ErrorKind};
|
||||
|
||||
pub mod attribute;
|
||||
pub mod media_playlist;
|
||||
pub mod media_segment;
|
||||
pub mod string;
|
||||
pub mod tag;
|
||||
|
|
191
src/media_playlist.rs
Normal file
191
src/media_playlist.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use {Error, ErrorKind, Result};
|
||||
use line::{Line, Lines};
|
||||
use media_segment::{MediaSegment, MediaSegmentBuilder};
|
||||
use tag::{ExtM3u, ExtXDiscontinuitySequence, ExtXEndList, ExtXIFramesOnly,
|
||||
ExtXIndependentSegments, ExtXMediaSequence, ExtXPlaylistType, ExtXStart,
|
||||
ExtXTargetDuration, ExtXVersion, Tag};
|
||||
use version::ProtocolVersion;
|
||||
|
||||
// TODO: There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist.
|
||||
// TODO: A Media Playlist tag MUST NOT appear in a Master Playlist.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaPlaylist {
|
||||
pub version: ExtXVersion,
|
||||
|
||||
// TODO: The EXTINF duration of each Media Segment in the Playlist
|
||||
// file, when rounded to the nearest integer, MUST be less than or equal
|
||||
// to the target duration
|
||||
pub target_duration: ExtXTargetDuration,
|
||||
|
||||
// TODO: The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media
|
||||
// Segment in the Playlist.
|
||||
pub media_sequence: Option<ExtXMediaSequence>,
|
||||
|
||||
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first
|
||||
// Media Segment in the Playlist.
|
||||
//
|
||||
// TODO: The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-
|
||||
// X-DISCONTINUITY tag.
|
||||
pub discontinuity_sequence: Option<ExtXDiscontinuitySequence>,
|
||||
|
||||
pub playlist_type: Option<ExtXPlaylistType>,
|
||||
pub i_frames_only: Option<ExtXIFramesOnly>,
|
||||
pub independent_segments: Option<ExtXIndependentSegments>,
|
||||
pub start: Option<ExtXStart>,
|
||||
|
||||
pub segments: Vec<MediaSegment>,
|
||||
|
||||
pub end_list: Option<ExtXEndList>,
|
||||
}
|
||||
impl fmt::Display for MediaPlaylist {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "{}", ExtM3u)?;
|
||||
if self.version.value() != ProtocolVersion::V1 {
|
||||
writeln!(f, "{}", self.version)?;
|
||||
}
|
||||
writeln!(f, "{}", self.target_duration)?;
|
||||
if let Some(ref t) = self.media_sequence {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
if let Some(ref t) = self.discontinuity_sequence {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
if let Some(ref t) = self.playlist_type {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
if let Some(ref t) = self.i_frames_only {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
if let Some(ref t) = self.independent_segments {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
if let Some(ref t) = self.start {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
for segment in &self.segments {
|
||||
writeln!(f, "{}", segment)?;
|
||||
}
|
||||
if let Some(ref t) = self.end_list {
|
||||
writeln!(f, "{}", t)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl FromStr for MediaPlaylist {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let mut version = None;
|
||||
let mut target_duration = None;
|
||||
let mut media_sequence = None;
|
||||
let mut discontinuity_sequence = None;
|
||||
let mut playlist_type = None;
|
||||
let mut i_frames_only = None;
|
||||
let mut independent_segments = None;
|
||||
let mut start = None;
|
||||
let mut end_list = None;
|
||||
|
||||
let mut segment = MediaSegmentBuilder::new();
|
||||
let mut segments = Vec::new();
|
||||
for (i, line) in Lines::new(s).enumerate() {
|
||||
match track!(line)? {
|
||||
Line::Blank | Line::Comment(_) => {}
|
||||
Line::Tag(tag) => {
|
||||
if i == 0 {
|
||||
track_assert_eq!(tag, Tag::ExtM3u(ExtM3u), ErrorKind::InvalidInput);
|
||||
continue;
|
||||
}
|
||||
match tag {
|
||||
Tag::ExtM3u(_) => unreachable!(),
|
||||
Tag::ExtXVersion(t) => {
|
||||
track_assert_eq!(version, None, ErrorKind::InvalidInput);
|
||||
version = Some(t);
|
||||
}
|
||||
Tag::ExtInf(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXByteRange(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXDiscontinuity(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXKey(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXMap(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXProgramDateTime(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXDateRange(t) => {
|
||||
segment.tag(t);
|
||||
}
|
||||
Tag::ExtXTargetDuration(t) => {
|
||||
track_assert_eq!(target_duration, None, ErrorKind::InvalidInput);
|
||||
target_duration = Some(t);
|
||||
}
|
||||
Tag::ExtXMediaSequence(t) => {
|
||||
track_assert_eq!(media_sequence, None, ErrorKind::InvalidInput);
|
||||
media_sequence = Some(t);
|
||||
}
|
||||
Tag::ExtXDiscontinuitySequence(t) => {
|
||||
track_assert_eq!(discontinuity_sequence, None, ErrorKind::InvalidInput);
|
||||
discontinuity_sequence = Some(t);
|
||||
}
|
||||
Tag::ExtXEndList(t) => {
|
||||
track_assert_eq!(end_list, None, ErrorKind::InvalidInput);
|
||||
end_list = Some(t);
|
||||
}
|
||||
Tag::ExtXPlaylistType(t) => {
|
||||
track_assert_eq!(playlist_type, None, ErrorKind::InvalidInput);
|
||||
playlist_type = Some(t);
|
||||
}
|
||||
Tag::ExtXIFramesOnly(t) => {
|
||||
track_assert_eq!(i_frames_only, None, ErrorKind::InvalidInput);
|
||||
i_frames_only = Some(t);
|
||||
}
|
||||
Tag::ExtXMedia(_)
|
||||
| Tag::ExtXStreamInf(_)
|
||||
| Tag::ExtXIFrameStreamInf(_)
|
||||
| Tag::ExtXSessionData(_)
|
||||
| Tag::ExtXSessionKey(_) => {
|
||||
track_panic!(ErrorKind::InvalidInput, "{}", tag)
|
||||
}
|
||||
Tag::ExtXIndependentSegments(t) => {
|
||||
track_assert_eq!(independent_segments, None, ErrorKind::InvalidInput);
|
||||
independent_segments = Some(t);
|
||||
}
|
||||
Tag::ExtXStart(t) => {
|
||||
track_assert_eq!(start, None, ErrorKind::InvalidInput);
|
||||
start = Some(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
Line::Uri(uri) => {
|
||||
segment.uri(uri.to_owned());
|
||||
segments.push(track!(segment.finish())?);
|
||||
segment = MediaSegmentBuilder::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let target_duration = track_assert_some!(target_duration, ErrorKind::InvalidInput);
|
||||
// TODO: check compatibility
|
||||
Ok(MediaPlaylist {
|
||||
version: version.unwrap_or_else(|| ExtXVersion::new(ProtocolVersion::V1)),
|
||||
target_duration,
|
||||
media_sequence,
|
||||
discontinuity_sequence,
|
||||
playlist_type,
|
||||
i_frames_only,
|
||||
independent_segments,
|
||||
start,
|
||||
segments,
|
||||
end_list,
|
||||
})
|
||||
}
|
||||
}
|
44
src/tag.rs
44
src/tag.rs
|
@ -15,6 +15,15 @@ macro_rules! may_invalid {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TagKind {
|
||||
Basic,
|
||||
MediaSegment,
|
||||
MediaPlaylist,
|
||||
MasterPlaylist,
|
||||
MediaOrMasterPlaylist,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MediaSegmentTag {
|
||||
ExtInf(ExtInf),
|
||||
|
@ -150,6 +159,32 @@ pub enum Tag {
|
|||
ExtXIndependentSegments(ExtXIndependentSegments),
|
||||
ExtXStart(ExtXStart),
|
||||
}
|
||||
impl Tag {
|
||||
pub fn kind(&self) -> TagKind {
|
||||
match *self {
|
||||
Tag::ExtM3u(_) | Tag::ExtXVersion(_) => TagKind::Basic,
|
||||
Tag::ExtInf(_)
|
||||
| Tag::ExtXByteRange(_)
|
||||
| Tag::ExtXDiscontinuity(_)
|
||||
| Tag::ExtXKey(_)
|
||||
| Tag::ExtXMap(_)
|
||||
| Tag::ExtXProgramDateTime(_)
|
||||
| Tag::ExtXDateRange(_) => TagKind::MediaSegment,
|
||||
Tag::ExtXTargetDuration(_)
|
||||
| Tag::ExtXMediaSequence(_)
|
||||
| Tag::ExtXDiscontinuitySequence(_)
|
||||
| Tag::ExtXEndList(_)
|
||||
| Tag::ExtXPlaylistType(_)
|
||||
| Tag::ExtXIFramesOnly(_) => TagKind::MediaPlaylist,
|
||||
Tag::ExtXMedia(_)
|
||||
| Tag::ExtXStreamInf(_)
|
||||
| Tag::ExtXIFrameStreamInf(_)
|
||||
| Tag::ExtXSessionData(_)
|
||||
| Tag::ExtXSessionKey(_) => TagKind::MasterPlaylist,
|
||||
Tag::ExtXIndependentSegments(_) | Tag::ExtXStart(_) => TagKind::MediaOrMasterPlaylist,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Tag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
|
@ -255,10 +290,17 @@ impl FromStr for ExtM3u {
|
|||
// TODO: A Playlist file MUST NOT contain more than one EXT-X-VERSION tag
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExtXVersion {
|
||||
pub version: ProtocolVersion,
|
||||
version: ProtocolVersion,
|
||||
}
|
||||
impl ExtXVersion {
|
||||
const PREFIX: &'static str = "#EXT-X-VERSION:";
|
||||
|
||||
pub fn new(version: ProtocolVersion) -> Self {
|
||||
ExtXVersion { version }
|
||||
}
|
||||
pub fn value(&self) -> ProtocolVersion {
|
||||
self.version
|
||||
}
|
||||
}
|
||||
impl fmt::Display for ExtXVersion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
|
Loading…
Reference in a new issue