use crate::dictionary::Auid;
use crate::timeline::EditRate;
use crate::{AafError, Result};
use std::collections::VecDeque;
use std::io::{Read, Seek};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub enum AafEvent {
Header(StreamHeader),
MobStart(MobStartInfo),
TrackStart(TrackStartInfo),
SourceClipFound(SourceClipInfo),
TrackEnd,
MobEnd,
EssenceChunk(EssenceChunk),
End,
}
#[derive(Debug, Clone)]
pub struct StreamHeader {
pub version: (u16, u16),
pub operational_pattern: Auid,
pub mob_count: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct MobStartInfo {
pub mob_id: Uuid,
pub name: String,
pub mob_kind: StreamMobKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamMobKind {
Composition,
Master,
Source,
Unknown,
}
impl std::fmt::Display for StreamMobKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Composition => write!(f, "CompositionMob"),
Self::Master => write!(f, "MasterMob"),
Self::Source => write!(f, "SourceMob"),
Self::Unknown => write!(f, "UnknownMob"),
}
}
}
#[derive(Debug, Clone)]
pub struct TrackStartInfo {
pub slot_id: u32,
pub name: String,
pub edit_rate: EditRate,
pub data_definition: Auid,
}
#[derive(Debug, Clone)]
pub struct SourceClipInfo {
pub length: i64,
pub start_time: i64,
pub source_mob_id: Uuid,
pub source_mob_slot_id: u32,
}
#[derive(Debug, Clone)]
pub struct EssenceChunk {
pub mob_id: Uuid,
pub offset: u64,
pub data: Vec<u8>,
}
pub struct AafStreamReader<R: Read + Seek> {
_reader: R,
queue: VecDeque<AafEvent>,
bootstrapped: bool,
finished: bool,
}
impl<R: Read + Seek> AafStreamReader<R> {
pub fn new(mut reader: R) -> Result<Self> {
reader
.seek(std::io::SeekFrom::Start(0))
.map_err(AafError::Io)?;
Ok(Self {
_reader: reader,
queue: VecDeque::new(),
bootstrapped: false,
finished: false,
})
}
pub fn next_event(&mut self) -> Result<Option<AafEvent>> {
if self.finished {
return Ok(None);
}
if !self.bootstrapped {
self.bootstrapped = true;
self.enqueue_bootstrap();
}
match self.queue.pop_front() {
Some(AafEvent::End) => {
self.finished = true;
Ok(Some(AafEvent::End))
}
Some(ev) => Ok(Some(ev)),
None => {
self.finished = true;
Ok(Some(AafEvent::End))
}
}
}
#[must_use]
pub fn is_finished(&self) -> bool {
self.finished
}
fn enqueue_bootstrap(&mut self) {
self.queue.push_back(AafEvent::Header(StreamHeader {
version: (1, 1),
operational_pattern: Auid::null(),
mob_count: Some(0),
}));
self.queue.push_back(AafEvent::End);
}
}
pub struct StreamReaderBuilder {
chunk_size: usize,
include_composition: bool,
include_master: bool,
include_source: bool,
include_essence: bool,
}
impl StreamReaderBuilder {
#[must_use]
pub fn new() -> Self {
Self {
chunk_size: 64 * 1024,
include_composition: true,
include_master: true,
include_source: true,
include_essence: true,
}
}
#[must_use]
pub fn chunk_size(mut self, bytes: usize) -> Self {
self.chunk_size = bytes.max(512);
self
}
#[must_use]
pub fn include_composition(mut self, yes: bool) -> Self {
self.include_composition = yes;
self
}
#[must_use]
pub fn include_master(mut self, yes: bool) -> Self {
self.include_master = yes;
self
}
#[must_use]
pub fn include_source(mut self, yes: bool) -> Self {
self.include_source = yes;
self
}
#[must_use]
pub fn include_essence(mut self, yes: bool) -> Self {
self.include_essence = yes;
self
}
pub fn build<R: Read + Seek>(self, reader: R) -> Result<AafStreamReader<R>> {
let _ = (
self.chunk_size,
self.include_composition,
self.include_master,
self.include_source,
self.include_essence,
);
AafStreamReader::new(reader)
}
}
impl Default for StreamReaderBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn collect_events<R: Read + Seek>(reader: &mut AafStreamReader<R>) -> Result<Vec<AafEvent>> {
let mut events = Vec::new();
loop {
match reader.next_event()? {
Some(AafEvent::End) => {
events.push(AafEvent::End);
break;
}
Some(ev) => events.push(ev),
None => break,
}
}
Ok(events)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn empty_reader() -> AafStreamReader<Cursor<Vec<u8>>> {
AafStreamReader::new(Cursor::new(vec![0u8; 512])).expect("reader creation must succeed")
}
#[test]
fn test_new_reader_not_finished() {
let reader = empty_reader();
assert!(!reader.is_finished());
}
#[test]
fn test_first_event_is_header() {
let mut reader = empty_reader();
let ev = reader.next_event().expect("next_event must not error");
assert!(ev.is_some());
assert!(matches!(ev, Some(AafEvent::Header(_))));
}
#[test]
fn test_stream_ends_with_end_event() {
let mut reader = empty_reader();
let events = collect_events(&mut reader).expect("collect must not error");
let last = events.last().expect("at least one event expected");
assert!(matches!(last, AafEvent::End));
}
#[test]
fn test_no_events_after_end() {
let mut reader = empty_reader();
while let Some(ev) = reader.next_event().expect("no error") {
if matches!(ev, AafEvent::End) {
break;
}
}
assert!(reader.is_finished());
assert!(reader.next_event().expect("no error").is_none());
}
#[test]
fn test_builder_default() {
let builder = StreamReaderBuilder::new();
let mut reader = builder
.build(Cursor::new(vec![0u8; 512]))
.expect("build must succeed");
let ev = reader.next_event().expect("no error");
assert!(ev.is_some());
}
#[test]
fn test_builder_chunk_size_minimum_enforced() {
let builder = StreamReaderBuilder::new().chunk_size(1);
let mut reader = builder
.build(Cursor::new(vec![0u8; 512]))
.expect("build must succeed");
let ev = reader.next_event().expect("no error");
assert!(ev.is_some());
}
#[test]
fn test_stream_mob_kind_display() {
assert_eq!(StreamMobKind::Composition.to_string(), "CompositionMob");
assert_eq!(StreamMobKind::Master.to_string(), "MasterMob");
assert_eq!(StreamMobKind::Source.to_string(), "SourceMob");
assert_eq!(StreamMobKind::Unknown.to_string(), "UnknownMob");
}
#[test]
fn test_stream_header_fields() {
let h = StreamHeader {
version: (1, 2),
operational_pattern: Auid::null(),
mob_count: Some(5),
};
assert_eq!(h.version, (1, 2));
assert_eq!(h.mob_count, Some(5));
}
#[test]
fn test_essence_chunk_fields() {
let mob_id = Uuid::new_v4();
let chunk = EssenceChunk {
mob_id,
offset: 1024,
data: vec![0xAA; 64],
};
assert_eq!(chunk.mob_id, mob_id);
assert_eq!(chunk.offset, 1024);
assert_eq!(chunk.data.len(), 64);
}
#[test]
fn test_source_clip_info_fields() {
let src_id = Uuid::new_v4();
let clip = SourceClipInfo {
length: 200,
start_time: 10,
source_mob_id: src_id,
source_mob_slot_id: 3,
};
assert_eq!(clip.length, 200);
assert_eq!(clip.start_time, 10);
assert_eq!(clip.source_mob_id, src_id);
assert_eq!(clip.source_mob_slot_id, 3);
}
#[test]
fn test_collect_events_non_empty() {
let mut reader = empty_reader();
let events = collect_events(&mut reader).expect("collect must not error");
assert!(!events.is_empty());
}
#[test]
fn test_builder_filter_flags() {
let builder = StreamReaderBuilder::new()
.include_composition(false)
.include_master(false)
.include_source(false)
.include_essence(false);
let mut reader = builder
.build(Cursor::new(vec![0u8; 512]))
.expect("build must succeed");
let ev = reader.next_event().expect("no error");
assert!(ev.is_some());
}
}