use std::{
fs::File,
io::{BufReader, Cursor, Read, Seek, Write},
path::Path,
};
use crate::{
DecodeConfig, EncoderConfig, RecompressConfig, Result,
decode::DecodeSummary,
decode_output::{commit_temp_output, open_temp_output},
encoder::EncodeSummary,
error::Error,
input::PcmReaderOptions,
pcm::PcmContainer,
read::{FlacReader, FlacReaderOptions, read_flac_reader_with_options},
recompress::{RecompressProgressSink, RecompressSummary},
};
pub use crate::{
inspect_flac_total_samples, inspect_pcm_total_samples, inspect_raw_pcm_total_samples,
};
const FILE_READ_BUFFER_CAPACITY: usize = 4 * 1024 * 1024;
pub fn encode_file<P, Q>(input_path: P, output_path: Q) -> Result<EncodeSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
encode_file_with_config(&EncoderConfig::default(), input_path, output_path)
}
pub fn encode_bytes(input: &[u8]) -> Result<Vec<u8>> {
encode_bytes_with_config(&EncoderConfig::default(), input)
}
pub fn recompress_file<P, Q>(input_path: P, output_path: Q) -> Result<RecompressSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
recompress_file_with_config(&RecompressConfig::default(), input_path, output_path)
}
pub fn recompress_bytes(input: &[u8]) -> Result<Vec<u8>> {
recompress_bytes_with_config(&RecompressConfig::default(), input)
}
pub(crate) fn encode_file_with_config<P, Q>(
config: &EncoderConfig,
input_path: P,
output_path: Q,
) -> Result<EncodeSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let input_path = input_path.as_ref();
let output_path = output_path.as_ref();
let reader = crate::input::PcmReader::with_options(
open_buffered_reader(input_path)?,
pcm_reader_options(config),
)?;
let mut encoder = config.clone().into_encoder(File::create(output_path)?);
encoder.encode_source(reader.into_source())
}
pub(crate) fn encode_bytes_with_config(config: &EncoderConfig, input: &[u8]) -> Result<Vec<u8>> {
let reader =
crate::input::PcmReader::with_options(Cursor::new(input), pcm_reader_options(config))?;
let mut encoder = config
.clone()
.into_encoder(Cursor::new(Vec::with_capacity(input.len())));
encoder.encode_source(reader.into_source())?;
Ok(encoder.into_inner().into_inner())
}
pub(crate) fn recompress_bytes_with_config(
config: &RecompressConfig,
input: &[u8],
) -> Result<Vec<u8>> {
let reader = read_flac_reader_with_options(Cursor::new(input), config.flac_reader_options())?;
let (writer, _) = recompress_reader_session_with_config_and_progress(
config,
reader,
Cursor::new(Vec::with_capacity(input.len())),
&mut crate::progress::NoProgress,
)?;
Ok(writer.into_inner())
}
pub(crate) fn recompress_file_with_config<P, Q>(
config: &RecompressConfig,
input_path: P,
output_path: Q,
) -> Result<RecompressSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let mut progress = crate::progress::NoProgress;
recompress_file_with_config_and_progress(config, input_path, output_path, &mut progress)
}
pub(crate) fn recompress_file_with_config_and_progress<P, Q, R>(
config: &RecompressConfig,
input_path: P,
output_path: Q,
progress: &mut R,
) -> Result<RecompressSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
R: crate::recompress::RecompressProgressSink,
{
let input_path = input_path.as_ref();
let output_path = output_path.as_ref();
let (temp_path, temp_file) = open_temp_output(output_path)?;
let result = (|| {
let reader = read_flac_reader_with_options(
open_buffered_reader(input_path)?,
config.flac_reader_options(),
)?;
let (mut temp_writer, summary) = recompress_reader_session_with_config_and_progress(
config, reader, temp_file, progress,
)?;
temp_writer.flush()?;
Ok(summary)
})();
match result {
Ok(summary) => {
if let Err(error) = commit_temp_output(&temp_path, output_path) {
let _ = std::fs::remove_file(&temp_path);
return Err(error);
}
Ok(summary)
}
Err(error) => {
let _ = std::fs::remove_file(&temp_path);
Err(error)
}
}
}
pub fn decode_file<P, Q>(input_path: P, output_path: Q) -> Result<DecodeSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
decode_file_with_config(&DecodeConfig::default(), input_path, output_path)
}
pub fn decode_bytes(input: &[u8]) -> Result<Vec<u8>> {
decode_bytes_with_config(&DecodeConfig::default(), input)
}
pub(crate) fn decode_bytes_with_config(config: &DecodeConfig, input: &[u8]) -> Result<Vec<u8>> {
let reader = read_flac_reader_with_options(Cursor::new(input), flac_reader_options(config))?;
let mut decoder = (*config).into_decoder(Cursor::new(Vec::with_capacity(input.len())));
decoder.decode_source(reader.into_decode_source())?;
Ok(decoder.into_inner().into_inner())
}
pub(crate) fn decode_file_with_config<P, Q>(
config: &DecodeConfig,
input_path: P,
output_path: Q,
) -> Result<DecodeSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let mut progress = crate::progress::NoProgress;
decode_file_with_config_and_progress(config, input_path, output_path, &mut progress)
}
pub(crate) fn decode_file_with_config_and_progress<P, Q, R>(
config: &DecodeConfig,
input_path: P,
output_path: Q,
progress: &mut R,
) -> Result<DecodeSummary>
where
P: AsRef<Path>,
Q: AsRef<Path>,
R: crate::progress::ProgressSink,
{
let input_path = input_path.as_ref();
let output_path = output_path.as_ref();
let (temp_path, temp_file) = open_temp_output(output_path)?;
let output_container =
inferred_output_container_from_path(output_path)?.unwrap_or(config.output_container());
let result = (|| {
let reader = read_flac_reader_with_options(
open_buffered_reader(input_path)?,
flac_reader_options(config),
)?;
let mut decoder = config
.with_output_container(output_container)
.into_decoder(temp_file);
let summary = decoder.decode_source_with_sink(reader.into_decode_source(), progress)?;
decoder.into_inner().flush()?;
Ok(summary)
})();
match result {
Ok(summary) => {
if let Err(error) = commit_temp_output(&temp_path, output_path) {
let _ = std::fs::remove_file(&temp_path);
return Err(error);
}
Ok(summary)
}
Err(error) => {
let _ = std::fs::remove_file(&temp_path);
Err(error)
}
}
}
fn recompress_reader_session_with_config_and_progress<R, W, P>(
config: &RecompressConfig,
reader: FlacReader<R>,
writer: W,
progress: &mut P,
) -> Result<(W, RecompressSummary)>
where
R: Read + Seek,
W: Write + Seek,
P: RecompressProgressSink,
{
let source = reader.into_recompress_source();
let mut recompressor = (*config).into_recompressor(writer);
let summary = recompressor.recompress_with_sink(source, progress)?;
Ok((recompressor.into_inner(), summary))
}
fn pcm_reader_options(config: &EncoderConfig) -> PcmReaderOptions {
PcmReaderOptions {
capture_fxmd: config.capture_fxmd(),
strict_fxmd_validation: config.strict_fxmd_validation(),
}
}
fn flac_reader_options(config: &DecodeConfig) -> FlacReaderOptions {
FlacReaderOptions {
strict_seektable_validation: config.strict_seektable_validation(),
strict_channel_mask_provenance: config.strict_channel_mask_provenance(),
}
}
fn open_buffered_reader(path: &Path) -> Result<BufReader<File>> {
Ok(BufReader::with_capacity(
FILE_READ_BUFFER_CAPACITY,
File::open(path)?,
))
}
fn inferred_output_container_from_path(path: &Path) -> Result<Option<PcmContainer>> {
match path.extension().and_then(|ext| ext.to_str()) {
Some(ext) if ext.eq_ignore_ascii_case("rf64") => wav_output_container(PcmContainer::Rf64),
Some(ext) if ext.eq_ignore_ascii_case("w64") => wav_output_container(PcmContainer::Wave64),
Some(ext) if ext.eq_ignore_ascii_case("aif") => aiff_output_container(PcmContainer::Aiff),
Some(ext) if ext.eq_ignore_ascii_case("aiff") => aiff_output_container(PcmContainer::Aiff),
Some(ext) if ext.eq_ignore_ascii_case("aifc") => aiff_output_container(PcmContainer::Aifc),
Some(ext) if ext.eq_ignore_ascii_case("caf") => caf_output_container(),
Some(ext) if ext.eq_ignore_ascii_case("wav") => wav_output_container(PcmContainer::Wave),
Some(ext) => Err(Error::Decode(format!(
"unsupported decode output extension '.{ext}'"
))),
None => Ok(None),
}
}
#[cfg(feature = "wav")]
fn wav_output_container(container: PcmContainer) -> Result<Option<PcmContainer>> {
Ok(Some(container))
}
#[cfg(not(feature = "wav"))]
fn wav_output_container(_container: PcmContainer) -> Result<Option<PcmContainer>> {
Err(Error::Decode(
"RIFF/WAVE family decode output requires the `wav` cargo feature".into(),
))
}
#[cfg(feature = "aiff")]
fn aiff_output_container(container: PcmContainer) -> Result<Option<PcmContainer>> {
Ok(Some(container))
}
#[cfg(not(feature = "aiff"))]
fn aiff_output_container(_container: PcmContainer) -> Result<Option<PcmContainer>> {
Err(Error::Decode(
"AIFF/AIFC decode output requires the `aiff` cargo feature".into(),
))
}
#[cfg(feature = "caf")]
fn caf_output_container() -> Result<Option<PcmContainer>> {
Ok(Some(PcmContainer::Caf))
}
#[cfg(not(feature = "caf"))]
fn caf_output_container() -> Result<Option<PcmContainer>> {
Err(Error::Decode(
"CAF decode output requires the `caf` cargo feature".into(),
))
}