use super::mpls::TsPlaylistFile;
use crate::primitives::Pid;
use crate::stream::TsStream;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MeasuredSnapshot {
pub file: String,
pub playlists: Vec<MeasuredPlaylist>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MeasuredPlaylist {
pub name: String,
pub measured_bytes: u64,
pub clips: Vec<MeasuredClip>,
pub streams: Vec<MeasuredStream>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MeasuredClip {
pub name: String,
pub angle_index: i32,
pub measured_bytes: u64,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MeasuredStream {
pub pid: Pid,
pub angle_index: usize,
pub bitrate: i64,
pub active_bitrate: i64,
}
pub(crate) const fn packet_size(packets: u64) -> u64 {
packets.wrapping_mul(192)
}
pub(crate) fn snapshot<'a>(
file: &str,
playlists: impl IntoIterator<Item = &'a TsPlaylistFile> + 'a,
) -> MeasuredSnapshot {
MeasuredSnapshot {
file: file.to_owned(),
playlists: playlists
.into_iter()
.filter_map(|playlist| playlist_tallies(file, playlist))
.collect(),
}
}
fn playlist_tallies(file: &str, playlist: &TsPlaylistFile) -> Option<MeasuredPlaylist> {
if !playlist.stream_clips.iter().any(|clip| clip.name == file) {
return None;
}
let clips: Vec<MeasuredClip> = playlist
.stream_clips
.iter()
.map(|clip| MeasuredClip {
name: clip.name.clone(),
angle_index: clip.angle_index,
measured_bytes: packet_size(clip.packet_count),
})
.collect();
let measured_bytes =
clips.iter().fold(0_u64, |sum, clip| sum.saturating_add(clip.measured_bytes));
let mut streams: Vec<MeasuredStream> =
playlist.streams.iter().map(|(pid, stream)| measured_stream(*pid, 0, stream)).collect();
for (index, angle) in playlist.angle_streams.iter().enumerate() {
streams.extend(
angle
.iter()
.map(|(pid, stream)| measured_stream(*pid, index.saturating_add(1), stream)),
);
}
Some(MeasuredPlaylist { name: playlist.name.clone(), measured_bytes, clips, streams })
}
const fn measured_stream(pid: u16, angle_index: usize, stream: &TsStream) -> MeasuredStream {
MeasuredStream {
pid: Pid::new(pid),
angle_index,
bitrate: stream.base().bit_rate,
active_bitrate: stream.base().active_bit_rate,
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{MeasuredClip, MeasuredStream, packet_size, snapshot};
use crate::bdrom::clpi::TsStreamClip;
use crate::bdrom::mpls::TsPlaylistFile;
use crate::primitives::Pid;
use crate::stream::{TsStream, TsStreamType, TsVideoStream};
fn playlist(name: &str, clips: &[(&str, i32, u64)]) -> TsPlaylistFile {
let mut playlist = TsPlaylistFile {
file_type: "MPLS0300".to_owned(),
name: name.to_owned(),
mvc_base_view_r: false,
chapters: Vec::new(),
playlist_streams: BTreeMap::new(),
streams: BTreeMap::new(),
angle_streams: Vec::new(),
stream_clips: Vec::new(),
angle_count: 0,
};
for &(file, angle_index, packet_count) in clips {
playlist.stream_clips.push(TsStreamClip {
name: file.to_owned(),
angle_index,
packet_count,
..TsStreamClip::default()
});
}
playlist
}
fn video(bit_rate: i64, active_bit_rate: i64) -> TsStream {
let mut stream = TsVideoStream::default();
stream.base.stream_type = TsStreamType::AvcVideo;
stream.base.bit_rate = bit_rate;
stream.base.active_bit_rate = active_bit_rate;
TsStream::Video(stream)
}
#[test]
fn a_snapshot_covers_only_the_playlists_that_play_the_file() {
let playlists = vec![
playlist("00000.MPLS", &[("00011.M2TS", 0, 10)]),
playlist("00001.MPLS", &[("00022.M2TS", 0, 20)]),
playlist("00002.MPLS", &[("00033.M2TS", 0, 30), ("00011.M2TS", 0, 40)]),
];
let taken = snapshot("00011.M2TS", &playlists);
assert_eq!(taken.file, "00011.M2TS");
let named: Vec<&str> = taken.playlists.iter().map(|p| p.name.as_str()).collect();
assert_eq!(named, ["00000.MPLS", "00002.MPLS"], "the playlist between them plays no 00011");
assert!(snapshot("00099.M2TS", &playlists).playlists.is_empty());
}
#[test]
fn a_playlists_bytes_are_its_clips_bytes_over_every_angle() {
let playlists =
vec![playlist("00000.MPLS", &[("00011.M2TS", 0, 10), ("00011.M2TS", 1, 3)])];
let taken = snapshot("00011.M2TS", &playlists);
let first = taken.playlists.first().expect("the playlist plays the file");
assert_eq!(first.measured_bytes, packet_size(13));
assert_eq!(
first.clips,
[
MeasuredClip {
name: "00011.M2TS".to_owned(),
angle_index: 0,
measured_bytes: packet_size(10),
},
MeasuredClip {
name: "00011.M2TS".to_owned(),
angle_index: 1,
measured_bytes: packet_size(3),
},
]
);
}
#[test]
fn the_clip_rows_cover_the_whole_playlist_not_just_the_scanned_file() {
let playlists =
vec![playlist("00000.MPLS", &[("00033.M2TS", 0, 5), ("00011.M2TS", 0, 10)])];
let taken = snapshot("00011.M2TS", &playlists);
let first = taken.playlists.first().expect("the playlist plays the file");
let rows: Vec<(&str, u64)> =
first.clips.iter().map(|clip| (clip.name.as_str(), clip.measured_bytes)).collect();
assert_eq!(rows, [("00033.M2TS", packet_size(5)), ("00011.M2TS", packet_size(10))]);
assert_eq!(first.measured_bytes, packet_size(15));
}
#[test]
fn every_presented_stream_reports_its_rates_under_its_own_angle() {
let mut playlists = vec![playlist("00000.MPLS", &[("00011.M2TS", 0, 10)])];
let first = playlists.first_mut().expect("one playlist");
first.streams = BTreeMap::from([(0x1011, video(7, 8)), (0x1100, video(5, 6))]);
first.angle_streams = vec![BTreeMap::from([(0x1011, video(3, 4))])];
let taken = snapshot("00011.M2TS", &playlists);
let measured = taken.playlists.first().expect("the playlist plays the file");
assert_eq!(
measured.streams,
[
MeasuredStream {
pid: Pid::new(0x1011),
angle_index: 0,
bitrate: 7,
active_bitrate: 8,
},
MeasuredStream {
pid: Pid::new(0x1100),
angle_index: 0,
bitrate: 5,
active_bitrate: 6,
},
MeasuredStream {
pid: Pid::new(0x1011),
angle_index: 1,
bitrate: 3,
active_bitrate: 4,
},
]
);
}
#[test]
fn an_unresolved_playlist_reports_no_streams() {
let playlists = vec![playlist("00000.MPLS", &[("00011.M2TS", 0, 10)])];
let taken = snapshot("00011.M2TS", &playlists);
let first = taken.playlists.first().expect("the playlist plays the file");
assert!(first.streams.is_empty());
assert_eq!(first.clips.len(), 1);
}
#[test]
fn a_packet_count_converts_at_192_bytes_a_packet() {
assert_eq!(packet_size(0), 0);
assert_eq!(packet_size(1), 192);
assert_eq!(packet_size(1000), 192_000);
}
}