diff --git a/src/read.rs b/src/read.rs index be42735f1..7e375eda7 100644 --- a/src/read.rs +++ b/src/read.rs @@ -508,6 +508,7 @@ impl Reader { Some(TagCode::DefineShape4) => tag_reader.read_define_shape(4)?, Some(TagCode::DefineSound) => tag_reader.read_define_sound()?, Some(TagCode::DefineText) => tag_reader.read_define_text()?, + Some(TagCode::DefineVideoStream) => tag_reader.read_define_video_stream()?, Some(TagCode::EnableTelemetry) => { tag_reader.read_u16()?; // Reserved let password_hash = if length > 2 { @@ -734,6 +735,8 @@ impl Reader { } }, + Some(TagCode::VideoFrame) => tag_reader.read_video_frame()?, + _ => { let size = length as usize; let mut data = Vec::with_capacity(size); @@ -743,7 +746,7 @@ impl Reader { tag_code: tag_code, data: data, } - } + }, }; if cfg!(debug_assertions) { @@ -2269,6 +2272,51 @@ impl Reader { is_device_font: flags2 & 0b1 == 0, }))) } + + fn read_define_video_stream(&mut self) -> Result { + let id = self.read_character_id()?; + let num_frames = self.read_u16()?; + let width = self.read_u16()?; + let height = self.read_u16()?; + let flags = self.read_u8()?; + // TODO(Herschel): Check SWF version. + let codec = match self.read_u8()? { + 2 => VideoCodec::H263, + 3 => VideoCodec::ScreenVideo, + 4 => VideoCodec::VP6, + 5 => VideoCodec::VP6WithAlpha, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid video codec.")), + }; + Ok(Tag::DefineVideoStream(DefineVideoStream { + id: id, + num_frames: num_frames, + width: width, + height: height, + is_smoothed: flags & 0b1 != 0, + codec: codec, + deblocking: match flags & 0b100_0 { + 0b000_0 => VideoDeblocking::UseVideoPacketValue, + 0b001_0 => VideoDeblocking::None, + 0b010_0 => VideoDeblocking::Level1, + 0b011_0 => VideoDeblocking::Level2, + 0b100_0 => VideoDeblocking::Level3, + 0b101_0 => VideoDeblocking::Level4, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid video deblocking value.")), + }, + })) + } + + fn read_video_frame(&mut self) -> Result { + let stream_id = self.read_character_id()?; + let frame_num = self.read_u16()?; + let mut data = vec![]; + self.input.read_to_end(&mut data)?; + Ok(Tag::VideoFrame(VideoFrame { + stream_id: stream_id, + frame_num: frame_num, + data: data, + })) + } } #[cfg(test)] diff --git a/src/test_data.rs b/src/test_data.rs index ec90951fa..cc80906b5 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -1058,6 +1058,20 @@ pub fn tag_tests() -> Vec { vec![ read_tag_bytes_from_file("tests/swfs/DefineFont-MX.swf", TagCode::DefineText) ), + ( + 6, + Tag::DefineVideoStream(DefineVideoStream { + id: 1, + num_frames: 4, + width: 8, + height: 8, + deblocking: VideoDeblocking::UseVideoPacketValue, + is_smoothed: false, + codec: VideoCodec::H263, + }), + read_tag_bytes_from_file("tests/swfs/DefineVideoStream-CC.swf", TagCode::DefineVideoStream) + ), + ( 5, Tag::DoAction( @@ -1437,6 +1451,16 @@ pub fn tag_tests() -> Vec { vec![ read_tag_bytes_from_file("tests/swfs/startsound2.swf", TagCode::StartSound2) ), + ( + 6, + Tag::VideoFrame(VideoFrame { + stream_id: 1, + frame_num: 0, + data: vec![0, 0, 132, 0, 4, 4, 17, 38, 190, 190, 190, 190, 201, 182], + }), + read_tag_bytes_from_file("tests/swfs/DefineVideoStream-CC.swf", TagCode::VideoFrame) + ), + (1, Tag::Unknown { tag_code: 512, data: vec![] }, vec![0b00_000000, 0b10000000]), (1, Tag::Unknown { tag_code: 513, data: vec![1, 2] }, vec![0b01_000010, 0b10000000, 1, 2]), ( diff --git a/src/types.rs b/src/types.rs index ed2108ef8..ebb76cfe7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -324,6 +324,7 @@ pub enum Tag { DefineSound(Box), DefineSprite(Sprite), DefineText(Box), + DefineVideoStream(DefineVideoStream), DoAbc(Vec), DoAction(Vec), DoInitAction { id: CharacterId, action_data: Vec }, @@ -342,7 +343,7 @@ pub enum Tag { SymbolClass(Vec), PlaceObject(Box), RemoveObject { depth: Depth, character_id: Option }, - + VideoFrame(VideoFrame), FileAttributes(FileAttributes), FrameLabel { label: String, is_anchor: bool }, @@ -825,3 +826,39 @@ pub enum BitmapFormat { Rgb15, Rgb24, } + +#[derive(Clone, Debug, PartialEq)] +pub struct DefineVideoStream { + pub id: CharacterId, + pub num_frames: u16, + pub width: u16, + pub height: u16, + pub is_smoothed: bool, + pub deblocking: VideoDeblocking, + pub codec: VideoCodec, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VideoDeblocking { + UseVideoPacketValue, + None, + Level1, + Level2, + Level3, + Level4, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum VideoCodec { + H263, + ScreenVideo, + VP6, + VP6WithAlpha, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct VideoFrame { + pub stream_id: CharacterId, + pub frame_num: u16, + pub data: Vec, +} diff --git a/src/write.rs b/src/write.rs index ea61d800e..6ec790d82 100644 --- a/src/write.rs +++ b/src/write.rs @@ -646,6 +646,7 @@ impl Writer { &Tag::DefineSound(ref sound) => self.write_define_sound(sound)?, &Tag::DefineSprite(ref sprite) => self.write_define_sprite(sprite)?, &Tag::DefineText(ref text) => self.write_define_text(text)?, + &Tag::DefineVideoStream(ref video) => self.write_define_video_stream(video)?, &Tag::DoAbc(ref action_data) => { self.write_tag_header(TagCode::DoAbc, action_data.len() as u32)?; self.output.write_all(action_data)?; @@ -801,6 +802,13 @@ impl Writer { } }, + &Tag::VideoFrame(ref frame) => { + self.write_tag_header(TagCode::VideoFrame, 4 + frame.data.len() as u32)?; + self.write_character_id(frame.stream_id)?; + self.write_u16(frame.frame_num)?; + self.output.write_all(&frame.data)?; + }, + &Tag::FileAttributes(ref attributes) => { self.write_tag_header(TagCode::FileAttributes, 4)?; let mut flags = 0u32; @@ -2146,6 +2154,32 @@ impl Writer { Ok(()) } + fn write_define_video_stream(&mut self, video: &DefineVideoStream) -> Result<()> { + self.write_tag_header(TagCode::DefineVideoStream, 10)?; + self.write_character_id(video.id)?; + self.write_u16(video.num_frames)?; + self.write_u16(video.width)?; + self.write_u16(video.height)?; + self.write_u8( + match video.deblocking { + VideoDeblocking::UseVideoPacketValue => 0b000_0, + VideoDeblocking::None => 0b001_0, + VideoDeblocking::Level1 => 0b010_0, + VideoDeblocking::Level2 => 0b011_0, + VideoDeblocking::Level3 => 0b100_0, + VideoDeblocking::Level4 => 0b101_0, + } | + if video.is_smoothed { 0b1 } else { 0 } + )?; + self.write_u8(match video.codec { + VideoCodec::H263 => 2, + VideoCodec::ScreenVideo => 3, + VideoCodec::VP6 => 4, + VideoCodec::VP6WithAlpha => 5, + })?; + Ok(()) + } + fn write_tag_header(&mut self, tag_code: TagCode, length: u32) -> Result<()> { self.write_tag_code_and_length(tag_code as u16, length) } diff --git a/tests/swfs/DefineVideoStream-CC.fla b/tests/swfs/DefineVideoStream-CC.fla new file mode 100644 index 000000000..66a529569 Binary files /dev/null and b/tests/swfs/DefineVideoStream-CC.fla differ diff --git a/tests/swfs/DefineVideoStream-CC.swf b/tests/swfs/DefineVideoStream-CC.swf new file mode 100644 index 000000000..04bbec8e2 Binary files /dev/null and b/tests/swfs/DefineVideoStream-CC.swf differ diff --git a/tests/swfs/dummy.flv b/tests/swfs/dummy.flv new file mode 100644 index 000000000..dcf2d8efb Binary files /dev/null and b/tests/swfs/dummy.flv differ