Support writing to opus

This commit is contained in:
Serial 2021-04-23 13:31:35 -04:00
parent c182b293fb
commit 14c04f3ae8
4 changed files with 136 additions and 54 deletions

View file

@ -17,11 +17,11 @@ in order to parse metadata in different file formats.
| File Format | Extensions | Read | Write | Metadata Format(s) |
|-------------|-------------------------------------------|------|-------|----------------------|
| Ape | `ape` |**X** |**X** | `APEv2` |
| AIFF | `aiff`, `aif` |**X** |**X** | `ID3v2` |
| FLAC | `flac` |**X** |**X** | `Vorbis Comments` |
| AIFF | `aiff`, `aif` |**X** |**X** | `ID3v2` |
| FLAC | `flac` |**X** |**X** | `Vorbis Comments` |
| MP3 | `mp3` |**X** |**X** | `ID3v2` |
| MP4 | `mp4`, `m4a`, `m4b`, `m4p`, `m4v`, `isom` |**X** |**X** | `Vorbis Comments` |
| Opus | `opus` |**X** | | `Vorbis Comments` |
| Opus | `opus` |**X** |**X** | `Vorbis Comments` |
| Ogg | `ogg`, `oga` |**X** |**X** | `Vorbis Comments` |
| WAV | `wav`, `wave` |**X** |**X** | `RIFF INFO`, `ID3v2` |

View file

@ -1,8 +1,9 @@
use crate::{Error, Result};
#[cfg(feature = "ogg")]
use ogg::PacketWriteEndInfo;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
pub(crate) fn ogg<T>(data: T, packet: &[u8]) -> Result<Cursor<Vec<u8>>>
pub(crate) fn ogg<T>(data: T, packet: &[u8]) -> Result<Vec<u8>>
where
T: Read + Seek,
{
@ -49,7 +50,79 @@ where
}
c.seek(SeekFrom::Start(0))?;
Ok(c)
Ok(c.into_inner())
}
pub(crate) fn opus<T>(mut data: T, packet: &[u8]) -> Result<Vec<u8>>
where
T: Read + Seek,
{
let mut beginning_sig = [0; 4];
data.read_exact(&mut beginning_sig)?;
if &beginning_sig != b"OggS" {
return Err(Error::UnknownFormat);
}
let mut first_page = [0; 23];
data.read_exact(&mut first_page)?;
let mut segment_table = vec![0; first_page[22] as usize];
data.read_exact(&mut segment_table)?;
let mut head = vec![0; segment_table.iter().map(|&b| b as usize).sum()];
data.read_exact(&mut head)?;
let (ident, head) = head.split_at(8);
if ident != b"OpusHead" {
return Err(Error::UnknownFormat);
}
if head[10] != 0 {
let mut channel_mapping_info = [0; 1];
data.read_exact(&mut channel_mapping_info)?;
let mut channel_mapping = vec![0; channel_mapping_info[0] as usize];
data.read_exact(&mut channel_mapping)?;
}
let mut sig = [0; 4];
data.read_exact(&mut sig)?;
if &sig != b"OggS" {
return Err(Error::UnknownFormat);
}
let mut second_page = [0; 23];
data.read_exact(&mut second_page)?;
let size_pos = data.seek(SeekFrom::Current(0))? as usize;
let mut segment_table = vec![0; second_page[22] as usize];
data.read_exact(&mut segment_table)?;
let start = data.seek(SeekFrom::Current(0))? as usize;
let mut tags = vec![0; segment_table.iter().map(|&b| b as usize).sum()];
data.read_exact(&mut tags)?;
let end = data.seek(SeekFrom::Current(0))? as usize;
if &tags[0..8] != b"OpusTags" {
return Err(Error::UnknownFormat);
}
data.seek(SeekFrom::Start(0))?;
let mut content = Vec::new();
data.read_to_end(&mut content)?;
content.splice(start..end, packet.to_vec());
content.insert(size_pos, (packet.len() % 255) as u8);
content.remove(size_pos + 1);
Ok(content)
}
pub(crate) fn wav<T>(mut data: T, packet: Vec<u8>) -> Result<Vec<u8>>

View file

