use crate::composition::{CompositionMob, Sequence, Track};
use crate::dictionary::{Auid, Dictionary};
use crate::object_model::{Header, Mob};
use crate::structured_storage::StorageWriter;
use crate::{AafFile, ContentStorage, Result};
use std::fs::File;
use std::io::{Seek, Write};
use std::path::Path;
use uuid::Uuid;
pub struct AafWriter<W: Write + Seek> {
storage: StorageWriter<W>,
header: Header,
dictionary: Dictionary,
content_storage: ContentStorage,
}
impl<W: Write + Seek> AafWriter<W> {
pub fn new(writer: W) -> Result<Self> {
let storage = StorageWriter::new(writer)?;
Ok(Self {
storage,
header: Header::new(),
dictionary: Dictionary::new(),
content_storage: ContentStorage::new(),
})
}
pub fn set_header(&mut self, header: Header) {
self.header = header;
}
pub fn content_storage_mut(&mut self) -> &mut ContentStorage {
&mut self.content_storage
}
pub fn add_composition_mob(&mut self, comp_mob: CompositionMob) {
self.content_storage.add_composition_mob(comp_mob);
}
pub fn add_mob(&mut self, mob: Mob) {
self.content_storage.add_mob(mob);
}
pub fn write(&mut self) -> Result<()> {
self.write_header()?;
self.write_dictionary()?;
self.write_content_storage()?;
self.storage.finalize()?;
Ok(())
}
fn write_header(&mut self) -> Result<()> {
let header_data = self.serialize_header()?;
self.storage.write_stream("Header", &header_data)?;
Ok(())
}
fn write_dictionary(&mut self) -> Result<()> {
let dict_data = self.serialize_dictionary()?;
self.storage.write_stream("MetaDictionary", &dict_data)?;
Ok(())
}
fn write_content_storage(&mut self) -> Result<()> {
let content_data = self.serialize_content_storage()?;
self.storage.write_stream("ContentStorage", &content_data)?;
Ok(())
}
fn serialize_header(&self) -> Result<Vec<u8>> {
let mut data = Vec::new();
data.extend_from_slice(&self.header.major_version.to_le_bytes());
data.extend_from_slice(&self.header.minor_version.to_le_bytes());
data.extend_from_slice(&self.header.byte_order.to_le_bytes());
data.extend_from_slice(&self.header.last_modified.to_le_bytes());
data.extend_from_slice(&self.header.object_model_version.to_le_bytes());
data.extend_from_slice(self.header.operational_pattern.as_bytes());
Ok(data)
}
fn serialize_dictionary(&self) -> Result<Vec<u8>> {
let mut data = Vec::new();
data.extend_from_slice(b"AAF_DICT");
Ok(data)
}
fn serialize_content_storage(&self) -> Result<Vec<u8>> {
let mut data = Vec::new();
data.extend_from_slice(b"AAF_CONTENT");
let comp_mob_count = self.content_storage.composition_mobs().len() as u32;
data.extend_from_slice(&comp_mob_count.to_le_bytes());
let mob_count = (self.content_storage.master_mobs().len()
+ self.content_storage.source_mobs().len()) as u32;
data.extend_from_slice(&mob_count.to_le_bytes());
Ok(data)
}
}
impl AafWriter<File> {
pub fn create<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::create(path)?;
Self::new(file)
}
}
pub struct AafBuilder {
header: Header,
dictionary: Dictionary,
content_storage: ContentStorage,
}
impl AafBuilder {
#[must_use]
pub fn new() -> Self {
Self {
header: Header::new(),
dictionary: Dictionary::new(),
content_storage: ContentStorage::new(),
}
}
#[must_use]
pub fn with_header(mut self, header: Header) -> Self {
self.header = header;
self
}
#[must_use]
pub fn add_composition_mob(mut self, comp_mob: CompositionMob) -> Self {
self.content_storage.add_composition_mob(comp_mob);
self
}
#[must_use]
pub fn add_mob(mut self, mob: Mob) -> Self {
self.content_storage.add_mob(mob);
self
}
#[must_use]
pub fn build(self) -> AafFile {
AafFile {
header: self.header,
dictionary: self.dictionary,
content_storage: self.content_storage,
essence_data: Vec::new(),
}
}
pub fn write_to_file<P: AsRef<Path>>(self, path: P) -> Result<()> {
let aaf_file = self.build();
let mut writer = AafWriter::create(path)?;
writer.set_header(aaf_file.header);
*writer.content_storage_mut() = aaf_file.content_storage;
writer.write()?;
Ok(())
}
}
impl Default for AafBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct CompositionBuilder {
mob_id: Uuid,
name: String,
tracks: Vec<Track>,
}
impl CompositionBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
mob_id: Uuid::new_v4(),
name: name.into(),
tracks: Vec::new(),
}
}
#[must_use]
pub fn with_mob_id(mut self, mob_id: Uuid) -> Self {
self.mob_id = mob_id;
self
}
#[must_use]
pub fn add_track(mut self, track: Track) -> Self {
self.tracks.push(track);
self
}
#[must_use]
pub fn build(self) -> CompositionMob {
let mut comp_mob = CompositionMob::new(self.mob_id, self.name);
for track in self.tracks {
comp_mob.add_track(track);
}
comp_mob
}
}
pub struct TrackBuilder {
track_id: u32,
name: String,
edit_rate: crate::timeline::EditRate,
track_type: crate::composition::TrackType,
sequence: Option<Sequence>,
}
impl TrackBuilder {
pub fn new(
track_id: u32,
name: impl Into<String>,
edit_rate: crate::timeline::EditRate,
track_type: crate::composition::TrackType,
) -> Self {
Self {
track_id,
name: name.into(),
edit_rate,
track_type,
sequence: None,
}
}
#[must_use]
pub fn with_sequence(mut self, sequence: Sequence) -> Self {
self.sequence = Some(sequence);
self
}
#[must_use]
pub fn build(self) -> Track {
let mut track = Track::new(self.track_id, self.name, self.edit_rate, self.track_type);
if let Some(sequence) = self.sequence {
track.set_sequence(sequence);
}
track
}
}
pub struct SequenceBuilder {
data_definition: Auid,
components: Vec<crate::composition::SequenceComponent>,
}
impl SequenceBuilder {
#[must_use]
pub fn new(data_definition: Auid) -> Self {
Self {
data_definition,
components: Vec::new(),
}
}
#[must_use]
pub fn picture() -> Self {
Self::new(Auid::PICTURE)
}
#[must_use]
pub fn sound() -> Self {
Self::new(Auid::SOUND)
}
#[must_use]
pub fn add_component(mut self, component: crate::composition::SequenceComponent) -> Self {
self.components.push(component);
self
}
#[must_use]
pub fn build(self) -> Sequence {
let mut sequence = Sequence::new(self.data_definition);
for component in self.components {
sequence.add_component(component);
}
sequence
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::composition::{SequenceComponent, SourceClip, TrackType};
use crate::timeline::{EditRate, Position};
#[test]
fn test_aaf_builder() {
let builder = AafBuilder::new();
let aaf_file = builder.build();
assert!(aaf_file.composition_mobs().is_empty());
}
#[test]
fn test_composition_builder() {
let comp = CompositionBuilder::new("Test Composition").build();
assert_eq!(comp.name(), "Test Composition");
}
#[test]
fn test_track_builder() {
let track = TrackBuilder::new(1, "Video", EditRate::PAL_25, TrackType::Picture).build();
assert_eq!(track.track_id, 1);
assert_eq!(track.name, "Video");
}
#[test]
fn test_sequence_builder() {
let sequence = SequenceBuilder::picture()
.add_component(SequenceComponent::SourceClip(SourceClip::new(
100,
Position::zero(),
Uuid::new_v4(),
1,
)))
.build();
assert!(sequence.is_picture());
assert_eq!(sequence.duration(), Some(100));
}
#[test]
fn test_full_composition_build() {
let clip = SourceClip::new(100, Position::zero(), Uuid::new_v4(), 1);
let sequence = SequenceBuilder::picture()
.add_component(SequenceComponent::SourceClip(clip))
.build();
let track = TrackBuilder::new(1, "Video", EditRate::PAL_25, TrackType::Picture)
.with_sequence(sequence)
.build();
let comp = CompositionBuilder::new("My Edit").add_track(track).build();
assert_eq!(comp.name(), "My Edit");
assert_eq!(comp.tracks().len(), 1);
}
}