1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::io::BufRead;

use types::{Result, Dimensions};
use common::riff::{RiffReader, RiffChunk, ChunkId};
use traits::LoadableMetadata;

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Metadata {
    VP8(VP8Metadata),
    VP8L(VP8LMetadata),
    VP8X(VP8XMetadata)
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct VP8Metadata {
    pub version_number: u8,
    pub show_frame: bool,
    pub first_partition_len: u32,
    pub frame: VP8Frame
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub enum VP8Frame {
    Key { dimensions: Dimensions, x_scale: u8, y_scale: u8 },
    Inter
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct VP8LMetadata;

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct VP8XMetadata;

const WEBP_CHUNK_TYPE: ChunkId = ChunkId([b'W', b'E', b'B', b'P']);
const ALPH_CHUNK_ID: ChunkId   = ChunkId([b'A', b'L', b'P', b'H']);
const VP8_CHUNK_ID: ChunkId    = ChunkId([b'V', b'P', b'8', b' ']);
const VP8L_CHUNK_ID: ChunkId   = ChunkId([b'V', b'P', b'8', b'L']);
const VP8X_CHUNK_ID: ChunkId   = ChunkId([b'V', b'P', b'8', b'X']);

impl Metadata {
    pub fn dimensions(&self) -> Dimensions {
        match *self {
            Metadata::VP8(VP8Metadata { frame: VP8Frame::Key { dimensions, .. }, .. }) => dimensions,
            _ => unimplemented!()
        }
    }
}

impl LoadableMetadata for Metadata {
    fn load<R: ?Sized + BufRead>(r: &mut R) -> Result<Metadata> {
        let mut rr = RiffReader::new(r);

        let mut root = try!(rr.root());
        if root.chunk_type() != WEBP_CHUNK_TYPE {
            return Err(invalid_format!("invalid WEBP signature"));
        }

        loop {
            let mut chunk = match root.next() {
                Some(c) => try!(c),
                None => return Err(unexpected_eof!("when reading first WEBP chunk"))
            };

            match chunk.chunk_id() {
                VP8_CHUNK_ID => return read_vp8_chunk(&mut chunk).map(Metadata::VP8),
                VP8L_CHUNK_ID => return Err(invalid_format!("unsupported (yet) VP8 chunk id")),
                VP8X_CHUNK_ID => return Err(invalid_format!("unsupported (yet) VP8 chunk id")),
                ALPH_CHUNK_ID => return Err(invalid_format!("unsupported (yet) VP8 chunk id")),
                cid => return Err(invalid_format!("invalid WEBP chunk id: {}", cid))
            }
        }
    }
}

fn read_vp8_chunk(chunk: &mut RiffChunk) -> Result<VP8Metadata> {
    let r = chunk.contents();

    let mut hdr = [0u8; 3];
    try!(r.read_exact(&mut hdr).map_err(if_eof!(std, "when reading VP8 frame header")));

    let mut result = VP8Metadata {
        version_number: 0,
        show_frame: false,
        first_partition_len: 0,
        frame: VP8Frame::Inter
    };

    // bits of first three bytes:
    //    xxxsvvvf xxxxxxxx xxxxxxxx
    // where
    //    f  --  frame type, 0 is key frame, 1 is interframe
    //    v  --  version number
    //    s  --  show frame flag, 1 is display, 0 is don't display
    //    x  --  size of first data partition in bytes

    let key_frame = hdr[0] & 1 == 0;
    result.version_number = (hdr[0] >> 1) & 7;
    result.show_frame = (hdr[0] >> 4) & 1 == 1;
    result.first_partition_len = ((hdr[0] >> 5) as u32) | 
                                 ((hdr[1] as u32) << 3) | 
                                 ((hdr[2] as u32) << 11);

    if key_frame {
        let mut hdr = [0u8; 7];
        try!(r.read_exact(&mut hdr).map_err(if_eof!(std, "when reading VP8 key frame header")));

        // check magic value
        if &hdr[..3] != &[0x9d, 0x01, 0x2a] {
            return Err(invalid_format!("VP8 key frame magic code is invalid: {:?}", &hdr[..3]));
        }

        // bits of next four bytes:
        //    wwwwwwww xxwwwwww hhhhhhhh yyhhhhhh
        // where
        //    x  --  horizontal scale
        //    w  --  width
        //    y  --  vertical scale
        //    h  --  height

        let width  = ((hdr[4] & 0x3f) as u32) << 8 | hdr[3] as u32;
        let height = ((hdr[6] & 0x3f) as u32) << 8 | hdr[5] as u32;
        let x_scale = hdr[4] >> 6;
        let y_scale = hdr[6] >> 6;

        result.frame = VP8Frame::Key {
            dimensions: (width, height).into(),
            x_scale: x_scale,
            y_scale: y_scale
        };
    }

    Ok(result)
}