mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-01-19 07:33:53 +00:00
Support writing to opus
This commit is contained in:
parent
c182b293fb
commit
14c04f3ae8
4 changed files with 136 additions and 54 deletions
|
@ -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` |
|
||||
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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` |
|
||||
//!
|
||||
|
|
Loading…
Reference in a new issue