@ -7,14 +7,16 @@ use crate::{
ToAny, ToAnyTag,
};
use std::borrow::BorrowMut;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Seek, SeekFrom, Write};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
#[cfg(feature = "duration")]
use std::time::Duration;
const VORBIS: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
const OPUSTAGS: [u8; 8] = [79, 112, 117, 115, 84, 97, 103, 115];
struct VorbisInnerTag {
format: Option<VorbisFormat>,
vendor: String,
@ -352,51 +354,7 @@ impl AudioTagWrite for VorbisTag {
if let Some(format) = self.inner.format.clone() {
match format {
VorbisFormat::Ogg => {
const START_SIGNATURE: [u8; 7] = [3, 118, 111, 114, 98, 105, 115];
const END_BYTE: u8 = 1;
let vendor = self.inner.vendor.clone();
let vendor_bytes = vendor.as_bytes();
let comments: Vec<(String, String)> = self
.inner
.comments
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
let vendor_len = vendor.len() as u32;
let comments_len = comments.len() as u32;
let mut packet = Vec::new();
packet.extend(START_SIGNATURE.iter());
packet.extend(vendor_len.to_le_bytes().iter());
packet.extend(vendor_bytes.iter());
packet.extend(comments_len.to_le_bytes().iter());
let mut comment_str = Vec::new();
for (a, b) in comments {
comment_str.push(format!("{}={}", a, b));
let last = comment_str.last().unwrap();
let len = last.as_bytes().len() as u32;
packet.extend(len.to_le_bytes().iter());
packet.extend(last.as_bytes().iter());
}
packet.push(END_BYTE);
let mut file_bytes = Vec::new();
std::io::copy(file.borrow_mut(), &mut file_bytes)?;
let data = logic::write::ogg(Cursor::new(file_bytes), &*packet)?.into_inner();
file.seek(SeekFrom::Start(0))?;
file.set_len(0)?;
file.write_all(&data)?;
write(file, &VORBIS, &self.inner.vendor, &self.inner.comments)?;
},
VorbisFormat::Flac => {
let mut flac_tag: metaflac::Tag = self.into();
@ -404,7 +362,7 @@ impl AudioTagWrite for VorbisTag {
flac_tag.write_to(file)?;
},
VorbisFormat::Opus => {
todo!()
write(file, &OPUSTAGS, &self.inner.vendor, &self.inner.comments)?;
},
}
}
@ -417,3 +375,54 @@ impl AudioTagWrite for VorbisTag {
Ok(())
}
}
fn write(
file: &mut File,
sig: &[u8],
vendor: &str,
comments: &HashMap<String, String>,
) -> Result<()> {
let mut packet = Vec::new();
packet.extend(sig.iter());
let comments: Vec<(String, String)> = comments
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
let vendor_len = vendor.len() as u32;
packet.extend(vendor_len.to_le_bytes().iter());
packet.extend(vendor.as_bytes().iter());
let comments_len = comments.len() as u32;
packet.extend(comments_len.to_le_bytes().iter());
let mut comment_str = Vec::new();
for (a, b) in comments {
comment_str.push(format!("{}={}", a, b));
let last = comment_str.last().unwrap();
let len = last.as_bytes().len() as u32;
packet.extend(len.to_le_bytes().iter());
packet.extend(last.as_bytes().iter());
}
if sig == VORBIS {
packet.push(1);
}
let mut file_bytes = Vec::new();
file.read_to_end(&mut file_bytes)?;
let data = if sig == VORBIS {
logic::write::ogg(Cursor::new(file_bytes), &*packet)?
} else {
logic::write::opus(Cursor::new(file_bytes), &*packet)?
};
file.seek(SeekFrom::Start(0))?;
file.set_len(0)?;
file.write_all(&data)?;
Ok(())
}

View file

@ -20,7 +20,7 @@
//! | FLAC | `flac` |**X** |**X** | `Vorbis Comments` |
//! | MP3 | `mp3` |**X** |**X** | `ID3v2` |
//! | MP4 | `mp4`, `m4a`, `m4b`, `m4p`, `m4v`, `isom` |**X** |**X** | `Vorbis Comments` |
//! | Opus | `opus` |**X** | | `Vorbis Comments` |
//! | Opus | `opus` |**X** |**X** | `Vorbis Comments` |
//! | Ogg | `ogg`, `oga` |**X** |**X** | `Vorbis Comments` |
//! | WAV | `wav`, `wave` |**X** |**X** | `RIFF INFO`, `ID3v2` |
//!