use std::{convert::Infallible, fmt::Display}; use opendal::Operator; use opus2::Application; use time::{UtcDateTime, format_description::StaticFormatDescription, macros::format_description}; use twilight_model::id::{ Id, marker::{ChannelMarker, GuildMarker}, }; #[derive(Debug, Clone)] pub struct RenderManager { operator: Operator, } impl RenderManager { pub fn new(operator: Operator) -> Self { Self { operator } } } #[derive(Debug, Clone)] pub struct Render { pub start: UtcDateTime, pub end: UtcDateTime, pub guild_id: Id, pub voice_channel_id: Id, } const DATE_FORMAT: StaticFormatDescription = format_description!("[year]-[month]-[day]T[hour]-[minute]-[second].[subsecond]Z"); impl Display for Render { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { start, end, guild_id, voice_channel_id, } = self; let start = start.format(&DATE_FORMAT).unwrap(); let end = end.format(&DATE_FORMAT).unwrap(); write!(f, "{guild_id}/{voice_channel_id}/{start}_{end}.opus") } } #[derive(Debug)] pub struct RenderData { pub channels: opus2::Channels, pub sample_rate: u32, pub samples: Vec, } impl RenderManager { pub async fn write( &self, render: &Render, RenderData { channels, sample_rate, samples, }: RenderData, ) -> Result< (), Infallible, // TODO: a real error type > { let mut encoder = opus2::Encoder::new(sample_rate, channels, Application::Audio).expect("TODO"); const MAX_SIZE: usize = usize::MAX / 4; let encode_result = encoder.encode_vec(&samples, MAX_SIZE); if let Err(error) = &encode_result { tracing::error!(?error); } else { tracing::info!("encode ok"); } encode_result.expect("TODO"); let path = render.to_string(); let write_result = self.operator.write(&path, bytes).await; tracing::info!(?write_result); write_result.expect("TODO"); Ok(()) } }