use super::misc::mktag;
use crate::error::{Error, Result};
use crate::tag::Tag;
use crate::value::Value;
pub fn read_swf(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 8 {
return Err(Error::InvalidData("not a SWF file".into()));
}
let compressed = match data[0] {
b'F' => false,
b'C' => true, b'Z' => true, _ => return Err(Error::InvalidData("not a SWF file".into())),
};
if data[1] != b'W' || data[2] != b'S' {
return Err(Error::InvalidData("not a SWF file".into()));
}
let mut tags = Vec::new();
let version = data[3];
let _file_length = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
tags.push(mktag(
"SWF",
"FlashVersion",
"Flash Version",
Value::U8(version),
));
tags.push(mktag(
"SWF",
"Compressed",
"Compressed",
Value::String(if compressed { "True" } else { "False" }.into()),
));
if !compressed && data.len() > 8 {
parse_swf_body(&data[8..], &mut tags);
}
Ok(tags)
}
fn parse_swf_body(body: &[u8], tags: &mut Vec<Tag>) {
if body.is_empty() {
return;
}
let n_bits = (body[0] >> 3) as usize;
let total_bits = 5 + n_bits * 4;
let n_bytes = (total_bits + 7) / 8;
if body.len() < n_bytes + 4 {
return;
}
let mut bit_str = 0u64;
let bytes_to_read = n_bytes.min(8);
for item in body.iter().take(bytes_to_read) {
bit_str = (bit_str << 8) | *item as u64;
}
let total_64 = bytes_to_read * 8;
let shift = total_64.saturating_sub(total_bits);
bit_str >>= shift;
let mask = if n_bits >= 64 {
u64::MAX
} else {
(1u64 << n_bits) - 1
};
let ymax_raw = (bit_str & mask) as i32;
let ymin_raw = ((bit_str >> n_bits) & mask) as i32;
let xmax_raw = ((bit_str >> (n_bits * 2)) & mask) as i32;
let xmin_raw = ((bit_str >> (n_bits * 3)) & mask) as i32;
let sign_extend = |v: i32, bits: usize| -> i32 {
if bits > 0 && bits < 32 && (v & (1 << (bits - 1))) != 0 {
v | (!0i32 << bits)
} else {
v
}
};
let xmin = sign_extend(xmin_raw, n_bits);
let xmax = sign_extend(xmax_raw, n_bits);
let ymin = sign_extend(ymin_raw, n_bits);
let ymax = sign_extend(ymax_raw, n_bits);
let width = ((xmax - xmin) as f64) / 20.0;
let height = ((ymax - ymin) as f64) / 20.0;
if width >= 0.0 && height >= 0.0 {
tags.push(mktag("SWF", "ImageWidth", "Image Width", Value::F64(width)));
tags.push(mktag(
"SWF",
"ImageHeight",
"Image Height",
Value::F64(height),
));
}
let fr_offset = n_bytes;
if fr_offset + 4 > body.len() {
return;
}
let frame_rate_raw = u16::from_le_bytes([body[fr_offset], body[fr_offset + 1]]);
let frame_count = u16::from_le_bytes([body[fr_offset + 2], body[fr_offset + 3]]);
let frame_rate = frame_rate_raw as f64 / 256.0;
tags.push(mktag(
"SWF",
"FrameRate",
"Frame Rate",
Value::F64(frame_rate),
));
tags.push(mktag(
"SWF",
"FrameCount",
"Frame Count",
Value::U16(frame_count),
));
if frame_rate > 0.0 && frame_count > 0 {
let duration = frame_count as f64 / frame_rate;
tags.push(mktag(
"SWF",
"Duration",
"Duration",
Value::String(format!("{:.2} s", duration)),
));
}
let mut tag_pos = fr_offset + 4;
let mut found_attributes = false;
while tag_pos + 2 <= body.len() {
let code = u16::from_le_bytes([body[tag_pos], body[tag_pos + 1]]);
let tag_type = code >> 6;
let short_len = (code & 0x3F) as usize;
tag_pos += 2;
let tag_len = if short_len == 0x3F {
if tag_pos + 4 > body.len() {
break;
}
let l = u32::from_le_bytes([
body[tag_pos],
body[tag_pos + 1],
body[tag_pos + 2],
body[tag_pos + 3],
]) as usize;
tag_pos += 4;
l
} else {
short_len
};
if tag_pos + tag_len > body.len() {
break;
}
match tag_type {
69 => {
if tag_len >= 1 {
let flags = body[tag_pos];
found_attributes = true;
if flags & 0x10 == 0 {
break;
} }
}
77 => {
let xmp_data = &body[tag_pos..tag_pos + tag_len];
if let Ok(xmp_tags) = crate::metadata::XmpReader::read(xmp_data) {
for t in xmp_tags {
if !tags.iter().any(|e| e.name == t.name) {
tags.push(t);
}
}
}
tags.push(mktag(
"SWF",
"XMPToolkit",
"XMP Toolkit",
Value::String(extract_xmp_toolkit(xmp_data)),
));
break;
}
_ => {}
}
tag_pos += tag_len;
}
let _ = found_attributes;
}
fn extract_xmp_toolkit(xmp: &[u8]) -> String {
let text = crate::encoding::decode_utf8_or_latin1(xmp);
if let Some(start) = text.find("xmptk=\"") {
let after = &text[start + 7..];
if let Some(end) = after.find('"') {
return after[..end].to_string();
}
}
if let Some(start) = text.find("<xmp:CreatorTool>") {
let after = &text[start + 17..];
if let Some(end) = after.find("</") {
return after[..end].to_string();
}
}
String::new()
}