Compare commits

..

No commits in common. "07e6c52e94f3cd600ba03624edc070a24d363cbf" and "f09a02a58d8e7bf2bdd3e7209d80f3be4fe2787d" have entirely different histories.

9 changed files with 70 additions and 632 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "bingus" name = "bingus"
version = "0.9.0" version = "0.8.0"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
description.workspace = true description.workspace = true

View file

@ -1,6 +1,4 @@
#[cfg(feature = "printpdf")]
pub use printpdf; pub use printpdf;
#[cfg(feature = "printpdf")]
mod pdf; mod pdf;
#[cfg(feature = "shiva")] #[cfg(feature = "shiva")]
mod shiva; mod shiva;

View file

@ -1,4 +1,2 @@
#[cfg(feature = "font-kit")]
mod fontkit; mod fontkit;
#[cfg(feature = "font-kit")]
pub use fontkit::Font; pub use fontkit::Font;

View file

@ -1,6 +1,3 @@
#[cfg(feature = "image")]
mod image; mod image;
#[cfg(feature = "image")]
pub use image::ImageBuffer as Image; pub use image::ImageBuffer as Image;
#[cfg(feature = "image")]
pub use image::*; pub use image::*;

View file

@ -1,5 +1,3 @@
#![deny(unused_crate_dependencies)]
#[cfg(feature = "binary")] #[cfg(feature = "binary")]
pub mod bin; pub mod bin;
#[cfg(feature = "documents")] #[cfg(feature = "documents")]
@ -25,13 +23,13 @@ pub mod dynamic {
path::Path, path::Path,
}; };
#[cfg(all(feature = "documents", feature = "shiva"))] #[cfg(feature = "documents")]
use super::doc::ShivaDocument; use super::doc::ShivaDocument;
#[cfg(all(feature = "fonts", feature = "font-kit"))] #[cfg(feature = "fonts")]
use super::fnt::Font as FontKitFont; use super::fnt::Font as FontKitFont;
#[cfg(all(feature = "pictures", feature = "image"))] #[cfg(feature = "pictures")]
use super::img::{self, DynamicImage}; use super::img::{self, DynamicImage};
#[cfg(all(feature = "music", feature = "symphonia"))] #[cfg(feature = "music")]
use super::snd::{self, Audio}; use super::snd::{self, Audio};
#[cfg(feature = "text")] #[cfg(feature = "text")]
use super::txt::Text; use super::txt::Text;
@ -40,32 +38,32 @@ pub mod dynamic {
use std::marker::PhantomData; use std::marker::PhantomData;
use cfg_if::cfg_if; use cfg_if::cfg_if;
#[cfg(all(feature = "fonts", feature = "font-kit"))] #[cfg(feature = "fonts")]
use font_kit::error::FontLoadingError; use font_kit::error::FontLoadingError;
pub use infer::*; pub use infer::*;
#[cfg(all(feature = "documents", feature = "printpdf"))] #[cfg(feature = "documents")]
use printpdf::PdfDocument; use printpdf::PdfDocument;
#[cfg(all(feature = "documents", feature = "shiva"))] #[cfg(feature = "documents")]
use shiva::core::{bytes, Document, DocumentType}; use shiva::core::{bytes, Document, DocumentType};
use thiserror::Error; use thiserror::Error;
pub enum DynamicBendable<'a> { pub enum DynamicBendable<'a> {
#[cfg(all(feature = "pictures", feature = "image"))] #[cfg(feature = "pictures")]
Image(DynamicImage), Image(DynamicImage),
#[cfg(feature = "binary")] #[cfg(feature = "binary")]
Binary(Bytes), Binary(Bytes),
#[cfg(all(feature = "music", feature = "symphonia"))] #[cfg(feature = "music")]
Sound(Audio), Sound(Audio),
#[cfg(feature = "text")] #[cfg(feature = "text")]
Text(Text<'a>), Text(Text<'a>),
#[cfg(not(feature = "text"))] #[cfg(not(feature = "text"))]
Phantom(PhantomData<&'a ()>), Phantom(PhantomData<&'a ()>),
#[cfg(all(feature = "documents", feature = "shiva"))] #[cfg(feature = "documents")]
Doc(ShivaDocument), Doc(ShivaDocument),
#[cfg(all(feature = "documents", feature = "printpdf"))] #[cfg(feature = "documents")]
Archive(PdfDocument), Archive(PdfDocument),
Meta, Meta,
#[cfg(all(feature = "fonts", feature = "font-kit"))] #[cfg(feature = "fonts")]
Font(FontKitFont), Font(FontKitFont),
} }
@ -87,22 +85,22 @@ pub mod dynamic {
pub enum OpenError { pub enum OpenError {
#[error("io: {0}")] #[error("io: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
#[cfg(all(feature = "pictures", feature = "image"))] #[cfg(feature = "pictures")]
#[error("image: {0}")] #[error("image: {0}")]
Image(#[from] img::ImageError), Image(#[from] img::ImageError),
#[cfg(all(feature = "music", feature = "symphonia"))] #[cfg(feature = "music")]
#[error("audio: {0}")] #[error("audio: {0}")]
Audio(#[from] snd::AudioOpenError), Audio(#[from] snd::AudioOpenError),
#[cfg(all(feature = "documents", feature = "printpdf"))] #[cfg(feature = "documents")]
#[error("pdf: {0}")] #[error("pdf: {0}")]
Pdf(String), Pdf(String),
#[cfg(feature = "text")] #[cfg(feature = "text")]
#[error("text: {0}")] #[error("text: {0}")]
Text(#[from] FromUtf8Error), Text(#[from] FromUtf8Error),
#[cfg(all(feature = "documents", feature = "shiva"))] #[cfg(feature = "documents")]
#[error("document: {0}")] #[error("document: {0}")]
Document(#[from] ShivaError), Document(#[from] ShivaError),
#[cfg(all(feature = "fonts", feature = "font-kit"))] #[cfg(feature = "fonts")]
#[error("font: {0:?}")] #[error("font: {0:?}")]
Font(#[from] FontLoadingError), Font(#[from] FontLoadingError),
} }
@ -157,11 +155,11 @@ pub mod dynamic {
.map( .map(
|(matcher, extension)| -> Result<DynamicBendable, OpenError> { |(matcher, extension)| -> Result<DynamicBendable, OpenError> {
Ok(match matcher { Ok(match matcher {
#[cfg(all(feature = "pictures", feature = "image"))] #[cfg(feature = "pictures")]
Image => DynamicBendable::Image(img::load_from_memory(&bytes)?), Image => DynamicBendable::Image(img::load_from_memory(&bytes)?),
#[cfg(all(feature = "music", feature = "symphonia"))] #[cfg(feature = "music")]
Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?), Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?),
#[cfg(all(feature = "documents", feature = "printpdf"))] #[cfg(feature = "documents")]
Archive if extension == "pdf" => DynamicBendable::Archive( Archive if extension == "pdf" => DynamicBendable::Archive(
PdfDocument::try_from_data_bytes( PdfDocument::try_from_data_bytes(
bytes, bytes,
@ -170,7 +168,7 @@ pub mod dynamic {
) )
.map_err(OpenError::Pdf)?, .map_err(OpenError::Pdf)?,
), ),
#[cfg(all(feature = "documents", feature = "shiva"))] #[cfg(feature = "documents")]
Archive | Doc => { Archive | Doc => {
let document_type = DocumentType::from_extension(extension) let document_type = DocumentType::from_extension(extension)
.ok_or(ShivaUnknownExtensionError) .ok_or(ShivaUnknownExtensionError)
@ -184,7 +182,7 @@ pub mod dynamic {
document_type, document_type,
)) ))
} }
#[cfg(all(feature = "fonts", feature = "font-kit"))] #[cfg(feature = "fonts")]
Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes( Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes(
bytes, bytes,
(), (),

View file

@ -1,8 +1,5 @@
#[cfg(feature = "symphonia")]
pub use symphonia::core::*; pub use symphonia::core::*;
mod raw; mod raw;
pub use raw::*; pub use raw::*;
#[cfg(feature = "symphonia")]
mod simphonia; mod simphonia;
#[cfg(feature = "symphonia")]
pub use simphonia::*; pub use simphonia::*;

View file

@ -12,7 +12,7 @@ keywords.workspace = true
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
bingus = "0.8" bingus = "0.6"
strum = { version = "0", features = ["derive"] } strum = { version = "0", features = ["derive"] }
derive-new = "0" derive-new = "0"
clap = { version = "4.5.40", features = ["derive"] } clap = { version = "4.5.40", features = ["derive"] }

View file

@ -18,18 +18,17 @@ use strum::{Display, EnumString};
pub(crate) struct Cli { pub(crate) struct Cli {
/// Input file or standard input ("-"). /// Input file or standard input ("-").
pub(crate) input: FileOrStd<Stdin>, pub(crate) input: FileOrStd<Stdin>,
#[command(subcommand)]
pub(crate) output_format: OutputFormat,
/// Output file or standard output ("-"). /// Output file or standard output ("-").
pub(crate) output: FileOrStd<Stdout>, pub(crate) output: FileOrStd<Stdout>,
/// Output format
#[command(subcommand)]
pub(crate) output_format: Option<OutputFormat>,
} }
#[derive(Clone, Copy, Parser)] #[derive(Clone, Copy, Parser)]
#[cfg_attr(debug_assertions, derive(Debug))] #[cfg_attr(debug_assertions, derive(Debug))]
pub struct Dimensions { pub struct Dimensions {
pub width: Option<u32>, pub width: u32,
pub height: Option<u32>, pub height: u32,
} }
#[derive(Clone, Copy, Parser)] #[derive(Clone, Copy, Parser)]
@ -41,23 +40,16 @@ pub struct ShivaFormat {
#[derive(Clone, Copy, Parser)] #[derive(Clone, Copy, Parser)]
pub struct Mp3Format { pub struct Mp3Format {
#[arg(default_value = "44100")]
pub sample_rate: u32, pub sample_rate: u32,
#[arg(default_value = "128")]
pub bitrate: u16, pub bitrate: u16,
#[arg(default_value = "5")]
pub quality: u8, pub quality: u8,
#[arg(default_value_t)]
pub sample_format: Mp3SampleFormat, pub sample_format: Mp3SampleFormat,
} }
impl Default for Mp3Format {
fn default() -> Self {
Self {
sample_rate: 44100,
bitrate: 128,
quality: 5,
sample_format: Default::default(),
}
}
}
#[derive(Clone, Copy, EnumString, Default, Display)] #[derive(Clone, Copy, EnumString, Default, Display)]
#[strum(ascii_case_insensitive)] #[strum(ascii_case_insensitive)]
pub enum Mp3SampleFormat { pub enum Mp3SampleFormat {
@ -71,19 +63,12 @@ pub enum Mp3SampleFormat {
#[derive(Clone, Copy, Parser)] #[derive(Clone, Copy, Parser)]
pub struct WavFormat { pub struct WavFormat {
#[arg(default_value = "48000")]
pub sample_rate: u32, pub sample_rate: u32,
#[arg(default_value_t)]
pub sample_format: WavSampleFormat, pub sample_format: WavSampleFormat,
} }
impl Default for WavFormat {
fn default() -> Self {
Self {
sample_rate: 48000,
sample_format: Default::default(),
}
}
}
#[derive(Clone, Copy, EnumString, Display, Default)] #[derive(Clone, Copy, EnumString, Display, Default)]
#[strum(ascii_case_insensitive)] #[strum(ascii_case_insensitive)]
pub enum WavSampleFormat { pub enum WavSampleFormat {
@ -96,20 +81,12 @@ pub enum WavSampleFormat {
#[derive(Clone, Copy, Parser)] #[derive(Clone, Copy, Parser)]
pub struct FlacFormat { pub struct FlacFormat {
#[arg(default_value = "48000")]
pub sample_rate: usize, pub sample_rate: usize,
#[arg(value_parser = flac_bps_value_parser)] #[arg(value_parser = flac_bps_value_parser, default_value = "16")]
pub bps: usize, pub bps: usize,
} }
impl Default for FlacFormat {
fn default() -> Self {
Self {
sample_rate: 48000,
bps: 16,
}
}
}
fn flac_bps_value_parser(input: &str) -> anyhow::Result<usize> { fn flac_bps_value_parser(input: &str) -> anyhow::Result<usize> {
input.parse().context("not a number").and_then(|n| match n { input.parse().context("not a number").and_then(|n| match n {
8 | 16 | 24 => Ok(n), 8 | 16 | 24 => Ok(n),
@ -129,8 +106,8 @@ impl From<WavSampleFormat> for hound::SampleFormat {
impl From<Dimensions> for bingus::img::Dimensions { impl From<Dimensions> for bingus::img::Dimensions {
fn from(value: Dimensions) -> Self { fn from(value: Dimensions) -> Self {
Self { Self {
width: value.width.unwrap_or_default(), width: value.width,
height: value.height.unwrap_or_default(), height: value.height,
} }
} }
} }
@ -196,10 +173,8 @@ where
} }
} }
#[derive(Clone, Parser, Default)] #[derive(Clone, Parser)]
pub(crate) enum OutputFormat { pub(crate) enum OutputFormat {
#[default]
Auto,
Bytes, Bytes,
Pdf, Pdf,
ShivaDocument(ShivaFormat), ShivaDocument(ShivaFormat),

View file

@ -1,5 +1,3 @@
#![deny(unused_crate_dependencies)]
use std::{ use std::{
borrow::Cow, borrow::Cow,
fs::File, fs::File,
@ -8,12 +6,12 @@ use std::{
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use bingus::{ use bingus::{
doc::{printpdf::PdfDocument, DocumentType, ShivaDocument, ShivaFormat}, doc::{printpdf::PdfDocument, ShivaDocument},
dynamic::{self, DynamicBendable}, dynamic::{self, DynamicBendable},
fnt::Font, fnt::Font,
img::{ img::{
Dimensions, GrayAlphaImage, GrayImage, Image, ImageFormat, Luma, LumaA, Rgb, Rgb32FImage, GrayAlphaImage, GrayImage, Image, Luma, LumaA, Rgb, Rgb32FImage, RgbImage, Rgba,
RgbImage, Rgba, Rgba32FImage, RgbaImage, Rgba32FImage, RgbaImage,
}, },
snd::{conv::IntoSample, sample::i24, RawSamples}, snd::{conv::IntoSample, sample::i24, RawSamples},
txt::Text, txt::Text,
@ -44,7 +42,7 @@ fn main() -> anyhow::Result<()> {
.context("reading input")?; .context("reading input")?;
buf buf
}; };
let bytes = match dynamic::open(&mut Cursor::new(input_bytes.clone())) let mut bytes = match dynamic::open(&mut Cursor::new(input_bytes.clone()))
.context("guessing media type / loading media")? .context("guessing media type / loading media")?
.or(String::from_utf8(input_bytes.clone()) .or(String::from_utf8(input_bytes.clone())
.ok() .ok()
@ -72,483 +70,10 @@ fn main() -> anyhow::Result<()> {
.context("bending input into bytes")?; .context("bending input into bytes")?;
match output { match output {
cli::FileOrStd::File(file) => { cli::FileOrStd::File(file) => {
match output_format.unwrap_or_default() { match output_format {
cli::OutputFormat::Auto => { cli::OutputFormat::Bytes => {
let extension = file File::bend_from(bytes, Box::new(file), Default::default())
.extension() .context("writing to file")?;
.and_then(|os_str| os_str.to_str())
.map(|s| s.to_ascii_lowercase());
if let Some(ext) = &extension {
match ext.as_str() {
"pdf" => {
File::bend_from(
PdfDocument::bend_from(bytes, (), Default::default())
.map_err(|s| anyhow!("{}", s))
.context("bending into PDF document")?
.into_data_bytes(),
Box::new(file),
Default::default(),
)
.context("writing PDF to output file")?;
}
_valid_shiva_extension
if DocumentType::from_extension(ext).is_some() =>
{
let document_type = DocumentType::from_extension(ext).unwrap();
File::bend_from(
ShivaDocument::bend_from(
bytes,
ShivaFormat::new(document_type, document_type),
Default::default(),
)
.context("bending into a Shiva Document")?,
Box::new(file),
Default::default(),
)
.context("writing Shiva Document to output file")?;
}
"ttf" | "ttc" | "otf" | "otc" | "cff" | "pfb" | "pfa" | "pfm"
| "afm" | "bdf" | "pcf" | "fon" | "fnt" | "svg" | "dfont" | "woff"
| "woff2" | "eot" => {
File::bend_from(
Font::bend_from(bytes, (), Default::default())
.context("bending to font")?
.into_data_bytes(),
Box::new(file),
Default::default(),
)
.context("writing font to output file")?;
}
"mp3" => {
eprintln!("warning: Rust MP3 lame encoder is freaky!");
let mp3_format = Mp3Format::default();
let Mp3Format {
bitrate,
quality,
sample_rate,
sample_format,
} = mp3_format;
let buff = {
use mp3lame_encoder::{Bitrate::*, Quality::*};
let mut encoder = Mp3EncoderBuilder::new()
.context("Failed to create MP3 encoder builder")?;
encoder
.set_num_channels(1)
.context("Failed to set MP3 encoder channels")?;
encoder
.set_sample_rate(sample_rate)
.context("Failed to set MP3 encoder sample rate")?;
encoder
.set_brate(match bitrate {
8 => Kbps8,
16 => Kbps16,
24 => Kbps24,
32 => Kbps32,
40 => Kbps40,
48 => Kbps48,
64 => Kbps64,
80 => Kbps80,
96 => Kbps96,
112 => Kbps112,
128 => Kbps128,
160 => Kbps160,
192 => Kbps192,
224 => Kbps224,
256 => Kbps256,
320 => Kbps320,
_ => bail!("invalid MP3 bitrate"),
})
.context("Failed to set MP3 encoder bitrate")?;
encoder
.set_quality(match quality {
0 => Best,
1 => SecondBest,
2 => NearBest,
3 => VeryNice,
4 => Nice,
5 => Good,
6 => Decent,
7 => Ok,
8 => SecondWorst,
9 => Worst,
_ => bail!("invalid MP3 quality"),
})
.context("Failed to set MP3 encoder quality")?;
let mut encoder = encoder
.build()
.context("Failed to initialize MP3 encoder")?;
let (encoded_size, mut output) = {
match sample_format {
cli::Mp3SampleFormat::U16 => {
let (left, right): (Vec<_>, Vec<_>) =
RawSamples::<u16>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap()
.into_inner()
.chunks_exact(2)
.map(|c| (c[0], c[1]))
.unzip();
let input = DualPcm {
left: &left,
right: &right,
};
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(
left.len() + right.len(),
),
);
(
encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?,
output,
)
}
cli::Mp3SampleFormat::I16 => {
let (left, right): (Vec<_>, Vec<_>) =
RawSamples::<i16>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap()
.into_inner()
.chunks_exact(2)
.map(|c| (c[0], c[1]))
.unzip();
let input = DualPcm {
left: &left,
right: &right,
};
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(
left.len() + right.len(),
),
);
(
encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?,
output,
)
}
cli::Mp3SampleFormat::I32 => {
let (left, right): (Vec<_>, Vec<_>) =
RawSamples::<i32>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap()
.into_inner()
.chunks_exact(2)
.map(|c| (c[0], c[1]))
.unzip();
let input = DualPcm {
left: &left,
right: &right,
};
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(
left.len() + right.len(),
),
);
(
encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?,
output,
)
}
cli::Mp3SampleFormat::F32 => {
let (left, right): (Vec<_>, Vec<_>) =
RawSamples::<f32>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap()
.into_inner()
.chunks_exact(2)
.map(|c| (c[0], c[1]))
.unzip();
let input = DualPcm {
left: &left,
right: &right,
};
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(
left.len() + right.len(),
),
);
(
encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?,
output,
)
}
cli::Mp3SampleFormat::F64 => {
let (left, right): (Vec<_>, Vec<_>) =
RawSamples::<f64>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap()
.into_inner()
.chunks_exact(2)
.map(|c| (c[0], c[1]))
.unzip();
let input = DualPcm {
left: &left,
right: &right,
};
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(
left.len() + right.len(),
),
);
(
encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?,
output,
)
}
}
};
unsafe {
output.set_len(output.len().wrapping_add(encoded_size));
}
let encoded_size = encoder
.flush::<FlushGap>(output.spare_capacity_mut())
.context(
"Failed MP3 flushing (don't know what that means)",
)?;
unsafe {
output.set_len(output.len().wrapping_add(encoded_size));
}
output
};
File::bend_from(buff, Box::new(file), Default::default())
.context("writing MP3 to output file")?;
}
"wav" => {
let wav_format = Default::default();
let WavFormat {
sample_rate,
sample_format,
} = wav_format;
match sample_format {
cli::WavSampleFormat::I8 => {
let samples = RawSamples::<i8>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap_infallible();
let mut buff = Cursor::new(Vec::with_capacity(
samples.as_ref().len() * 8,
));
{
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate,
bits_per_sample: i8::BITS as u16,
sample_format: sample_format.into(),
},
)
.context("Failed to create WAV writer")?;
for sample in samples.as_ref() {
writer.write_sample(*sample)?;
}
}
File::try_from_data_bytes(
buff.into_inner(),
Box::new(file),
Default::default(),
)
.context("writing WAV data to output file")?
}
cli::WavSampleFormat::I16 => {
let samples = RawSamples::<i16>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap_infallible();
let mut buff = Cursor::new(Vec::with_capacity(
samples.as_ref().len() * 8,
));
{
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate,
bits_per_sample: i16::BITS as u16,
sample_format: sample_format.into(),
},
)
.context("Failed to create WAV writer")?;
for sample in samples.as_ref() {
writer.write_sample(*sample)?;
}
}
File::try_from_data_bytes(
buff.into_inner(),
Box::new(file),
Default::default(),
)
.context("writing WAV data to output file")?
}
cli::WavSampleFormat::I32 => {
let samples = RawSamples::<i32>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap_infallible();
let mut buff = Cursor::new(Vec::with_capacity(
samples.as_ref().len() * 8,
));
{
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate,
bits_per_sample: i32::BITS as u16,
sample_format: sample_format.into(),
},
)
.context("Failed to create WAV writer")?;
for sample in samples.as_ref() {
writer.write_sample(*sample)?;
}
}
File::try_from_data_bytes(
buff.into_inner(),
Box::new(file),
Default::default(),
)
.context("writing WAV data to output file")?
}
cli::WavSampleFormat::F32 => {
let samples = RawSamples::<f32>::bend_from(
bytes,
(),
Default::default(),
)
.unwrap_infallible();
let mut buff = Cursor::new(Vec::with_capacity(
samples.as_ref().len() * 8,
));
{
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate,
bits_per_sample: 32,
sample_format: sample_format.into(),
},
)
.context("Failed to create WAV writer")?;
for sample in samples.as_ref() {
writer.write_sample(*sample)?;
}
}
File::try_from_data_bytes(
buff.into_inner(),
Box::new(file),
Default::default(),
)
.context("writing WAV data to output file")?
}
};
}
"flac" => {
let flac_format = Default::default();
let FlacFormat { sample_rate, bps } = flac_format;
let config = flacenc::config::Encoder::default()
.into_verified()
.expect("Config data error.");
let samples =
RawSamples::<i32>::bend_from(bytes, (), Default::default())
.unwrap_infallible();
let source = flacenc::source::MemSource::from_samples(
samples.as_ref().iter().copied()
.map(|sample| match bps {
8 => IntoSample::<i8>::into_sample(sample) as i32,
16 => IntoSample::<i16>::into_sample(sample) as i32,
24 => IntoSample::<i24>::into_sample(sample).inner(),
_=> unimplemented!("sorry, the current implementation for the flac encoder doesn't support any other bitrate than 8, 16 or 24.")
})
.collect::<Vec<i32>>()
.as_slice(),
1,
bps,
sample_rate,
);
let flac_stream = flacenc::encode_with_fixed_block_size(
&config,
source,
config.block_size,
)
.expect("Encode failed.");
// `Stream` imlpements `BitRepr` so you can obtain the encoded stream via
// `ByteSink` struct that implements `BitSink`.
let mut sink = flacenc::bitsink::ByteSink::new();
flac_stream
.write(&mut sink)
.context("Failed to write samples to FLAC byte sink")?;
File::try_from_data_bytes(
sink.into_inner(),
Box::new(file),
Default::default(),
)
.context("writing FLAC data to output file")?;
}
"txt" => {
File::bend_from(
Text::try_from_data_bytes(bytes, (), Default::default())
.context("invalid UTF-8")?,
Box::new(file),
Default::default(),
)
.context("writing text to output file")?;
}
_valid_image_extension
if ImageFormat::from_extension(ext).is_some() =>
{
let len = bytes.len();
RgbImage::from_data_bytes(
bytes,
Dimensions::square((len / 3).isqrt() as u32),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
"bin" => {
File::bend_from(bytes, Box::new(file), Default::default())
.context("writing to file")?;
}
_ => bail!("Unknown file extension! Can't guess output file type. Please use one of the available types instead (see --help)."),
}
} else {
bail!("Can't guess the output file type without a file extension. Please specify one.");
}
} }
cli::OutputFormat::Pdf => { cli::OutputFormat::Pdf => {
File::bend_from( File::bend_from(
@ -946,126 +471,76 @@ fn main() -> anyhow::Result<()> {
.context("writing text to output file")?; .context("writing text to output file")?;
} }
cli::OutputFormat::ImageLuma8(dimensions) => { cli::OutputFormat::ImageLuma8(dimensions) => {
let len = bytes.len(); GrayImage::from_data_bytes(bytes, dimensions.into(), Default::default())
GrayImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
} }
cli::OutputFormat::ImageLumaA8(dimensions) => { cli::OutputFormat::ImageLumaA8(dimensions) => {
let len = bytes.len(); GrayAlphaImage::from_data_bytes(bytes, dimensions.into(), Default::default())
GrayAlphaImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgb8(dimensions) => { cli::OutputFormat::ImageRgb8(dimensions) => {
let len = bytes.len(); RgbImage::from_data_bytes(bytes, dimensions.into(), Default::default())
RgbImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgba8(dimensions) => { cli::OutputFormat::ImageRgba8(dimensions) => {
let len = bytes.len(); RgbaImage::from_data_bytes(bytes, dimensions.into(), Default::default())
RgbaImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
} }
cli::OutputFormat::ImageLuma16(dimensions) => { cli::OutputFormat::ImageLuma16(dimensions) => {
let len = bytes.len();
Image::<Luma<u16>, Vec<u16>>::from_data_bytes( Image::<Luma<u16>, Vec<u16>>::from_data_bytes(
bytes, bytes,
if_zero_adjust(dimensions.into(), len), dimensions.into(),
Default::default(), Default::default(),
) )
.save(file) .save(file)
.context("writing image data to output file")? .context("writing image data to output file")?
} }
cli::OutputFormat::ImageLumaA16(dimensions) => { cli::OutputFormat::ImageLumaA16(dimensions) => {
let len = bytes.len();
Image::<LumaA<u16>, Vec<u16>>::from_data_bytes( Image::<LumaA<u16>, Vec<u16>>::from_data_bytes(
bytes, bytes,
if_zero_adjust(dimensions.into(), len), dimensions.into(),
Default::default(), Default::default(),
) )
.save(file) .save(file)
.context("writing image data to output file")? .context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgb16(dimensions) => { cli::OutputFormat::ImageRgb16(dimensions) => {
let len = bytes.len();
Image::<Rgb<u16>, Vec<u16>>::from_data_bytes( Image::<Rgb<u16>, Vec<u16>>::from_data_bytes(
bytes, bytes,
if_zero_adjust(dimensions.into(), len), dimensions.into(),
Default::default(), Default::default(),
) )
.save(file) .save(file)
.context("writing image data to output file")? .context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgba16(dimensions) => { cli::OutputFormat::ImageRgba16(dimensions) => {
let len = bytes.len();
Image::<Rgba<u16>, Vec<u16>>::from_data_bytes( Image::<Rgba<u16>, Vec<u16>>::from_data_bytes(
bytes, bytes,
if_zero_adjust(dimensions.into(), len), dimensions.into(),
Default::default(), Default::default(),
) )
.save(file) .save(file)
.context("writing image data to output file")? .context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgb32F(dimensions) => { cli::OutputFormat::ImageRgb32F(dimensions) => {
let len = bytes.len(); Rgb32FImage::from_data_bytes(bytes, dimensions.into(), Default::default())
Rgb32FImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
} }
cli::OutputFormat::ImageRgba32F(dimensions) => { cli::OutputFormat::ImageRgba32F(dimensions) => {
let len = bytes.len(); Rgba32FImage::from_data_bytes(bytes, dimensions.into(), Default::default())
Rgba32FImage::from_data_bytes( .save(file)
bytes, .context("writing image data to output file")?
if_zero_adjust(dimensions.into(), len),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::Bytes => {
File::bend_from(bytes, Box::new(file), Default::default())
.context("writing to file")?;
} }
}; };
} }
cli::FileOrStd::Std(_) => Stdout::new() cli::FileOrStd::Std(_) => Stdout::new()
.write_all(&bytes) .write_all(&mut bytes)
.context("writing to stdout")?, .context("writing to stdout")?,
}; };
Ok(()) Ok(())
} }
fn if_zero_adjust(dimensions: Dimensions, bytes_len: usize) -> Dimensions {
if dimensions == Dimensions::square(0) {
Dimensions::square((bytes_len / 3).isqrt() as u32)
} else if dimensions.width == 0 {
Dimensions::square(dimensions.height)
} else if dimensions.height == 0 {
Dimensions::square(dimensions.width)
} else {
dimensions
}
}