mlv 0.1.1

Utilities for reading and writing Magic Lantern MLV files
Documentation
//! A library for reading and writing Magic Lantern Video files.
//!
//! A MLV file begins with 4 magic bytes ("MLVI"), followed by 32-bit header size,
//! followed by [`FileHeader`]; all of it can be automatically read with [`FileHeader::read`].
//!
//! It then contains a sequence of frames, as defined in the [`frame`] module, which can be
//! read iteratively.

#![warn(missing_docs)]

/// Types of MLV frames and their headers.
pub mod frame;

use frame::*;
use packbytes::{ByteArray, FromBytes, ToBytes};

use std::io;

/// "MLVI"
pub const MAGIC_BYTES: [u8; 4] = *b"MLVI";

/// Magicc Lanten Video header.
#[derive(FromBytes, ToBytes, Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FileHeader {
    /// Null-terminated C string of the exact revision of this format.
    pub version: [u8; 8],
    /// UID of the file (group) generated using hw counter, time of the day and PRNG.
    pub guid: u64,
    /// Id within `count` this file has.
    pub num: u16,
    /// How many files belong to this group?
    pub count: u16,
    /// File flags, refer to [`FileFlag`].
    pub flags: u32,
    /// Video class, refer to [`VideoClass`].
    pub video_class: u16,
    /// Audio class, refer to [`AudioClass`].
    pub audio_class: u16,
    /// Number of video frames in this file.
    pub video_frame_count: u32,
    /// Number of audio frames in this file.
    pub audio_frame_count: u32,
    /// Frames per second.
    pub fps: Fraction,
}

/// A fractional type used in the MLV format.
#[derive(FromBytes, ToBytes, Clone, Copy, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Fraction {
    /// Numerator.
    pub numerator: i32,
    /// Denominator.
    pub denominator: u32,
}

/// ID of the Canon camera model.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u32)]
pub enum Camera {
    /// 5D mark 2
    C5d2 = 0x80000218,
    /// 7D
    C7d = 0x80000250,
    /// 500D
    C500d = 0x80000252,
    /// 50D
    C50d = 0x80000261,
    /// 550D
    C550d = 0x80000270,
    /// 5D mark III
    C5d3 = 0x80000285,
    /// 600D
    C600d = 0x80000286,
    /// 60D
    C60d = 0x80000287,
    /// 1100D
    C1100d = 0x80000288,
    /// 7dD
    C7d2 = 0x80000289,
    /// 650D
    C650d = 0x80000301,
    /// 6D
    C6d = 0x80000302,
    /// 70D
    C70d = 0x80000325,
    /// 700D
    C700d = 0x80000326,
    /// 1200D
    C1200d = 0x80000327,
    /// EOS M
    M = 0x80000331,
    /// 100D
    C100d = 0x80000346,
    /// 760D
    C760d = 0x80000347,
    /// 5D mark IV
    C5d4 = 0x80000349,
    /// 80D
    C80d = 0x80000350,
    /// EOS M2
    M2 = 0x80000355,
    /// 5DS
    C5ds = 0x80000382,
    /// 750D
    C750d = 0x80000393,
    /// 5DS R
    C5dsr = 0x80000401,
    /// 1300D
    C1300d = 0x80000404,
    /// Unknown model
    Unknown,
}

/// MLV file flag.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FileFlag {
    /// Out of order data
    OutOfOrderData = 0x1,
    /// Dropped frames
    DroppedFrames = 0x2,
    /// Single image mode
    SingleImageMode = 0x4,
    /// Stopped due to error
    StoppedDueToError = 0x8,
}

/// MLV video class.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VideoClass {
    /// None
    None = 0,
    /// Raw video
    Raw = 1,
    /// YUV data
    Yuv = 2,
    /// JPEG frames
    Jpeg = 3,
    /// H.264 video
    H264 = 4,
}

/// MLV video class flag.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum VideoClassFlag {
    /// None
    None = 0,
    /// Lossless JPEG
    LJ92 = 0x20,
    /// Delta compression
    Delta = 0x40,
    /// LZMA compression
    Lzma = 0x80,
}

