use numpy::PyReadonlyArrayDyn;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use super::enums::{Application, Bandwidth, Signal};
use super::numpy_io::borrow_interleaved_f32;
#[pyclass(module = "ruopus", name = "OpusEncoder")]
pub struct OpusEncoder {
inner: crate::encoder::OpusEncoder,
channels: usize,
complexity: u8,
bitrate: Option<u32>,
dtx: bool,
bandwidth: Bandwidth,
bandwidth_forced: bool,
vbr: bool,
force_channels: Option<usize>,
signal: Signal,
application: Application,
max_bandwidth: Bandwidth,
inband_fec: bool,
packet_loss_perc: u8,
}
impl OpusEncoder {
fn encode_with<'py>(
&mut self,
py: Python<'py>,
pcm: &PyReadonlyArrayDyn<'_, f32>,
max_bytes: usize,
f: fn(&mut crate::encoder::OpusEncoder, &[f32], usize) -> Result<Vec<u8>, crate::encoder::EncodeError>,
) -> PyResult<Bound<'py, PyBytes>> {
let pcm = borrow_interleaved_f32(pcm, self.channels)?;
let packet = py.detach(|| f(&mut self.inner, &pcm, max_bytes))?;
Ok(PyBytes::new(py, &packet))
}
}
#[pymethods]
impl OpusEncoder {
#[new]
#[pyo3(signature = (
channels,
*,
complexity = 10,
bitrate = None,
dtx = false,
bandwidth = Bandwidth::FullBand,
signal = Signal::Auto,
application = Application::Audio,
max_bandwidth = Bandwidth::FullBand,
inband_fec = false,
packet_loss_perc = 0,
))]
#[allow(clippy::too_many_arguments, reason = "mirrors the Opus encoder control surface")]
fn new(
channels: usize,
complexity: u8,
bitrate: Option<u32>,
dtx: bool,
bandwidth: Bandwidth,
signal: Signal,
application: Application,
max_bandwidth: Bandwidth,
inband_fec: bool,
packet_loss_perc: u8,
) -> PyResult<Self> {
if channels != 1 && channels != 2 {
return Err(PyValueError::new_err("channels must be 1 or 2"));
}
let mut inner = crate::encoder::OpusEncoder::new(channels);
inner.set_complexity(complexity);
inner.set_bitrate(bitrate);
inner.set_dtx(dtx);
inner.set_signal(signal.into());
inner.set_application(application.into());
inner.set_max_bandwidth(max_bandwidth.into());
inner.set_bandwidth(bandwidth.into());
inner.set_inband_fec(inband_fec);
inner.set_packet_loss_perc(packet_loss_perc);
Ok(Self {
inner,
channels,
complexity: complexity.min(10),
bitrate,
dtx,
bandwidth,
bandwidth_forced: true,
vbr: true,
force_channels: None,
signal,
application,
max_bandwidth,
inband_fec,
packet_loss_perc: packet_loss_perc.min(100),
})
}
#[getter]
fn channels(&self) -> usize {
self.channels
}
#[getter]
fn get_complexity(&self) -> u8 {
self.complexity
}
#[setter]
fn set_complexity(&mut self, complexity: u8) {
self.inner.set_complexity(complexity);
self.complexity = complexity.min(10);
}
#[getter]
fn get_bitrate(&self) -> Option<u32> {
self.bitrate
}
#[setter]
fn set_bitrate(&mut self, bitrate: Option<u32>) {
self.inner.set_bitrate(bitrate);
self.bitrate = bitrate;
}
#[getter]
fn get_dtx(&self) -> bool {
self.dtx
}
#[setter]
fn set_dtx(&mut self, on: bool) {
self.inner.set_dtx(on);
self.dtx = on;
}
#[getter]
fn get_inband_fec(&self) -> bool {
self.inband_fec
}
#[setter]
fn set_inband_fec(&mut self, on: bool) {
self.inner.set_inband_fec(on);
self.inband_fec = on;
}
#[getter]
fn get_packet_loss_perc(&self) -> u8 {
self.packet_loss_perc
}
#[setter]
fn set_packet_loss_perc(&mut self, perc: u8) {
self.inner.set_packet_loss_perc(perc);
self.packet_loss_perc = perc.min(100);
}
#[getter]
fn get_vbr(&self) -> bool {
self.vbr
}
#[setter]
fn set_vbr(&mut self, vbr: bool) {
self.inner.set_vbr(vbr);
self.vbr = vbr;
}
#[getter]
fn get_bandwidth(&self) -> Bandwidth {
self.bandwidth
}
#[setter]
fn set_bandwidth(&mut self, bandwidth: Bandwidth) {
self.inner.set_bandwidth(bandwidth.into());
self.bandwidth = bandwidth;
self.bandwidth_forced = true;
}
fn set_auto_bandwidth(&mut self) {
self.inner.set_auto_bandwidth();
self.bandwidth_forced = false;
}
#[getter]
fn get_bandwidth_forced(&self) -> bool {
self.bandwidth_forced
}
#[getter]
fn get_signal(&self) -> Signal {
self.signal
}
#[setter]
fn set_signal(&mut self, signal: Signal) {
self.inner.set_signal(signal.into());
self.signal = signal;
}
#[getter]
fn get_application(&self) -> Application {
self.application
}
#[setter]
fn set_application(&mut self, application: Application) {
self.inner.set_application(application.into());
self.application = application;
}
#[getter]
fn get_max_bandwidth(&self) -> Bandwidth {
self.max_bandwidth
}
#[setter]
fn set_max_bandwidth(&mut self, bandwidth: Bandwidth) {
self.inner.set_max_bandwidth(bandwidth.into());
self.max_bandwidth = bandwidth;
}
#[getter]
fn get_force_channels(&self) -> Option<usize> {
self.force_channels
}
#[setter]
fn set_force_channels(&mut self, force: Option<usize>) -> PyResult<()> {
if let Some(n) = force
&& n != 1
&& n != 2
{
return Err(PyValueError::new_err("force_channels must be None, 1, or 2"));
}
self.inner.set_force_channels(force);
self.force_channels = force;
Ok(())
}
#[getter]
fn lookahead(&self) -> u32 {
self.inner.lookahead()
}
#[getter]
fn final_range(&self) -> u32 {
self.inner.final_range()
}
fn reset(&mut self) {
self.inner.reset();
}
#[pyo3(signature = (pcm: "numpy.typing.NDArray[numpy.float32]", max_bytes = 1275))]
fn encode_auto<'py>(
&mut self,
py: Python<'py>,
pcm: PyReadonlyArrayDyn<'_, f32>,
max_bytes: usize,
) -> PyResult<Bound<'py, PyBytes>> {
self.encode_with(py, &pcm, max_bytes, crate::encoder::OpusEncoder::encode_auto)
}
#[pyo3(signature = (pcm: "numpy.typing.NDArray[numpy.float32]", max_bytes = 1275))]
fn encode<'py>(
&mut self,
py: Python<'py>,
pcm: PyReadonlyArrayDyn<'_, f32>,
max_bytes: usize,
) -> PyResult<Bound<'py, PyBytes>> {
self.encode_with(py, &pcm, max_bytes, crate::encoder::OpusEncoder::encode)
}
#[pyo3(signature = (pcm: "numpy.typing.NDArray[numpy.float32]", max_bytes = 1275))]
fn encode_silk<'py>(
&mut self,
py: Python<'py>,
pcm: PyReadonlyArrayDyn<'_, f32>,
max_bytes: usize,
) -> PyResult<Bound<'py, PyBytes>> {
self.encode_with(py, &pcm, max_bytes, crate::encoder::OpusEncoder::encode_silk)
}
#[pyo3(signature = (pcm: "numpy.typing.NDArray[numpy.float32]", max_bytes = 1275))]
fn encode_hybrid<'py>(
&mut self,
py: Python<'py>,
pcm: PyReadonlyArrayDyn<'_, f32>,
max_bytes: usize,
) -> PyResult<Bound<'py, PyBytes>> {
self.encode_with(py, &pcm, max_bytes, crate::encoder::OpusEncoder::encode_hybrid)
}
fn __repr__(&self) -> String {
format!(
"OpusEncoder(channels={}, complexity={}, bitrate={}, dtx={})",
self.channels,
self.complexity,
self.bitrate.map_or_else(|| "None".to_string(), |b| b.to_string()),
if self.dtx { "True" } else { "False" },
)
}
}