use flac_codec::decode::FlacSampleReader;
use flac_codec::encode::FlacSampleWriter;
use std::path::Path;
fn main() {
if cfg!(debug_assertions) {
eprintln!("WARNING: running in --release mode is preferred for best performance");
}
match std::env::args_os().skip(1).collect::<Vec<_>>().as_slice() {
[first, rest @ .., last] => {
if let Err(err) = concat_flacs(first, rest, last) {
eprintln!("* Error: {err}");
}
}
_ => eprintln!("* Usage: flac-cat <file 1.flac> [file 2.flac] ... <output.flac>"),
}
}
fn concat_flacs<P: AsRef<Path>>(first: &P, rest: &[P], output: &P) -> Result<(), Error> {
use flac_codec::{decode::Metadata, encode::Options};
let mut first = FlacSampleReader::open(first)?;
let rest = rest
.iter()
.map(|f| {
FlacSampleReader::open(f)
.map_err(Error::Flac)
.and_then(|r| {
(r.sample_rate() == first.sample_rate()
&& r.channel_count() == first.channel_count()
&& r.bits_per_sample() == first.bits_per_sample())
.then_some(r)
.ok_or(Error::InconsistentParameters)
})
})
.collect::<Result<Vec<_>, _>>()?;
let mut output = FlacSampleWriter::create(
output,
Options::default(),
first.sample_rate(),
first.bits_per_sample(),
first.channel_count(),
first.total_samples().and_then(|first_samples| {
rest.iter()
.map(|r| r.total_samples())
.sum::<Option<u64>>()
.map(|rest_samples| first_samples + rest_samples)
.map(|total_samples| total_samples * u64::from(first.channel_count()))
}),
)?;
copy(&mut first, &mut output)?;
for mut file in rest {
copy(&mut file, &mut output)?;
}
output.finalize().map_err(Error::Flac)
}
fn copy<R, W>(
r: &mut FlacSampleReader<R>,
w: &mut FlacSampleWriter<W>,
) -> Result<(), flac_codec::Error>
where
R: std::io::Read,
W: std::io::Write + std::io::Seek,
{
loop {
match r.fill_buf()? {
[] => break Ok(()),
buf => {
let buf_len = buf.len();
w.write(buf)?;
r.consume(buf_len);
}
}
}
}
#[derive(Debug)]
enum Error {
Flac(flac_codec::Error),
InconsistentParameters,
}
impl From<flac_codec::Error> for Error {
fn from(err: flac_codec::Error) -> Self {
Self::Flac(err)
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Flac(flac) => flac.fmt(f),
Self::InconsistentParameters => {
"all files must have same samples rate, channels and bits-per-sample".fmt(f)
}
}
}
}