use std::borrow::Cow;
use log::{info, trace, warn};
use thiserror::Error;
use super::{
common_header_validation, SetupHeaderParse, VorbisCommentFieldsAction, VorbisOptimizerError,
VorbisOptimizerSettings, VorbisVendorStringAction
};
use crate::{vorbis::PacketType, OPTIVORBIS_SHORT_VERSION_TAG, OPTIVORBIS_VERSION_TAG};
pub(super) struct CommentHeaderParse;
#[derive(Default)]
pub(super) struct VorbisCommentData {
pub(super) vendor_string: Option<Vec<u8>>,
pub(super) user_comments: Vec<Vec<u8>> }
#[derive(Debug, Error)]
enum CommentReadError {
#[error("{0}")]
OptimizerError(VorbisOptimizerError),
#[error("End of packet while reading comment header packet")]
EndOfPacket
}
impl<T: Into<VorbisOptimizerError>> From<T> for CommentReadError {
fn from(into_err: T) -> Self {
Self::OptimizerError(into_err.into())
}
}
impl CommentHeaderParse {
pub(super) fn analyze_packet(
&mut self,
packet: &[u8],
settings: &VorbisOptimizerSettings
) -> Result<(Option<u16>, Option<SetupHeaderParse>), VorbisOptimizerError> {
trace!("Decoding comment header Vorbis packet");
let comment_header = match common_header_validation(packet, PacketType::CommentHeader) {
Err(
VorbisOptimizerError::TooSmallPacket(_)
| VorbisOptimizerError::UnexpectedHeaderPacketLength { .. }
) => {
&[]
}
Ok(comment_header) => comment_header,
Err(error) => {
return Err(error);
}
};
let mut user_comments = vec![];
let mut vendor_string = None;
match parse(
comment_header,
settings,
&mut vendor_string,
&mut user_comments
) {
Err(CommentReadError::OptimizerError(err)) => return Err(err),
Err(CommentReadError::EndOfPacket) => {
warn!(
"End of Vorbis packet while decoding the comment header. \
The comment header is likely corrupt, but optimization can continue"
)
}
_ => ()
}
Ok((
None,
Some(SetupHeaderParse {
comment_data: VorbisCommentData {
vendor_string,
user_comments
}
})
))
}
}
fn parse(
comment_header: &[u8],
settings: &VorbisOptimizerSettings,
vendor_string: &mut Option<Vec<u8>>,
user_comments: &mut Vec<Vec<u8>>
) -> Result<(), CommentReadError> {
macro_rules! get_packet_checked {
($index:expr) => {
comment_header
.get($index)
.ok_or(CommentReadError::EndOfPacket)?
};
}
let vendor_string_length = usize::try_from(u32::from_le_bytes(
get_packet_checked!(..4).try_into().unwrap()
))?;
let raw_vendor_string = get_packet_checked!(4..4 + vendor_string_length);
info!(
"Encoder vendor string: {}",
String::from_utf8_lossy(raw_vendor_string)
);
*vendor_string = Some(match settings.vendor_string_action {
VorbisVendorStringAction::Copy => raw_vendor_string.into(),
VorbisVendorStringAction::Replace => OPTIVORBIS_VERSION_TAG.into(),
VorbisVendorStringAction::AppendTag => {
append_tag_if_needed(raw_vendor_string, OPTIVORBIS_VERSION_TAG)
}
VorbisVendorStringAction::AppendShortTag => {
append_tag_if_needed(raw_vendor_string, OPTIVORBIS_SHORT_VERSION_TAG)
}
VorbisVendorStringAction::Empty => "".into()
});
let mut user_comment_count = u32::from_le_bytes(
get_packet_checked!(4 + vendor_string_length..4 + vendor_string_length + 4)
.try_into()
.unwrap()
);
info!("User comment count: {}", user_comment_count);
if settings.comment_fields_action == VorbisCommentFieldsAction::Copy {
trace!("Copying user comments");
let mut user_comment_length_start_index = 4 + vendor_string_length + 4;
while user_comment_count > 0 {
let user_comment_length_end_index = user_comment_length_start_index + 4;
let user_comment_length = usize::try_from(u32::from_le_bytes(
get_packet_checked!(user_comment_length_start_index..user_comment_length_end_index)
.try_into()
.unwrap()
))?;
let user_comment_end_index = user_comment_length_end_index + user_comment_length;
let user_comment =
get_packet_checked!(user_comment_length_end_index..user_comment_end_index);
info!("User comment: {}", String::from_utf8_lossy(user_comment));
user_comments.push(user_comment.into());
user_comment_length_start_index = user_comment_end_index;
user_comment_count -= 1;
}
} else {
trace!("Skipping user comments");
}
Ok(())
}
fn append_tag_if_needed<'str, S: Into<Cow<'str, [u8]>>>(vendor_string: S, tag: &str) -> Vec<u8> {
let mut vendor_string = vendor_string.into().into_owned();
if !ends_with_optivorbis_version_tag_and_separator(&vendor_string) {
vendor_string.extend_from_slice(b"; ");
vendor_string.extend_from_slice(tag.as_bytes());
}
vendor_string
}
fn ends_with_optivorbis_version_tag_and_separator<S: AsRef<[u8]>>(vendor_string: S) -> bool {
let vendor_string = vendor_string.as_ref();
vendor_string
.strip_suffix(OPTIVORBIS_SHORT_VERSION_TAG.as_bytes())
.map(|vendor_string_without_tag| vendor_string_without_tag.ends_with(b"; "))
.or_else(|| {
vendor_string
.strip_suffix(OPTIVORBIS_VERSION_TAG.as_bytes())
.map(|vendor_string_without_tag| vendor_string_without_tag.ends_with(b"; "))
})
.unwrap_or(false)
}