use crate::dictionary::{Auid, Dictionary, TypeDefinition};
use crate::structured_storage::StorageReader;
use crate::timeline::{EditRate, Position};
use crate::{ContentStorage, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::collections::HashMap;
use std::io::{Cursor, Read, Seek};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct Header {
pub major_version: u16,
pub minor_version: u16,
pub byte_order: u16,
pub last_modified: u64,
pub object_model_version: u32,
pub operational_pattern: Auid,
pub essence_containers: Vec<Auid>,
}
impl Header {
#[must_use]
pub fn new() -> Self {
Self {
major_version: 1,
minor_version: 1,
byte_order: 0xFFFE,
last_modified: 0,
object_model_version: 1,
operational_pattern: Auid::null(),
essence_containers: Vec::new(),
}
}
#[must_use]
pub fn version_string(&self) -> String {
format!("{}.{}", self.major_version, self.minor_version)
}
}
impl Default for Header {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Mob {
pub mob_id: Uuid,
pub name: String,
pub slots: Vec<MobSlot>,
pub mob_type: MobType,
pub creation_time: Option<u64>,
pub modified_time: Option<u64>,
pub comments: HashMap<String, String>,
pub attributes: HashMap<String, PropertyValue>,
}
impl Mob {
#[must_use]
pub fn new(mob_id: Uuid, name: String, mob_type: MobType) -> Self {
Self {
mob_id,
name,
slots: Vec::new(),
mob_type,
creation_time: None,
modified_time: None,
comments: HashMap::new(),
attributes: HashMap::new(),
}
}
#[must_use]
pub fn mob_id(&self) -> Uuid {
self.mob_id
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
pub fn mob_id_mut(&mut self) -> &mut Uuid {
&mut self.mob_id
}
#[must_use]
pub fn mob_type(&self) -> MobType {
self.mob_type
}
#[must_use]
pub fn is_master_mob(&self) -> bool {
matches!(self.mob_type, MobType::Master)
}
#[must_use]
pub fn is_source_mob(&self) -> bool {
matches!(self.mob_type, MobType::Source)
}
#[must_use]
pub fn is_composition_mob(&self) -> bool {
matches!(self.mob_type, MobType::Composition)
}
#[must_use]
pub fn slots(&self) -> &[MobSlot] {
&self.slots
}
#[must_use]
pub fn get_slot(&self, slot_id: u32) -> Option<&MobSlot> {
self.slots.iter().find(|s| s.slot_id == slot_id)
}
pub fn add_slot(&mut self, slot: MobSlot) {
self.slots.push(slot);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MobType {
Master,
Source,
Composition,
}
#[derive(Debug, Clone)]
pub struct MobSlot {
pub slot_id: u32,
pub name: String,
pub physical_track_number: Option<u32>,
pub edit_rate: EditRate,
pub origin: Position,
pub segment: Option<Box<Segment>>,
pub slot_type: SlotType,
}
impl MobSlot {
#[must_use]
pub fn new_timeline(slot_id: u32, name: String, edit_rate: EditRate, origin: Position) -> Self {
Self {
slot_id,
name,
physical_track_number: None,
edit_rate,
origin,
segment: None,
slot_type: SlotType::Timeline,
}
}
#[must_use]
pub fn new_event(slot_id: u32, name: String, edit_rate: EditRate) -> Self {
Self {
slot_id,
name,
physical_track_number: None,
edit_rate,
origin: Position::zero(),
segment: None,
slot_type: SlotType::Event,
}
}
#[must_use]
pub fn segment(&self) -> Option<&Segment> {
self.segment.as_deref()
}
pub fn set_segment(&mut self, segment: Segment) {
self.segment = Some(Box::new(segment));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotType {
Timeline,
Event,
Static,
}
#[derive(Debug, Clone)]
pub enum Segment {
Sequence(SequenceSegment),
SourceClip(SourceClipSegment),
Filler(FillerSegment),
Transition(TransitionSegment),
Selector(SelectorSegment),
NestedScope(NestedScopeSegment),
OperationGroup(OperationGroupSegment),
}
impl Segment {
#[must_use]
pub fn length(&self) -> Option<i64> {
match self {
Segment::Sequence(s) => s.length,
Segment::SourceClip(s) => Some(s.length),
Segment::Filler(s) => Some(s.length),
Segment::Transition(s) => Some(s.length),
Segment::Selector(s) => s.length,
Segment::NestedScope(s) => s.length,
Segment::OperationGroup(s) => s.length,
}
}
#[must_use]
pub fn is_sequence(&self) -> bool {
matches!(self, Segment::Sequence(_))
}
#[must_use]
pub fn is_source_clip(&self) -> bool {
matches!(self, Segment::SourceClip(_))
}
}
#[derive(Debug, Clone)]
pub struct SequenceSegment {
pub components: Vec<Component>,
pub length: Option<i64>,
}
impl SequenceSegment {
#[must_use]
pub fn new() -> Self {
Self {
components: Vec::new(),
length: None,
}
}
pub fn add_component(&mut self, component: Component) {
self.components.push(component);
}
#[must_use]
pub fn calculate_length(&self) -> Option<i64> {
let mut total = 0i64;
for comp in &self.components {
total += comp.length()?;
}
Some(total)
}
}
impl Default for SequenceSegment {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SourceClipSegment {
pub length: i64,
pub start_time: Position,
pub source_mob_id: Uuid,
pub source_mob_slot_id: u32,
}
impl SourceClipSegment {
#[must_use]
pub fn new(
length: i64,
start_time: Position,
source_mob_id: Uuid,
source_mob_slot_id: u32,
) -> Self {
Self {
length,
start_time,
source_mob_id,
source_mob_slot_id,
}
}
}
#[derive(Debug, Clone)]
pub struct FillerSegment {
pub length: i64,
}
impl FillerSegment {
#[must_use]
pub fn new(length: i64) -> Self {
Self { length }
}
}
#[derive(Debug, Clone)]
pub struct TransitionSegment {
pub length: i64,
pub cut_point: Position,
pub effect: Option<Box<OperationGroupSegment>>,
}
impl TransitionSegment {
#[must_use]
pub fn new(length: i64, cut_point: Position) -> Self {
Self {
length,
cut_point,
effect: None,
}
}
}
#[derive(Debug, Clone)]
pub struct SelectorSegment {
pub selected: u32,
pub alternates: Vec<Segment>,
pub length: Option<i64>,
}
impl SelectorSegment {
#[must_use]
pub fn new(selected: u32) -> Self {
Self {
selected,
alternates: Vec::new(),
length: None,
}
}
}
#[derive(Debug, Clone)]
pub struct NestedScopeSegment {
pub slots: Vec<MobSlot>,
pub length: Option<i64>,
}
impl NestedScopeSegment {
#[must_use]
pub fn new() -> Self {
Self {
slots: Vec::new(),
length: None,
}
}
}
impl Default for NestedScopeSegment {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct OperationGroupSegment {
pub operation_id: Auid,
pub input_segments: Vec<Segment>,
pub parameters: Vec<Parameter>,
pub length: Option<i64>,
}
impl OperationGroupSegment {
#[must_use]
pub fn new(operation_id: Auid) -> Self {
Self {
operation_id,
input_segments: Vec::new(),
parameters: Vec::new(),
length: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Component {
pub data_definition: Auid,
pub segment: Segment,
}
impl Component {
#[must_use]
pub fn new(data_definition: Auid, segment: Segment) -> Self {
Self {
data_definition,
segment,
}
}
#[must_use]
pub fn length(&self) -> Option<i64> {
self.segment.length()
}
#[must_use]
pub fn is_picture(&self) -> bool {
self.data_definition.is_picture()
}
#[must_use]
pub fn is_sound(&self) -> bool {
self.data_definition.is_sound()
}
#[must_use]
pub fn is_timecode(&self) -> bool {
self.data_definition.is_timecode()
}
}
#[derive(Debug, Clone)]
pub struct Parameter {
pub definition_id: Auid,
pub name: String,
pub value: ParameterValue,
}
impl Parameter {
#[must_use]
pub fn new(definition_id: Auid, name: String, value: ParameterValue) -> Self {
Self {
definition_id,
name,
value,
}
}
}
#[derive(Debug, Clone)]
pub enum ParameterValue {
Constant(PropertyValue),
Varying(Vec<Keyframe>),
}
#[derive(Debug, Clone)]
pub struct Keyframe {
pub time: Position,
pub value: PropertyValue,
pub interpolation: InterpolationType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterpolationType {
None,
Linear,
Bezier,
Cubic,
}
#[derive(Debug, Clone)]
pub enum PropertyValue {
Boolean(bool),
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Float(f32),
Double(f64),
String(String),
Bytes(Vec<u8>),
Auid(Auid),
Uuid(Uuid),
StrongRef(Uuid),
WeakRef(Uuid),
Array(Vec<PropertyValue>),
Record(HashMap<String, PropertyValue>),
Set(Vec<PropertyValue>),
}
impl PropertyValue {
pub fn read_from_bytes(data: &[u8], type_def: &TypeDefinition) -> Result<Self> {
let mut cursor = Cursor::new(data);
match type_def.name() {
"Boolean" | "bool" => {
let val = cursor.read_u8()? != 0;
Ok(PropertyValue::Boolean(val))
}
"Int8" | "i8" => {
let val = cursor.read_i8()?;
Ok(PropertyValue::Int8(val))
}
"UInt8" | "u8" => {
let val = cursor.read_u8()?;
Ok(PropertyValue::UInt8(val))
}
"Int16" | "i16" => {
let val = cursor.read_i16::<LittleEndian>()?;
Ok(PropertyValue::Int16(val))
}
"UInt16" | "u16" => {
let val = cursor.read_u16::<LittleEndian>()?;
Ok(PropertyValue::UInt16(val))
}
"Int32" | "i32" => {
let val = cursor.read_i32::<LittleEndian>()?;
Ok(PropertyValue::Int32(val))
}
"UInt32" | "u32" => {
let val = cursor.read_u32::<LittleEndian>()?;
Ok(PropertyValue::UInt32(val))
}
"Int64" | "i64" | "Length" | "Position" => {
let val = cursor.read_i64::<LittleEndian>()?;
Ok(PropertyValue::Int64(val))
}
"UInt64" | "u64" => {
let val = cursor.read_u64::<LittleEndian>()?;
Ok(PropertyValue::UInt64(val))
}
"Float" | "f32" => {
let val = cursor.read_f32::<LittleEndian>()?;
Ok(PropertyValue::Float(val))
}
"Double" | "f64" => {
let val = cursor.read_f64::<LittleEndian>()?;
Ok(PropertyValue::Double(val))
}
"String" | "UTF16String" => {
let mut utf16_chars = Vec::new();
while let Ok(ch) = cursor.read_u16::<LittleEndian>() {
if ch == 0 {
break;
}
utf16_chars.push(ch);
}
let string = String::from_utf16_lossy(&utf16_chars);
Ok(PropertyValue::String(string))
}
"AUID" => {
let mut auid_bytes = [0u8; 16];
cursor.read_exact(&mut auid_bytes)?;
let auid = Auid::from_bytes(&auid_bytes);
Ok(PropertyValue::Auid(auid))
}
"MobID" | "UUID" => {
let mut uuid_bytes = [0u8; 16];
cursor.read_exact(&mut uuid_bytes)?;
let uuid = Uuid::from_bytes(uuid_bytes);
Ok(PropertyValue::Uuid(uuid))
}
_ => {
Ok(PropertyValue::Bytes(data.to_vec()))
}
}
}
#[must_use]
pub fn as_int(&self) -> Option<i64> {
match self {
PropertyValue::Int8(v) => Some(i64::from(*v)),
PropertyValue::UInt8(v) => Some(i64::from(*v)),
PropertyValue::Int16(v) => Some(i64::from(*v)),
PropertyValue::UInt16(v) => Some(i64::from(*v)),
PropertyValue::Int32(v) => Some(i64::from(*v)),
PropertyValue::UInt32(v) => Some(i64::from(*v)),
PropertyValue::Int64(v) => Some(*v),
PropertyValue::UInt64(v) => Some(*v as i64),
_ => None,
}
}
#[must_use]
pub fn as_string(&self) -> Option<&str> {
match self {
PropertyValue::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_uuid(&self) -> Option<Uuid> {
match self {
PropertyValue::Uuid(u) => Some(*u),
PropertyValue::StrongRef(u) => Some(*u),
PropertyValue::WeakRef(u) => Some(*u),
_ => None,
}
}
}
pub struct ObjectResolver {
objects: HashMap<Uuid, ObjectData>,
}
impl ObjectResolver {
#[must_use]
pub fn new() -> Self {
Self {
objects: HashMap::new(),
}
}
pub fn register(&mut self, id: Uuid, data: ObjectData) {
self.objects.insert(id, data);
}
#[must_use]
pub fn resolve(&self, id: &Uuid) -> Option<&ObjectData> {
self.objects.get(id)
}
#[must_use]
pub fn resolve_strong_ref(&self, value: &PropertyValue) -> Option<&ObjectData> {
if let PropertyValue::StrongRef(id) = value {
self.resolve(id)
} else {
None
}
}
#[must_use]
pub fn resolve_weak_ref(&self, value: &PropertyValue) -> Option<&ObjectData> {
if let PropertyValue::WeakRef(id) = value {
self.resolve(id)
} else {
None
}
}
}
impl Default for ObjectResolver {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ObjectData {
pub id: Uuid,
pub class_id: Auid,
pub properties: HashMap<String, PropertyValue>,
}
impl ObjectData {
#[must_use]
pub fn new(id: Uuid, class_id: Auid) -> Self {
Self {
id,
class_id,
properties: HashMap::new(),
}
}
#[must_use]
pub fn get_property(&self, name: &str) -> Option<&PropertyValue> {
self.properties.get(name)
}
pub fn set_property(&mut self, name: String, value: PropertyValue) {
self.properties.insert(name, value);
}
#[must_use]
pub fn get_int(&self, name: &str) -> Option<i64> {
self.get_property(name).and_then(PropertyValue::as_int)
}
#[must_use]
pub fn get_string(&self, name: &str) -> Option<&str> {
self.get_property(name).and_then(|v| v.as_string())
}
#[must_use]
pub fn get_uuid(&self, name: &str) -> Option<Uuid> {
self.get_property(name).and_then(PropertyValue::as_uuid)
}
}
pub fn read_header<R: Read + Seek>(_storage: &mut StorageReader<R>) -> Result<Header> {
Ok(Header::new())
}
pub fn read_content_storage<R: Read + Seek>(
_storage: &mut StorageReader<R>,
_dictionary: &Dictionary,
) -> Result<ContentStorage> {
Ok(ContentStorage::new())
}
pub trait PropertyVisitor {
fn visit_property(&mut self, name: &str, value: &PropertyValue) -> Result<()>;
fn visit_object(&mut self, obj: &ObjectData) -> Result<()> {
for (name, value) in &obj.properties {
self.visit_property(name, value)?;
}
Ok(())
}
}
pub struct PropertyTraverser<'a> {
resolver: &'a ObjectResolver,
}
impl<'a> PropertyTraverser<'a> {
#[must_use]
pub fn new(resolver: &'a ObjectResolver) -> Self {
Self { resolver }
}
pub fn traverse<V: PropertyVisitor>(&self, obj: &ObjectData, visitor: &mut V) -> Result<()> {
visitor.visit_object(obj)?;
for value in obj.properties.values() {
if let Some(ref_obj) = self.resolver.resolve_strong_ref(value) {
self.traverse(ref_obj, visitor)?;
}
}
Ok(())
}
}
pub struct WeakReferenceMap {
references: HashMap<Uuid, Uuid>,
}
impl WeakReferenceMap {
#[must_use]
pub fn new() -> Self {
Self {
references: HashMap::new(),
}
}
pub fn add(&mut self, target_id: Uuid, mob_id: Uuid) {
self.references.insert(target_id, mob_id);
}
#[must_use]
pub fn resolve(&self, target_id: &Uuid) -> Option<Uuid> {
self.references.get(target_id).copied()
}
}
impl Default for WeakReferenceMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_header_creation() {
let header = Header::new();
assert_eq!(header.major_version, 1);
assert_eq!(header.minor_version, 1);
assert_eq!(header.version_string(), "1.1");
}
#[test]
fn test_mob_creation() {
let mob_id = Uuid::new_v4();
let mob = Mob::new(mob_id, "Test Mob".to_string(), MobType::Master);
assert_eq!(mob.mob_id(), mob_id);
assert_eq!(mob.name(), "Test Mob");
assert!(mob.is_master_mob());
assert!(!mob.is_source_mob());
}
#[test]
fn test_mob_slot_creation() {
let slot = MobSlot::new_timeline(
1,
"Video".to_string(),
EditRate::new(24, 1),
Position::zero(),
);
assert_eq!(slot.slot_id, 1);
assert_eq!(slot.name, "Video");
assert_eq!(slot.slot_type, SlotType::Timeline);
}
#[test]
fn test_sequence_segment() {
let seq = SequenceSegment::new();
assert!(seq.components.is_empty());
assert_eq!(seq.calculate_length(), Some(0));
}
#[test]
fn test_source_clip() {
let clip = SourceClipSegment::new(100, Position::zero(), Uuid::new_v4(), 1);
assert_eq!(clip.length, 100);
}
#[test]
fn test_filler_segment() {
let filler = FillerSegment::new(50);
assert_eq!(filler.length, 50);
}
#[test]
fn test_property_value_int() {
let val = PropertyValue::Int32(42);
assert_eq!(val.as_int(), Some(42));
}
#[test]
fn test_property_value_string() {
let val = PropertyValue::String("test".to_string());
assert_eq!(val.as_string(), Some("test"));
}
#[test]
fn test_object_data() {
let obj = ObjectData::new(Uuid::new_v4(), Auid::null());
assert!(obj.properties.is_empty());
}
#[test]
fn test_object_resolver() {
let resolver = ObjectResolver::new();
let id = Uuid::new_v4();
assert!(resolver.resolve(&id).is_none());
}
#[test]
fn test_weak_reference_map() {
let mut map = WeakReferenceMap::new();
let target_id = Uuid::new_v4();
let mob_id = Uuid::new_v4();
map.add(target_id, mob_id);
assert_eq!(map.resolve(&target_id), Some(mob_id));
}
}