use crate::VERSION_STRING;
use crate::ogg::OggPage;
use crate::vorbis::VCommentDict;
use crate::{AudexError, FileType, Result, StreamInfo};
use byteorder::{BigEndian, ReadBytesExt};
use std::fs::File;
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::time::Duration;
#[cfg(feature = "async")]
use tokio::fs::{File as TokioFile, OpenOptions as TokioOpenOptions};
#[cfg(feature = "async")]
use tokio::io::{AsyncSeekExt, BufReader as TokioBufReader};
pub use OggFlac as OGGFLAC;
#[derive(Debug, Clone, PartialEq)]
pub struct OggError(pub String);
impl std::fmt::Display for OggError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for OggError {}
#[derive(Debug, Clone, PartialEq)]
pub struct OggFLACHeaderError(pub OggError);
impl std::fmt::Display for OggFLACHeaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "OggFLAC header error: {}", self.0)
}
}
impl std::error::Error for OggFLACHeaderError {}
impl From<OggError> for OggFLACHeaderError {
fn from(err: OggError) -> Self {
OggFLACHeaderError(err)
}
}
impl From<OggFLACHeaderError> for AudexError {
fn from(err: OggFLACHeaderError) -> Self {
AudexError::InvalidData(err.to_string())
}
}
impl From<OggError> for AudexError {
fn from(err: OggError) -> Self {
AudexError::InvalidData(err.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct OggFLACStreamInfo {
pub min_blocksize: u16,
pub max_blocksize: u16,
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub total_samples: u64,
pub length: Option<Duration>,
pub serial_number: u32,
}
impl OggFLACStreamInfo {
pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Result<Self> {
let mut stream_info = Self::default();
const MAX_PAGE_SEARCH: usize = 1024;
let mut pages_read: usize = 0;
loop {
pages_read += 1;
if pages_read > MAX_PAGE_SEARCH {
return Err(OggFLACHeaderError(OggError(
"No FLAC stream found within page limit".to_string(),
))
.into());
}
let page = match OggPage::from_reader(reader) {
Ok(page) => page,
Err(_) => {
return Err(
OggFLACHeaderError(OggError("No FLAC stream found".to_string())).into(),
);
}
};
if let Some(first_packet) = page.packets.first() {
if first_packet.len() >= 5 && &first_packet[0..5] == b"\x7FFLAC" {
stream_info.serial_number = page.serial;
stream_info.parse_header(first_packet)?;
stream_info.post_tags(reader)?;
return Ok(stream_info);
}
}
}
}
fn parse_header(&mut self, packet: &[u8]) -> Result<()> {
if packet.len() < 13 {
return Err(
OggFLACHeaderError(OggError("Invalid FLAC header length".to_string())).into(),
);
}
let mut cursor = Cursor::new(&packet[5..]);
let major = cursor.read_u8()?;
let minor = cursor.read_u8()?;
let _packets = cursor.read_u16::<BigEndian>()?;
let mut flac_marker = [0u8; 4];
cursor.read_exact(&mut flac_marker)?;
if (major, minor) != (1, 0) {
return Err(OggFLACHeaderError(OggError(format!(
"unknown mapping version: {}.{}",
major, minor
)))
.into());
}
if &flac_marker != b"fLaC" {
return Err(OggFLACHeaderError(OggError("Invalid FLAC marker".to_string())).into());
}
if packet.len() < 17 {
return Err(OggFLACHeaderError(OggError(
"Invalid FLAC stream info length".to_string(),
))
.into());
}
let stream_data = &packet[17..];
self.parse_flac_stream_info(stream_data)?;
Ok(())
}
fn parse_flac_stream_info(&mut self, data: &[u8]) -> Result<()> {
if data.len() < 34 {
return Err(OggFLACHeaderError(OggError(format!(
"STREAMINFO block too short: got {} bytes, need 34",
data.len()
)))
.into());
}
let streaminfo_data = &data[0..34];
let mut cursor = Cursor::new(streaminfo_data);
self.min_blocksize = cursor.read_u16::<BigEndian>()?;
self.max_blocksize = cursor.read_u16::<BigEndian>()?;
std::io::Seek::seek(&mut cursor, SeekFrom::Current(6))?;
let combined = cursor.read_u64::<BigEndian>()?;
self.sample_rate = ((combined >> 44) & 0xFFFFF) as u32; self.channels = (((combined >> 41) & 0x07) as u16) + 1; self.bits_per_sample = (((combined >> 36) & 0x1F) as u16) + 1; self.total_samples = combined & 0xFFFFFFFFF;
if self.sample_rate == 0 {
return Err(OggFLACHeaderError(OggError(
"Invalid sample rate: 0 is not a valid playable rate".to_string(),
))
.into());
}
Ok(())
}
fn post_tags<R: Read + Seek>(&mut self, reader: &mut R) -> Result<()> {
if self.length.is_some() {
return Ok(());
}
if let Some(last_page) =
OggPage::find_last_with_finishing(reader, self.serial_number, true)?
{
if self.sample_rate > 0 && last_page.position >= 0 {
let duration_secs = last_page.position as f64 / self.sample_rate as f64;
if duration_secs.is_finite()
&& duration_secs >= 0.0
&& duration_secs <= u64::MAX as f64
{
self.length = Some(Duration::from_secs_f64(duration_secs));
}
}
}
Ok(())
}
pub fn pprint(&self) -> String {
let duration = self.length.map(|d| d.as_secs_f64()).unwrap_or(0.0);
format!("Ogg FLAC, {:.2} seconds, {} Hz", duration, self.sample_rate)
}
}
impl StreamInfo for OggFLACStreamInfo {
fn length(&self) -> Option<Duration> {
self.length
}
fn bitrate(&self) -> Option<u32> {
None
}
fn sample_rate(&self) -> Option<u32> {
if self.sample_rate > 0 {
Some(self.sample_rate)
} else {
None
}
}
fn channels(&self) -> Option<u16> {
if self.channels > 0 {
Some(self.channels)
} else {
None
}
}
fn bits_per_sample(&self) -> Option<u16> {
if self.bits_per_sample > 0 {
Some(self.bits_per_sample)
} else {
None
}
}
}
#[derive(Debug, Default)]
pub struct OggFLACVComment {
pub inner: VCommentDict,
pub serial_number: u32,
}
impl std::ops::Deref for OggFLACVComment {
type Target = VCommentDict;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for OggFLACVComment {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl crate::Tags for OggFLACVComment {
fn get(&self, key: &str) -> Option<&[String]> {
self.inner.get(key)
}
fn set(&mut self, key: &str, values: Vec<String>) {
self.inner.set(key, values)
}
fn remove(&mut self, key: &str) {
self.inner.remove(key);
}
fn keys(&self) -> Vec<String> {
self.inner.keys()
}
fn pprint(&self) -> String {
format!("OggFLACVComment({})", self.inner.keys().len())
}
fn module_name(&self) -> &'static str {
"oggflac"
}
}
impl OggFLACVComment {
pub fn from_reader<R: Read + Seek>(reader: &mut R, serial_number: u32) -> Result<Self> {
let mut tags = OggFLACVComment {
inner: VCommentDict::new(),
serial_number,
};
let mut pages = Vec::new();
let mut found_comment = false;
let mut pages_read: usize = 0;
let mut cumulative_bytes = 0u64;
let limits = crate::limits::ParseLimits::default();
while let Ok(page) = OggPage::from_reader(reader) {
pages_read += 1;
if pages_read > Self::MAX_PAGE_SEARCH {
break;
}
if page.serial == serial_number {
if let Some(first_packet) = page.packets.first() {
if first_packet.len() >= 4 {
let block_type = first_packet[0] & 0x7F;
if block_type == 4 {
OggPage::accumulate_page_bytes_with_limit(
limits,
&mut cumulative_bytes,
&page,
"Ogg FLAC comment packet",
)?;
pages.push(page);
found_comment = true;
} else if found_comment {
let is_complete = pages
.last()
.ok_or_else(|| {
AudexError::InvalidData(
"expected non-empty page list after comment block".into(),
)
})?
.is_complete();
if !is_complete {
OggPage::accumulate_page_bytes_with_limit(
limits,
&mut cumulative_bytes,
&page,
"Ogg FLAC comment packet",
)?;
pages.push(page);
} else {
break;
}
}
}
} else if found_comment {
let is_complete = pages
.last()
.ok_or_else(|| {
AudexError::InvalidData(
"expected non-empty page list after comment block".into(),
)
})?
.is_complete();
if !is_complete {
OggPage::accumulate_page_bytes_with_limit(
limits,
&mut cumulative_bytes,
&page,
"Ogg FLAC comment packet",
)?;
pages.push(page);
}
}
}
}
if pages.is_empty() {
return Ok(tags);
}
let packets = OggPage::to_packets(&pages, false)?;
if packets.is_empty() || packets[0].len() < 4 {
return Ok(tags);
}
let comment_data = &packets[0][4..];
let mut cursor = Cursor::new(comment_data);
let _ = tags
.inner
.load(&mut cursor, crate::vorbis::ErrorMode::Replace, false);
Ok(tags)
}
const MAX_PAGE_SEARCH: usize = 1024;
pub fn inject<R: Read + Write + Seek + 'static>(
&self,
fileobj: &mut R,
_padding_func: Option<fn(&crate::tags::PaddingInfo) -> i64>,
) -> Result<()> {
use crate::ogg::OggPage;
fileobj.seek(SeekFrom::Start(0))?;
let mut page = OggPage::from_reader(fileobj)?;
let mut pages_read = 1usize;
while page.packets.is_empty() || !page.packets[0].starts_with(b"\x7FFLAC") {
page = OggPage::from_reader(fileobj)?;
pages_read += 1;
if pages_read > Self::MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while searching for FLAC header".to_string(),
));
}
}
let first_page = page;
let expected_seq = first_page.sequence + 1;
let mut page = OggPage::from_reader(fileobj)?;
pages_read += 1;
while !(page.sequence == expected_seq && page.serial == first_page.serial) {
page = OggPage::from_reader(fileobj)?;
pages_read += 1;
if pages_read > Self::MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while searching for comment block".to_string(),
));
}
}
let mut old_pages = vec![page];
loop {
let last_page = old_pages.last().ok_or_else(|| {
AudexError::InvalidData(
"expected non-empty page list while reading comment pages".into(),
)
})?;
if last_page.is_complete() || last_page.packets.len() > 1 {
break;
}
let page = OggPage::from_reader(fileobj)?;
pages_read += 1;
if pages_read > Self::MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while reading comment pages".to_string(),
));
}
if page.serial == first_page.serial {
old_pages.push(page);
}
}
let packets = OggPage::to_packets(&old_pages, false)?;
if packets.is_empty() {
return Err(AudexError::InvalidData("No packets found".to_string()));
}
let mut comment_to_write = self.inner.clone();
if !comment_to_write.keys().is_empty() {
comment_to_write.set_vendor(format!("Audex {}", VERSION_STRING));
}
let mut comment_data = Vec::new();
comment_to_write.write(&mut comment_data, Some(false))?;
if comment_data.len() > 0xFF_FFFF {
return Err(AudexError::InvalidData(format!(
"OGG FLAC comment data size ({}) exceeds 24-bit maximum ({})",
comment_data.len(),
0xFF_FFFF
)));
}
let header_byte = packets[0][0];
let block_size = comment_data.len() as u32;
let mut new_comment_packet = Vec::with_capacity(4 + comment_data.len());
new_comment_packet.push(header_byte);
new_comment_packet.extend_from_slice(&[
((block_size >> 16) & 0xFF) as u8,
((block_size >> 8) & 0xFF) as u8,
(block_size & 0xFF) as u8,
]);
new_comment_packet.extend_from_slice(&comment_data);
let mut new_packets = packets;
new_packets[0] = new_comment_packet;
let new_pages = OggPage::from_packets(new_packets, old_pages[0].sequence, 4096, 2048);
if new_pages.is_empty() {
return Err(AudexError::InvalidData(
"Failed to create new OGG pages".to_string(),
));
}
OggPage::replace(fileobj, &old_pages, new_pages)?;
Ok(())
}
}
#[derive(Debug)]
pub struct OggFlac {
pub info: OggFLACStreamInfo,
pub tags: Option<OggFLACVComment>,
filename: String,
}
impl OggFlac {
pub fn new() -> Self {
Self {
info: OggFLACStreamInfo::default(),
tags: None,
filename: String::new(),
}
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
debug_event!("parsing OGG FLAC file");
let file_path = path.as_ref();
let mut file = BufReader::new(File::open(file_path)?);
let mut oggflac = Self::new();
oggflac.filename = file_path.to_string_lossy().to_string();
oggflac.info = OggFLACStreamInfo::from_reader(&mut file)?;
file.seek(SeekFrom::Start(0))?;
oggflac.tags = Some(OggFLACVComment::from_reader(
&mut file,
oggflac.info.serial_number,
)?);
if let Some(ref _tags) = oggflac.tags {
debug_event!(tag_count = _tags.keys().len(), "OGG FLAC tags loaded");
}
Ok(oggflac)
}
pub fn save_with_options<P>(
&mut self,
path: Option<P>,
padding_func: Option<fn(&crate::tags::PaddingInfo) -> i64>,
) -> Result<()>
where
P: AsRef<Path>,
{
use std::fs::OpenOptions;
let (file_path, is_new_path) = match &path {
Some(p) => (p.as_ref().to_string_lossy().to_string(), true),
None => (self.filename.clone(), false),
};
if file_path.is_empty() {
return Err(AudexError::InvalidData(
"No filename available for saving".to_string(),
));
}
let tags = self
.tags
.as_ref()
.ok_or_else(|| AudexError::InvalidData("No tags available for saving".to_string()))?;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(Path::new(&file_path))?;
tags.inject(&mut file, padding_func)?;
if is_new_path {
self.filename = file_path;
}
Ok(())
}
}
impl Default for OggFlac {
fn default() -> Self {
Self::new()
}
}
impl FileType for OggFlac {
type Tags = OggFLACVComment;
type Info = OggFLACStreamInfo;
fn format_id() -> &'static str {
"OggFlac"
}
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::from_file(path)
}
fn load_from_reader(reader: &mut dyn crate::ReadSeek) -> Result<Self> {
let mut reader = reader;
let mut oggflac = Self::new();
oggflac.info = OggFLACStreamInfo::from_reader(&mut reader)?;
reader.seek(std::io::SeekFrom::Start(0))?;
oggflac.tags = Some(OggFLACVComment::from_reader(
&mut reader,
oggflac.info.serial_number,
)?);
Ok(oggflac)
}
fn save(&mut self) -> Result<()> {
debug_event!("saving OGG FLAC metadata");
self.save_with_options(None::<&str>, None)
}
fn clear(&mut self) -> Result<()> {
let prev_tags = self.tags.take();
let mut inner = VCommentDict::new();
inner.set_vendor(String::new());
self.tags = Some(OggFLACVComment {
inner,
serial_number: self.info.serial_number,
});
if let Err(e) = self.save() {
self.tags = prev_tags;
return Err(e);
}
Ok(())
}
fn save_to_writer(&mut self, writer: &mut dyn crate::ReadWriteSeek) -> Result<()> {
let tags = self
.tags
.as_ref()
.ok_or_else(|| AudexError::InvalidData("No tags available for saving".to_string()))?;
let buf = crate::util::read_all_from_writer_limited(writer, "in-memory Ogg FLAC save")?;
let mut cursor = Cursor::new(buf);
tags.inject(&mut cursor, None)?;
let result = cursor.into_inner();
writer.seek(std::io::SeekFrom::Start(0))?;
writer.write_all(&result)?;
crate::util::truncate_writer_dyn(writer, result.len() as u64)?;
Ok(())
}
fn clear_writer(&mut self, writer: &mut dyn crate::ReadWriteSeek) -> Result<()> {
let mut inner = VCommentDict::new();
inner.set_vendor(String::new());
self.tags = Some(OggFLACVComment {
inner,
serial_number: self.info.serial_number,
});
self.save_to_writer(writer)
}
fn save_to_path(&mut self, path: &Path) -> Result<()> {
self.save_with_options(Some(path), None)
}
fn add_tags(&mut self) -> Result<()> {
if self.tags.is_some() {
return Err(AudexError::InvalidOperation(
"Tags already exist".to_string(),
));
}
self.tags = Some(OggFLACVComment {
inner: VCommentDict::new(),
serial_number: self.info.serial_number,
});
Ok(())
}
fn tags(&self) -> Option<&Self::Tags> {
self.tags.as_ref()
}
fn tags_mut(&mut self) -> Option<&mut Self::Tags> {
self.tags.as_mut()
}
fn info(&self) -> &Self::Info {
&self.info
}
fn score(_filename: &str, header: &[u8]) -> i32 {
let oggs_score = if header.len() >= 4 && &header[0..4] == b"OggS" {
1
} else {
0
};
let flac_score = {
let has_flac = header.windows(4).any(|window| window == b"FLAC");
let has_flac_lower = header.windows(4).any(|window| window == b"fLaC");
(if has_flac { 1 } else { 0 }) + (if has_flac_lower { 1 } else { 0 })
};
oggs_score * flac_score
}
fn mime_types() -> &'static [&'static str] {
&["audio/x-oggflac"]
}
}
pub fn clear<P: AsRef<Path>>(path: P) -> Result<()> {
let mut oggflac = OggFlac::load(path)?;
oggflac.clear()
}
#[cfg(feature = "async")]
impl OggFlac {
pub async fn load_async<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_buf = path.as_ref().to_path_buf();
let file = TokioFile::open(&path_buf).await?;
let mut reader = TokioBufReader::new(file);
reader.seek(SeekFrom::Start(0)).await?;
let info = Self::parse_info_async(&mut reader).await?;
let tags = Self::parse_tags_async(&mut reader, info.serial_number).await?;
Ok(Self {
info,
tags: Some(tags),
filename: path_buf.to_string_lossy().to_string(),
})
}
async fn parse_info_async<R: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin>(
reader: &mut R,
) -> Result<OggFLACStreamInfo> {
let mut stream_info = OggFLACStreamInfo::default();
loop {
let page = match OggPage::from_reader_async(reader).await {
Ok(page) => page,
Err(_) => {
return Err(AudexError::InvalidData("No FLAC stream found".to_string()));
}
};
if let Some(first_packet) = page.packets.first() {
if first_packet.len() >= 5 && &first_packet[0..5] == b"\x7FFLAC" {
stream_info.serial_number = page.serial;
stream_info.parse_header(first_packet)?;
Self::post_tags_info_async(reader, &mut stream_info).await?;
return Ok(stream_info);
}
}
}
}
async fn post_tags_info_async<R: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin>(
reader: &mut R,
info: &mut OggFLACStreamInfo,
) -> Result<()> {
if info.length.is_some() {
return Ok(());
}
if let Some(last_page) = OggPage::find_last_async(reader, info.serial_number, true).await? {
if info.sample_rate > 0 && last_page.position > 0 {
let duration_secs = last_page.position as f64 / info.sample_rate as f64;
if duration_secs.is_finite()
&& duration_secs >= 0.0
&& duration_secs <= u64::MAX as f64
{
info.length = Some(std::time::Duration::from_secs_f64(duration_secs));
}
}
}
Ok(())
}
async fn parse_tags_async<R: tokio::io::AsyncRead + tokio::io::AsyncSeek + Unpin>(
reader: &mut R,
serial_number: u32,
) -> Result<OggFLACVComment> {
reader.seek(SeekFrom::Start(0)).await?;
let mut pages = Vec::new();
let mut found_tags = false;
let mut cumulative_bytes = 0u64;
let limits = crate::limits::ParseLimits::default();
loop {
let page = match OggPage::from_reader_async(reader).await {
Ok(page) => page,
Err(_) => break,
};
if page.serial == serial_number {
if let Some(first_packet) = page.packets.first() {
if first_packet.len() >= 4 {
let block_type = first_packet[0] & 0x7F;
if block_type == 4 {
OggPage::accumulate_page_bytes_with_limit(
limits,
&mut cumulative_bytes,
&page,
"Ogg FLAC comment packet",
)?;
pages.push(page);
found_tags = true;
} else if found_tags {
let is_complete = pages
.last()
.ok_or_else(|| {
AudexError::InvalidData(
"expected non-empty page list after comment block".into(),
)
})?
.is_complete();
if !is_complete {
OggPage::accumulate_page_bytes_with_limit(
limits,
&mut cumulative_bytes,
&page,
"Ogg FLAC comment packet",
)?;
pages.push(page);
} else {
break;
}
}
}
}
}
}
let mut tags = OggFLACVComment {
inner: VCommentDict::new(),
serial_number,
};
if pages.is_empty() {
return Ok(tags);
}
let packets = OggPage::to_packets(&pages, false)?;
if packets.is_empty() || packets[0].len() < 4 {
return Ok(tags);
}
let comment_data = &packets[0][4..];
let mut cursor = Cursor::new(comment_data);
let _ = tags
.inner
.load(&mut cursor, crate::vorbis::ErrorMode::Replace, false);
Ok(tags)
}
pub async fn save_async(&mut self) -> Result<()> {
self.save_with_options_async(None::<&str>, None).await
}
pub async fn save_with_options_async<P>(
&mut self,
path: Option<P>,
padding_func: Option<fn(&crate::tags::PaddingInfo) -> i64>,
) -> Result<()>
where
P: AsRef<Path>,
{
let (file_path, is_new_path) = match &path {
Some(p) => (p.as_ref().to_string_lossy().to_string(), true),
None => (self.filename.clone(), false),
};
if file_path.is_empty() {
return Err(AudexError::InvalidData(
"No filename available for saving".to_string(),
));
}
let tags = self
.tags
.as_ref()
.ok_or_else(|| AudexError::InvalidData("No tags available for saving".to_string()))?;
let mut file = TokioOpenOptions::new()
.read(true)
.write(true)
.open(&file_path)
.await?;
Self::inject_tags_async(&mut file, tags, padding_func).await?;
if is_new_path {
self.filename = file_path;
}
Ok(())
}
async fn inject_tags_async(
fileobj: &mut TokioFile,
tags: &OggFLACVComment,
_padding_func: Option<fn(&crate::tags::PaddingInfo) -> i64>,
) -> Result<()> {
const MAX_PAGE_SEARCH: usize = 1024;
fileobj.seek(SeekFrom::Start(0)).await?;
let mut page = OggPage::from_reader_async(fileobj).await?;
let mut pages_read = 1usize;
while page.packets.is_empty() || !page.packets[0].starts_with(b"\x7FFLAC") {
page = OggPage::from_reader_async(fileobj).await?;
pages_read += 1;
if pages_read > MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while searching for FLAC header".to_string(),
));
}
}
let first_page = page;
let expected_seq = first_page.sequence + 1;
let mut page = OggPage::from_reader_async(fileobj).await?;
pages_read += 1;
while !(page.sequence == expected_seq && page.serial == first_page.serial) {
page = OggPage::from_reader_async(fileobj).await?;
pages_read += 1;
if pages_read > MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while searching for comment block".to_string(),
));
}
}
let mut old_pages = vec![page];
loop {
let last_page = old_pages.last().ok_or_else(|| {
AudexError::InvalidData(
"expected non-empty page list while reading comment pages".into(),
)
})?;
if last_page.is_complete() || last_page.packets.len() > 1 {
break;
}
let page = OggPage::from_reader_async(fileobj).await?;
pages_read += 1;
if pages_read > MAX_PAGE_SEARCH {
return Err(AudexError::InvalidData(
"Too many OGG pages while reading comment pages".to_string(),
));
}
if page.serial == first_page.serial {
old_pages.push(page);
}
}
let packets = OggPage::to_packets(&old_pages, false)?;
if packets.is_empty() {
return Err(AudexError::InvalidData("No packets found".to_string()));
}
let mut comment_to_write = tags.inner.clone();
if !comment_to_write.keys().is_empty() {
comment_to_write.set_vendor(format!("Audex {}", VERSION_STRING));
}
let mut comment_data = Vec::new();
comment_to_write.write(&mut comment_data, Some(false))?;
if comment_data.len() > 0xFF_FFFF {
return Err(AudexError::InvalidData(format!(
"OGG FLAC comment data size ({}) exceeds 24-bit maximum ({})",
comment_data.len(),
0xFF_FFFF
)));
}
let header_byte = packets[0][0];
let block_size = comment_data.len() as u32;
let mut new_comment_packet = Vec::with_capacity(4 + comment_data.len());
new_comment_packet.push(header_byte);
new_comment_packet.extend_from_slice(&[
((block_size >> 16) & 0xFF) as u8,
((block_size >> 8) & 0xFF) as u8,
(block_size & 0xFF) as u8,
]);
new_comment_packet.extend_from_slice(&comment_data);
let mut new_packets = packets;
new_packets[0] = new_comment_packet;
let new_pages = OggPage::from_packets(new_packets, old_pages[0].sequence, 4096, 2048);
if new_pages.is_empty() {
return Err(AudexError::InvalidData(
"Failed to create new OGG pages".to_string(),
));
}
OggPage::replace_async(fileobj, &old_pages, new_pages).await?;
Ok(())
}
pub async fn clear_async(&mut self) -> Result<()> {
let mut inner = VCommentDict::new();
inner.set_vendor(String::new());
let empty_tags = OggFLACVComment {
inner,
serial_number: self.info.serial_number,
};
self.tags = Some(empty_tags);
self.save_async().await?;
Ok(())
}
pub async fn delete_async<P: AsRef<Path>>(path: P) -> Result<()> {
let mut oggflac = Self::load_async(path).await?;
oggflac.clear_async().await
}
}
#[cfg(feature = "async")]
pub async fn clear_async<P: AsRef<Path>>(path: P) -> Result<()> {
OggFlac::delete_async(path).await
}