/// MLV audio class
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AudioClass {
    /// None
    None = 0,
    /// Waveform audio
    Wav,
}

/// MLV audio class flag
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AudioClassFlag {
    /// None
    None = 0,
    /// LZMA compressed audio
    Lzma = 0x80,
}

impl FileHeader {
    /// Read the MLV file header from the reader.
    /// Possible additional bytes in the header are returned in the second parameter.
    pub fn read<R: io::Read>(reader: &mut R) -> io::Result<(Self, Vec<u8>)> {
        let mut magic = [0; 4];
        reader.read_exact(&mut magic)?;
        if magic != MAGIC_BYTES {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "MLV header not found",
            ));
        }

        let headersize = <FileHeader as FromBytes>::Bytes::SIZE;
        let size = u32::read_packed(reader)? as usize;
        // First 8 bytes read as magic bytes and size
        let size = size.saturating_sub(8);
        if size < headersize {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Header size too small",
            ));
        }

        let header = FileHeader::read_packed(reader)?;

        let mut remaining = vec![0; size - headersize];
        reader.read_exact(&mut remaining)?;
        Ok((header, remaining))
    }

    /// Write the MLV file using the provided headers and binary data of video and audio frames.
    pub fn write_mlv<W, S, I, J>(
        mut self,
        headers: impl IntoIterator<Item = Frame<S>>,
        video_frames: impl IntoIterator<IntoIter = I>,
        audio_frames: impl IntoIterator<IntoIter = J>,
        writer: &mut W,
    ) -> io::Result<()>
    where
        W: io::Write,
        S: AsRef<[u8]>,
        I: Iterator,
        J: Iterator,
        I::Item: AsRef<[u8]>,
        J::Item: AsRef<[u8]>,
    {
        let headers = headers.into_iter();
        let video_frames = video_frames.into_iter();
        let audio_frames = audio_frames.into_iter();

        self.video_frame_count = video_frames.size_hint().0 as u32;
        self.audio_frame_count = audio_frames.size_hint().0 as u32;

        let headersize = (<FileHeader as ToBytes>::Bytes::SIZE + 8) as u32;
        writer.write_all(&MAGIC_BYTES)?;
        headersize.write_packed(writer)?;
        self.write_packed(writer)?;

        for frame in headers {
            frame.write(writer, 0)?;
        }

        let video_frame_time = (1_000_000 * self.fps.denominator) / self.fps.numerator as u32;
        for (number, payload) in video_frames.enumerate() {
            let frameblock = Frame {
                header: Video::from_frame_number(number as u32).into(),
                payload,
            };
            frameblock.write(writer, number as u64 * video_frame_time as u64)?;
        }

        let mut audio_frame_time = 0;
        if self.audio_frame_count != 0 {
            audio_frame_time = (self.video_frame_count * video_frame_time) / self.audio_frame_count;
        }
        for (number, payload) in audio_frames.enumerate() {
            let frameblock = Frame {
                header: Audio {
                    number: number as u32,
                }
                .into(),
                payload,
            };
            frameblock.write(writer, number as u64 * audio_frame_time as u64)?;
        }
        Ok(())
    }
}

impl Fraction {
    /// Create an fps value with the provided numerator and the denominator 1000.
    pub const fn new(numerator: i32, denominator: u32) -> Self {
        Self {
            numerator,
            denominator,
        }
    }

    /// Create an fps value with the provided numerator and the denominator 1000.
    pub const fn in_thousandths(numerator: i32) -> Self {
        Self {
            numerator,
            denominator: 1000,
        }
    }

    /// Create an fps value equivalent to the provided integer.
    pub const fn integral(n: i32) -> Self {
        Self {
            numerator: n * 1000,
            denominator: 1000,
        }
    }
}

impl From<Fraction> for f32 {
    fn from(f: Fraction) -> f32 {
        f.numerator as f32 / f.denominator as f32
    }
}

impl From<Fraction> for f64 {
    fn from(f: Fraction) -> f64 {
        f.numerator as f64 / f.denominator as f64
    }
}