#![no_std]
#![warn(missing_docs)]
#![allow(clippy::style)]
#![allow(clippy::missing_safety_doc)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
pub use mp3lame_sys as ffi;
use alloc::vec::Vec;
use core::mem::{self, MaybeUninit};
use core::num::{NonZeroU32, NonZeroUsize};
use core::ptr::{self, NonNull};
use core::{cmp, fmt};
use core::ffi::c_int;
mod input;
pub use input::*;
pub const MAX_ALBUM_ART_SIZE: usize = 128 * 1024;
pub const fn max_required_buffer_size(sample_number: usize) -> usize {
let mut sample_extra_size = sample_number / 4;
if (sample_number % 4) > 0 {
sample_extra_size = sample_extra_size.wrapping_add(1);
}
sample_number.wrapping_add(sample_extra_size).wrapping_add(7200)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Id3TagError {
AlbumArtOverflow,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BuildError {
Generic,
NoMem,
BadBRate,
BadSampleFreq,
InternalError,
Other(c_int),
}
impl BuildError {
#[inline(always)]
fn from_c_int(code: c_int) -> Result<(), Self> {
if code >= 0 {
return Ok(())
}
match code {
-1 => Err(Self::Generic),
-10 => Err(Self::NoMem),
-11 => Err(Self::BadBRate),
-12 => Err(Self::BadSampleFreq),
-13 => Err(Self::InternalError),
_ => Err(Self::Other(code)),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for BuildError {
}
impl fmt::Display for BuildError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Generic => fmt.write_str("error"),
Self::NoMem => fmt.write_str("alloc failure"),
Self::BadBRate => fmt.write_str("bad bitrate"),
Self::BadSampleFreq => fmt.write_str("bad sample frequency"),
Self::InternalError => fmt.write_str("internal error"),
Self::Other(code) => fmt.write_fmt(format_args!("error code={code}")),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum EncodeError {
BufferTooSmall,
NoMem,
InvalidState,
PsychoAcoustic,
Other(c_int),
}
impl EncodeError {
#[inline(always)]
fn from_c_int(code: c_int) -> Result<usize, Self> {
if code >= 0 {
return Ok(code as usize)
}
match code {
-1 => Err(Self::BufferTooSmall),
-2 => Err(Self::NoMem),
-3 => Err(Self::InvalidState),
-4 => Err(Self::PsychoAcoustic),
_ => Err(Self::Other(code)),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {
}
impl fmt::Display for EncodeError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::BufferTooSmall => fmt.write_str("output buffer is insufficient for encoder output"),
Self::NoMem => fmt.write_str("alloc failure"),
Self::InvalidState => fmt.write_str("attempt to use uninitialized encoder"),
Self::PsychoAcoustic => fmt.write_str("psycho acoustic problems"),
Self::Other(code) => fmt.write_fmt(format_args!("error code={code}")),
}
}
}
#[derive(Copy, Clone)]
#[repr(u16)]
pub enum Bitrate {
Kbps8 = 8,
Kbps16 = 16,
Kbps24 = 24,
Kbps32 = 32,
Kbps40 = 40,
Kbps48 = 48,
Kbps64 = 64,
Kbps80 = 80,
Kbps96 = 96,
Kbps112 = 112,
Kbps128 = 128,
Kbps160 = 160,
Kbps192 = 192,
Kbps224 = 224,
Kbps256 = 256,
Kbps320 = 320,
}
pub use Bitrate as Birtate;
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum VbrMode {
Off = ffi::vbr_mode::vbr_off as u8,
Mt = ffi::vbr_mode::vbr_mt as u8,
Rh = ffi::vbr_mode::vbr_rh as u8,
Abr = ffi::vbr_mode::vbr_abr as u8,
Mtrh = ffi::vbr_mode::vbr_mtrh as u8,
}
impl Default for VbrMode {
#[inline(always)]
fn default() -> Self {
Self::Mtrh
}
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Mode {
Mono = ffi::MPEG_mode::MONO as u8,
Stereo = ffi::MPEG_mode::STEREO as u8,
JointStereo = ffi::MPEG_mode::JOINT_STEREO as u8,
DaulChannel = ffi::MPEG_mode::DUAL_CHANNEL as u8,
NotSet = ffi::MPEG_mode::NOT_SET as u8,
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Quality {
Best = 0,
SecondBest = 1,
NearBest = 2,
VeryNice = 3,
Nice = 4,
Good = 5,
Decent = 6,
Ok = 7,
SecondWorst = 8,
Worst = 9,
}
#[derive(Copy, Clone)]
pub struct Id3Tag<'a> {
pub title: &'a [u8],
pub artist: &'a [u8],
pub album: &'a [u8],
pub album_art: &'a [u8],
pub year: &'a [u8],
pub comment: &'a [u8],
}
impl Id3Tag<'_> {
#[inline(always)]
pub const fn is_any_set(&self) -> bool {
!self.title.is_empty() || !self.artist.is_empty() || !self.album.is_empty() || !self.album_art.is_empty() || !self.year.is_empty() || !self.comment.is_empty()
}
}
pub struct Builder {
inner: NonNull<ffi::lame_global_flags>,
}
impl Builder {
#[inline]
pub fn new() -> Option<Self> {
let ptr = unsafe {
ffi::lame_init()
};
NonNull::new(ptr).map(|inner| Self {
inner
})
}
#[inline(always)]
pub unsafe fn as_ptr(&mut self) -> *mut ffi::lame_global_flags {
self.ptr()
}
#[inline(always)]
fn ptr(&mut self) -> *mut ffi::lame_global_flags {
self.inner.as_ptr()
}
#[inline]
pub fn set_output_sample_rate(&mut self, rate: Option<NonZeroU32>) -> Result<(), BuildError> {
let rate = rate.map_or(0, NonZeroU32::get).try_into().unwrap_or(c_int::MAX);
let res = unsafe {
ffi::lame_set_out_samplerate(self.ptr(), rate)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_output_sample_rate(mut self, rate: Option<NonZeroU32>) -> Result<Self, BuildError> {
self.set_output_sample_rate(rate)?;
Ok(self)
}
#[inline]
pub fn set_sample_rate(&mut self, rate: u32) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_in_samplerate(self.ptr(), rate.try_into().unwrap_or(c_int::MAX))
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_sample_rate(mut self, rate: u32) -> Result<Self, BuildError> {
self.set_sample_rate(rate)?;
Ok(self)
}
#[inline]
pub fn set_num_channels(&mut self, num: u8) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_num_channels(self.ptr(), num as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_num_channels(mut self, num: u8) -> Result<Self, BuildError> {
self.set_num_channels(num)?;
Ok(self)
}
#[inline]
pub fn set_brate(&mut self, brate: Bitrate) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_brate(self.ptr(), brate as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_brate(mut self, brate: Bitrate) -> Result<Self, BuildError> {
self.set_brate(brate)?;
Ok(self)
}
#[inline]
pub fn set_mode(&mut self, mode: Mode) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_mode(self.ptr(), mode as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_mode(mut self, mode: Mode) -> Result<Self, BuildError> {
self.set_mode(mode)?;
Ok(self)
}
#[inline]
pub fn set_quality(&mut self, quality: Quality) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_quality(self.ptr(), quality as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_quality(mut self, quality: Quality) -> Result<Self, BuildError> {
self.set_quality(quality)?;
Ok(self)
}
#[inline]
pub fn set_vbr_quality(&mut self, quality: Quality) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_VBR_q(self.ptr(), quality as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_vbr_quality(mut self, quality: Quality) -> Result<Self, BuildError> {
self.set_vbr_quality(quality)?;
Ok(self)
}
#[inline]
pub fn set_to_write_vbr_tag(&mut self, value: bool) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_bWriteVbrTag(self.ptr(), value as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_to_write_vbr_tag(mut self, value: bool) -> Result<Self, BuildError> {
self.set_to_write_vbr_tag(value)?;
Ok(self)
}
#[inline]
pub fn set_vbr_mode(&mut self, value: VbrMode) -> Result<(), BuildError> {
let res = unsafe {
ffi::lame_set_VBR(self.ptr(), value as _)
};
BuildError::from_c_int(res)
}
#[inline]
pub fn with_vbr_mode(mut self, value: VbrMode) -> Result<Self, BuildError> {
self.set_vbr_mode(value)?;
Ok(self)
}
#[inline]
pub fn set_id3_tag(&mut self, value: Id3Tag<'_>) -> Result<(), Id3TagError> {
if !value.is_any_set() {
return Ok(());
}
const MAX_BUFFER: usize = 250;
let mut buffer = [0u8; MAX_BUFFER + 1];
unsafe {
ffi::id3tag_init(self.ptr());
ffi::id3tag_add_v2(self.ptr());
if !value.album_art.is_empty() {
let size = value.album_art.len();
if size > MAX_ALBUM_ART_SIZE {
return Err(Id3TagError::AlbumArtOverflow);
}
let ptr = value.album_art.as_ptr();
ffi::id3tag_set_albumart(self.ptr(), ptr as _, size);
}
if !value.title.is_empty() {
let size = cmp::min(MAX_BUFFER, value.title.len());
ptr::copy_nonoverlapping(value.title.as_ptr(), buffer.as_mut_ptr(), size);
buffer[size] = 0;
ffi::id3tag_set_title(self.ptr(), buffer.as_ptr() as _);
}
if !value.album.is_empty() {
let size = cmp::min(MAX_BUFFER, value.album.len());
ptr::copy_nonoverlapping(value.album.as_ptr(), buffer.as_mut_ptr(), size);
buffer[size] = 0;
ffi::id3tag_set_album(self.ptr(), buffer.as_ptr() as _);
}
if !value.artist.is_empty() {
let size = cmp::min(MAX_BUFFER, value.artist.len());
ptr::copy_nonoverlapping(value.artist.as_ptr(), buffer.as_mut_ptr(), size);
buffer[size] = 0;
ffi::id3tag_set_artist(self.ptr(), buffer.as_ptr() as _);
}
if !value.year.is_empty() {
let size = cmp::min(MAX_BUFFER, value.year.len());
ptr::copy_nonoverlapping(value.year.as_ptr(), buffer.as_mut_ptr(), size);
buffer[size] = 0;
ffi::id3tag_set_year(self.ptr(), buffer.as_ptr() as _);
}
if !value.comment.is_empty() {
let size = cmp::min(MAX_BUFFER, value.comment.len());
ptr::copy_nonoverlapping(value.comment.as_ptr(), buffer.as_mut_ptr(), size);
buffer[size] = 0;
ffi::id3tag_set_comment(self.ptr(), buffer.as_ptr() as _);
}
}
Ok(())
}
#[inline]
pub fn with_id3_tag(mut self, value: Id3Tag<'_>) -> Result<Self, Id3TagError> {
self.set_id3_tag(value)?;
Ok(self)
}
#[inline]
pub fn build(mut self) -> Result<Encoder, BuildError> {
let res = unsafe {
ffi::lame_init_params(self.ptr())
};
match BuildError::from_c_int(res) {
Ok(()) => {
let inner = self.inner;
mem::forget(self);
Ok(Encoder { inner })
},
Err(error) => Err(error),
}
}
}
impl Drop for Builder {
#[inline]
fn drop(&mut self) {
unsafe {
ffi::lame_close(self.ptr())
};
}
}
pub struct Encoder {
inner: NonNull<ffi::lame_global_flags>,
}
impl Encoder {
#[inline(always)]
fn ptr(&self) -> *mut ffi::lame_global_flags {
self.inner.as_ptr()
}
#[inline]
pub fn sample_rate(&self) -> u32 {
unsafe {
ffi::lame_get_in_samplerate(self.ptr()) as u32
}
}
#[inline]
pub fn num_channels(&self) -> u8 {
unsafe {
ffi::lame_get_num_channels(self.ptr()) as u8
}
}
#[inline]
pub fn is_lame_tag_written(&self) -> bool {
unsafe {
ffi::lame_get_bWriteVbrTag(self.ptr()) != 0
}
}
#[inline]
pub fn id3v2_tag_size(&self) -> usize {
unsafe {
ffi::lame_get_id3v2_tag(self.ptr(), ptr::null_mut(), 0)
}
}
#[inline]
pub fn lame_tag_size(&self) -> usize {
unsafe {
ffi::lame_get_lametag_frame(self.ptr(), ptr::null_mut(), 0)
}
}
#[inline]
pub fn lame_tag_encode(&self, output: &mut [MaybeUninit<u8>]) -> Option<NonZeroUsize> {
if output.len() < self.lame_tag_size() {
None
} else {
NonZeroUsize::new(unsafe {
ffi::lame_get_lametag_frame(self.ptr(), output.as_mut_ptr() as _, output.len())
})
}
}
#[inline]
pub fn lame_tag_encode_to_vec(&self, output: &mut Vec<u8>) -> Option<NonZeroUsize> {
let original_len = output.len();
match self.lame_tag_encode(output.spare_capacity_mut()) {
Some(written) => {
unsafe {
output.set_len(original_len.saturating_add(written.get()));
}
Some(written)
},
None => None
}
}
#[inline]
pub fn encode(&mut self, input: impl EncoderInput, output: &mut [MaybeUninit<u8>]) -> Result<usize, EncodeError> {
let output_len = output.len();
let output_buf = output.as_mut_ptr();
let result = input.encode(self, output_buf as _, output_len);
EncodeError::from_c_int(result)
}
#[inline(always)]
pub fn encode_to_vec(&mut self, input: impl EncoderInput, output: &mut Vec<u8>) -> Result<usize, EncodeError> {
let original_len = output.len();
match self.encode(input, output.spare_capacity_mut()) {
Ok(written) => {
unsafe {
output.set_len(original_len.saturating_add(written));
}
Ok(written)
},
Err(error) => Err(error),
}
}
#[inline]
pub fn flush<T: EncoderFlush>(&mut self, output: &mut [MaybeUninit<u8>]) -> Result<usize, EncodeError> {
let output_len = output.len();
let output_buf = output.as_mut_ptr();
let result = T::flush(self, output_buf as _, output_len);
EncodeError::from_c_int(result)
}
#[inline(always)]
pub fn flush_to_vec<T: EncoderFlush>(&mut self, output: &mut Vec<u8>) -> Result<usize, EncodeError> {
let original_len = output.len();
match self.flush::<T>(output.spare_capacity_mut()) {
Ok(written) => {
unsafe {
output.set_len(original_len.saturating_add(written));
}
Ok(written)
},
Err(error) => Err(error),
}
}
}
impl Drop for Encoder {
#[inline]
fn drop(&mut self) {
unsafe {
ffi::lame_close(self.ptr())
};
}
}
unsafe impl Send for Encoder {}
unsafe impl Sync for Encoder {}
pub fn encoder() -> Result<Encoder, BuildError> {
match Builder::new() {
Some(mut builder) => {
builder.set_brate(Bitrate::Kbps192)?;
builder.set_quality(Quality::Best)?;
builder.build()
},
None => Err(BuildError::NoMem)
}
}