use std::io::{Cursor, Read};
use std::fmt;
use crate::ParserError;
const ASF_HEADER_OBJECT: [u8; 16] = [
0x30,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,
0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
];
const ASF_CONTENT_DESCRIPTION_OBJECT: [u8; 16] = [
0x33,0x26,0xB2,0x75,0x8E,0x66,0xCF,0x11,
0xA6,0xD9,0x00,0xAA,0x00,0x62,0xCE,0x6C
];
const ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT: [u8; 16] = [
0x40,0xA4,0xD0,0xD2,0x07,0xE3,0xD2,0x11,
0x97,0xF0,0x00,0xA0,0xC9,0x5E,0xA8,0x50
];
#[derive(Debug, Default)]
pub struct AsfMeta {
pub album_artist: Option<String>,
pub album_cover_url: Option<String>,
pub album_title: Option<String>,
pub aspect_ratio_x: Option<String>,
pub aspect_ratio_y: Option<String>,
pub audio_file_url: Option<String>,
pub audio_source_url: Option<String>,
pub author: Option<String>,
pub author_url: Option<String>,
pub banner_image_data: Option<Vec<u8>>,
pub banner_image_type: Option<String>,
pub banner_image_url: Option<String>,
pub beats_per_minute: Option<String>,
pub bitrate: Option<String>,
pub broadcast: Option<String>,
pub category: Option<String>,
pub codec: Option<String>,
pub composer: Option<String>,
pub copyright: Option<String>,
pub copyright_url: Option<String>,
pub description: Option<String>,
pub director: Option<String>,
pub duration: Option<String>,
pub encoded_by: Option<String>,
pub encoding_settings: Option<String>,
pub encoding_time: Option<String>,
pub file_size: Option<String>,
pub genre: Option<String>,
pub genre_id: Option<String>,
pub has_arbitrary_data_stream: Option<bool>,
pub has_attached_images: Option<bool>,
pub has_audio: Option<bool>,
pub has_file_transfer_stream: Option<bool>,
pub has_image: Option<bool>,
pub has_script: Option<bool>,
pub has_video: Option<bool>,
pub isrc: Option<String>,
pub is_protected: Option<bool>,
pub is_trusted: Option<bool>,
pub language: Option<String>,
pub lyrics: Option<String>,
pub mood: Option<String>,
pub number_of_frames: Option<String>,
pub optimal_bitrate: Option<String>,
pub original_album_title: Option<String>,
pub original_artist: Option<String>,
pub original_filename: Option<String>,
pub original_lyricist: Option<String>,
pub original_release_time: Option<String>,
pub original_release_year: Option<String>,
pub parental_rating: Option<String>,
pub parental_rating_reason: Option<String>,
pub producer: Option<String>,
pub promotion_url: Option<String>,
pub provider: Option<String>,
pub provider_copyright: Option<String>,
pub provider_rating: Option<String>,
pub publisher: Option<String>,
pub rating: Option<String>,
pub seekable: Option<bool>,
pub subtitle: Option<String>,
pub subtitle_description: Option<String>,
pub subtitle_content_id: Option<String>,
pub text: Option<String>,
pub title: Option<String>,
pub tool_name: Option<String>,
pub tool_version: Option<String>,
pub track: Option<String>,
pub track_number: Option<String>,
pub user_web_url: Option<String>,
pub video_frame_rate: Option<String>,
pub video_height: Option<String>,
pub video_width: Option<String>,
pub writer: Option<String>,
pub year: Option<String>,
}
impl fmt::Display for AsfMeta {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (k, v) in [
("Title", &self.title),
("Author", &self.author),
("Album Title", &self.album_title),
("Album Artist", &self.album_artist),
("Genre", &self.genre),
("Track Number", &self.track_number),
("Year", &self.year),
] {
if let Some(val) = v {
writeln!(f, "{}: {}", k, val)?;
}
}
Ok(())
}
}
impl AsfMeta {
pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
let mut cursor = Cursor::new(data);
let mut meta = AsfMeta::default();
let mut guid = [0u8; 16];
cursor.read_exact(&mut guid)?;
if guid != ASF_HEADER_OBJECT {
return Err(ParserError::new("Not ASF"));
}
let _header_size = read_u64(&mut cursor)?;
let object_count = read_u32(&mut cursor)?;
let _r1 = read_u8(&mut cursor)?;
let _r2 = read_u8(&mut cursor)?;
for _ in 0..object_count {
let mut obj_guid = [0u8; 16];
cursor.read_exact(&mut obj_guid)?;
let size = read_u64(&mut cursor)?;
let start = cursor.position();
if obj_guid == ASF_CONTENT_DESCRIPTION_OBJECT {
parse_content_description(&mut cursor, &mut meta)?;
} else if obj_guid == ASF_EXTENDED_CONTENT_DESCRIPTION_OBJECT {
parse_extended_description(&mut cursor, &mut meta)?;
}
cursor.set_position(start + (size - 24));
}
Ok(meta)
}
}
fn parse_content_description(cursor: &mut Cursor<&[u8]>, meta: &mut AsfMeta) -> Result<(), ParserError> {
let title_len = read_u16(cursor)? as usize;
let author_len = read_u16(cursor)? as usize;
let _ = read_u16(cursor)?;
let _ = read_u16(cursor)?;
let _ = read_u16(cursor)?;
if title_len > 0 {
meta.title = Some(read_utf16(cursor, title_len)?);
}
if author_len > 0 {
meta.author = Some(read_utf16(cursor, author_len)?);
}
Ok(())
}
fn parse_extended_description(cursor: &mut Cursor<&[u8]>, meta: &mut AsfMeta) -> Result<(), ParserError> {
let count = read_u16(cursor)?;
for _ in 0..count {
let name_len = read_u16(cursor)? as usize;
let raw_name = read_utf16(cursor, name_len)?;
let key = normalize_key(&raw_name);
let value_type = read_u16(cursor)?;
let value_len = read_u16(cursor)? as usize;
match value_type {
0 => {
let val = read_utf16(cursor, value_len)?;
set_string(meta, &key, val);
}
1 => {
let mut buf = vec![0u8; value_len];
cursor.read_exact(&mut buf)?;
set_binary(meta, &key, buf);
}
2 => {
let val = read_u16(cursor)? != 0;
set_bool(meta, &key, val);
}
3 => {
let val = read_u32(cursor)?.to_string();
set_string(meta, &key, val);
}
4 => {
let val = read_u64(cursor)?.to_string();
set_string(meta, &key, val);
}
_ => skip_bytes(cursor, value_len)?,
}
}
Ok(())
}
fn normalize_key(k: &str) -> String {
k.trim_start_matches("WM/").to_string()
}
fn set_string(m: &mut AsfMeta, k: &str, v: String) {
match k {
"AlbumArtist" => m.album_artist = Some(v),
_ => {}
}
}
fn set_bool(m: &mut AsfMeta, k: &str, v: bool) {
match k {
"HasArbitraryDataStream" => m.has_arbitrary_data_stream = Some(v),
_ => {}
}
}
fn set_binary(m: &mut AsfMeta, k: &str, v: Vec<u8>) {
if k == "BannerImageData" {
m.banner_image_data = Some(v);
}
}
fn read_u8(c: &mut Cursor<&[u8]>) -> Result<u8, ParserError> {
let mut b = [0;1]; c.read_exact(&mut b)?; Ok(b[0])
}
fn read_u16(c: &mut Cursor<&[u8]>) -> Result<u16, ParserError> {
let mut b = [0;2]; c.read_exact(&mut b)?; Ok(u16::from_le_bytes(b))
}
fn read_u32(c: &mut Cursor<&[u8]>) -> Result<u32, ParserError> {
let mut b = [0;4]; c.read_exact(&mut b)?; Ok(u32::from_le_bytes(b))
}
fn read_u64(c: &mut Cursor<&[u8]>) -> Result<u64, ParserError> {
let mut b = [0;8]; c.read_exact(&mut b)?; Ok(u64::from_le_bytes(b))
}
fn read_utf16(c: &mut Cursor<&[u8]>, len: usize) -> Result<String, ParserError> {
let mut buf = vec![0; len];
c.read_exact(&mut buf)?;
let mut out = Vec::new();
for ch in buf.chunks_exact(2) {
let v = u16::from_le_bytes([ch[0], ch[1]]);
if v == 0 { break; }
out.push(v);
}
Ok(String::from_utf16_lossy(&out))
}
fn skip_bytes(c: &mut Cursor<&[u8]>, len: usize) -> Result<(), ParserError> {
c.set_position(c.position() + len as u64);
Ok(())
}