Compare commits

...
Sign in to create a new pull request.

83 commits
fundsp ... main

Author SHA1 Message Date
brevalferrari
63a7cb5c20
Soften text bend-in
Allow invalid UTF-8
2025-09-16 00:31:18 +02:00
brevalferrari
533cb094e0
Typo
Onput -> Output
2025-09-16 00:11:35 +02:00
brevalferrari
c7f537f4f1
fix other deps for crates.io 2025-06-20 12:25:05 +02:00
brevalferrari
399bff704f
fix anyhow dep for crates.io 2025-06-20 12:15:03 +02:00
brevalferrari
1c7d187bd7
fixed bingus version 2025-06-20 11:03:52 +02:00
brevalferrari
4dcb82d3a0
increase version 2025-06-20 11:02:47 +02:00
brevalferrari
e4dddb4aba
complete CLI for input & output to a different format (MP3 lame is freaky) 2025-06-20 10:55:48 +02:00
brevalferrari
2cf1ec385f
relax Bendable trait methods generics 2025-06-19 23:00:23 +02:00
brevalferrari
f72a30b972
remove useless format Bendable method, modify API exposition 2025-06-19 10:34:36 +02:00
brevalferrari
6bb627d77b bong CLI template 2025-06-14 20:56:57 +02:00
Breval Ferrari
52c15986a8
add some strum on Format 2025-04-30 17:27:33 -04:00
Breval Ferrari
53bdd265b3
remove duplicate code, open from any Read sources 2025-04-30 17:07:44 -04:00
Breval Ferrari
130a75d0ca
remove Sized requirement 2025-04-19 19:32:31 -04:00
Breval Ferrari
2a93d99841
fill todos on dynamic open 2025-04-19 18:26:44 -04:00
Breval Ferrari
6e37c21bf2
add unimplemented tip when binary feature is disabled 2025-04-19 18:03:21 -04:00
Breval Ferrari
9596264322
increment minor (forgor) 2025-04-19 17:27:36 -04:00
Breval Ferrari
51ab13b3ef
feature flags everywhere (warnings but no errors so far) 2025-04-19 17:26:58 -04:00
Breval Ferrari
84a3b182ab
add remaining rayon, better feature switches 2025-04-19 15:00:32 -04:00
Breval Ferrari
d2ede29165
rayon optional feature 2025-04-19 12:22:19 -04:00
Breval Ferrari
066d89d250
I actually don't need fontconfig 2025-04-19 12:03:55 -04:00
Breval Ferrari
5c8fa837ca
remove wildcard dep version for crates.io 2025-04-19 11:54:14 -04:00
Breval Ferrari
eae2bd3d48
v0.3.0 with updated README ready for crates.io 2025-04-19 11:50:46 -04:00
Breval Ferrari
4ab4d33e0c
replace &Unit with Cow<Unit> in map F generics 2025-04-19 11:32:51 -04:00
Breval Ferrari
bff2b660dc
update deps 2025-04-15 19:01:46 -04:00
Breval Ferrari
ab294950b4
fonts 2025-04-15 19:00:52 -04:00
Breval Ferrari
f207ac0b06
reorg imports 2025-04-15 19:00:10 -04:00
Breval Ferrari
4df5c27bdd
update deps, add font-kit 2025-04-15 16:10:25 -04:00
Breval Ferrari
24d4b5ac2c
interpret as bytes by default 2025-04-15 15:54:39 -04:00
Breval Ferrari
efb9c7c39d
shiva example 2025-04-06 10:41:41 -04:00
Breval Ferrari
0c3439d788
text & doc support (+dynamic) 2025-04-06 10:41:22 -04:00
Breval Ferrari
ae6e2edafa
unwrap infaillible 2025-04-05 20:02:44 -04:00
Breval Ferrari
b00ce70504
pdf to image example 2025-04-05 19:43:40 -04:00
Breval Ferrari
dc74662263
expose Bytes 2025-04-05 19:43:30 -04:00
Breval Ferrari
df2c3747b7
print warnings, some minor fixes (still doesn't fully work) 2025-04-05 19:43:22 -04:00
Breval Ferrari
7d5c860d05
update printpdf 2025-04-05 19:41:50 -04:00
Breval Ferrari
0ac3340373
doesn't work lol 2025-03-22 23:09:21 -04:00
Breval Ferrari
308e3a9d11
make files bendable 2025-03-22 22:28:41 -04:00
Breval Ferrari
ba15f4fda3
open pdf files, change type to archive 2025-03-20 10:04:35 -04:00
Breval Ferrari
5c3ea7a512
twas nothing in the end 2025-03-20 09:52:11 -04:00
Breval Ferrari
c4fcb7bb25
PDF support but no open yet cause test fails although there's no apparent issue 2025-03-20 02:17:05 -04:00
Breval Ferrari
f9b54bb505
update doc for Audio (changed specs) 2025-03-19 01:28:56 -04:00
Breval Ferrari
d48822fd96
display instead of debug open errors 2025-03-19 01:28:42 -04:00
Breval Ferrari
2c01b3761d
losen deps 2025-03-19 01:28:11 -04:00
Breval Ferrari
ba9443fc08
clippy 2025-03-19 01:16:08 -04:00
Breval Ferrari
19c6ee71d5
passing test! but had to remove FromDataBytes 2025-03-19 01:12:34 -04:00
Breval Ferrari
b94421a223
new sample files 2025-03-19 01:12:15 -04:00
Breval Ferrari
0ac6b3a16d
new sample file 2025-03-19 00:45:33 -04:00
Breval Ferrari
fe1ee27d80
hint from file extension 2025-03-19 00:45:04 -04:00
Breval Ferrari
9130f52f66
set project_root as dev dep 2025-03-17 01:10:52 -04:00
Breval Ferrari
c58275dc75
wrote a test but it doesn't work... 2025-03-17 01:05:31 -04:00
Breval Ferrari
af7b4f77bb
add open function for audio 2025-03-17 00:49:32 -04:00
Breval Ferrari
4d80833bbd
add symphonia codec support for opening and exporting encoded files (not bendable since that would lead to data corruption) 2025-03-17 00:33:38 -04:00
Breval Ferrari
2733cd9345
remove dasp_sample, use Sample from symphonia instead 2025-03-17 00:32:37 -04:00
Breval Ferrari
2d73ac2e4c
whoops, wrong file 2025-03-15 23:06:37 -04:00
Breval Ferrari
84419e3e5f
add sound testing material 2025-03-15 22:35:49 -04:00
Breval Ferrari
090d397409
move testing material to its own folder 2025-03-15 22:18:37 -04:00
Breval Ferrari
299887ff84
some more complex sample manipulation example 2025-03-15 01:11:29 -04:00
Breval Ferrari
bf58d2cd51
databending example in bong 2025-03-15 00:00:51 -04:00
Breval Ferrari
07f2a9fa38
dimensions operator impls, pad image if it's too small 2025-03-14 23:54:22 -04:00
Breval Ferrari
f4e8f7c276
relax trait requirements, rename failable methods with 'try' 2025-03-14 22:53:16 -04:00
Breval Ferrari
58cf13a564
raw sound samples with rayon 2025-03-10 11:06:07 -04:00
Breval Ferrari
76ced9bb9d
raw sound samples 2025-03-10 00:31:20 -04:00
Breval Ferrari
68b90af755
improve rayon on bytes 2025-03-10 00:31:05 -04:00
Breval Ferrari
7ca053d918
remove workspace deps 2025-03-10 00:30:30 -04:00
bcce055d4c
cleanup generic open function 2025-03-06 12:55:59 -05:00
3527c0a2af
add crop setting, DynamicBendable like the image crate 2025-01-18 22:32:21 -05:00
e1edc611bc
binary code (renamed, it was mistaken for the crate's binary) 2025-01-17 22:49:59 -05:00
5e078c951d
new media form: binary data 2025-01-17 19:22:34 -05:00
0ac42b0793
switch to native endianness 2025-01-17 19:21:19 -05:00
1728b9b2c4
rename apply to map just like the image crate, one more test 2025-01-17 19:15:33 -05:00
1508a03717
float subpixel format support, more tests 2025-01-17 18:50:06 -05:00
0d5532ab08
fix for rayon, new test 2025-01-17 12:32:43 -05:00
9748483e74
expose image types, fix impls contracts, one test 2025-01-17 12:22:13 -05:00
ff6e44072e
rebranding, image databending 2025-01-17 01:45:25 -05:00
f7eb44179d
remove this member from the workspace, it's full of errors and things 2025-01-16 17:41:26 -05:00
1ef8d8c4cb
nuke funny zone, we're getting serious 2025-01-16 17:39:26 -05:00
42bd1a49f4
dont know what dis was about, now it's time for revolution 2025-01-16 17:38:34 -05:00
d69011ddb6
download raw data 2024-07-27 10:26:46 +02:00
b6eb5720c9
update image 2024-07-27 08:41:34 +02:00
2190440b3f
Merge branch 'fundsp' 2024-07-27 08:40:02 +02:00
7b39c8e6dc
rename into Into 2024-07-05 12:47:23 +02:00
a1e2111e6d
recursive trait impls 2024-07-03 22:57:27 +02:00
f688dbe225
remove bad supertrait, add remaining AsSample impl 2024-07-03 22:27:30 +02:00
39 changed files with 2189 additions and 224 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
/target
*.lock
bmp/out.*
fonts
test.*

View file

@ -1,24 +1,9 @@
[workspace]
resolver = "2"
members = ["bent", "bent-funny-zone", "bingus", "bong"]
members = ["bingus", "bong"]
package.edition = "2021"
package.license = "Unlicense"
package.description = "databending made easy"
package.authors = ["Breval Ferrari <breee@duck.com>"]
package.repository = "https://codeberg.org/p6nj/bent"
package.keywords = ["databending", "data-bending", "bending", "bend", "art"]
[workspace.dependencies]
fundsp = "0.18.2"
image = "0.25.1"
anyhow = "1.0.86"
bingus = { version = "0.1.0", path = "bingus" }
num-traits = "0.2.19"
dasp_sample = "0.11.0"
rayon = "1.10.0"
dirs = "5.0.1"
rfd = "0.14.1"
derive-new = "0.6.0"
infer = "0.16.0"
indicatif = { version = "0.17.8", features = ["rayon"] }
getset = "0.1.2"

View file

@ -1,5 +1,12 @@
# BENT
> Get bent! Databending made easy.
This crate aims to provide a simple GUI to bend any file to another format without dealing with headers.
Codecs and processors would come as features and be chosen in the settings to recompile the binary.
This project aims to provide a simple GUI to bend any file to another format without dealing with headers.
Codecs and processors would come as features and be chosen in the settings to recompile the binary.
There are three crates in this project :
- bingus, the core library
- bong, the CLI binary
- bent, the GUI

View file

@ -1,24 +0,0 @@
[package]
name = "bent-funny-zone"
version = "0.1.0"
edition.workspace = true
license.workspace = true
description.workspace = true
authors.workspace = true
repository.workspace = true
keywords.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = { workspace = true }
fundsp = { workspace = true }
anyhow = { workspace = true }
bingus = { workspace = true }
dasp_sample = { workspace = true }
rayon = { workspace = true }
dirs = { workspace = true }
rfd = { workspace = true }
derive-new = { workspace = true }
infer = { workspace = true }
indicatif = { workspace = true }

View file

@ -1,131 +0,0 @@
use std::path::PathBuf;
use anyhow::{Context, Result};
use bingus::rawdata::RawData;
use dasp_sample::{Sample, U24};
use derive_new::new;
use dirs::{download_dir, home_dir, picture_dir};
use fundsp::math::uparc;
use image::{io::Reader as ImageReader, ImageFormat, RgbImage};
use indicatif::{ParallelProgressIterator, ProgressStyle};
use rayon::{iter::ParallelIterator, slice::ParallelSlice};
use rfd::FileDialog;
struct Files {
input: PathBuf,
output: PathBuf,
}
#[derive(new)]
struct DataPath {
files: Files,
}
impl Files {
fn try_new(input: PathBuf, output: PathBuf) -> Result<Self> {
Ok(Files {
input: Self::verify_image_file(input, "input")?,
output: Self::verify_image_file(output, "output")?,
})
}
fn verify_image_file(path: PathBuf, label: &'static str) -> Result<PathBuf> {
ImageFormat::from_path(&path).with_context(|| {
format!("your {label} image must have a supported extension from this list:")
+ &ImageFormat::all()
.map(|format| {
format!(
"\n\t{:?} ({})",
format,
format
.extensions_str()
.iter()
.fold(String::new(), |acc, x| acc + &format!(".{x}, "))
.strip_suffix(", ")
.unwrap()
)
})
.reduce(|acc, x| acc + &x)
.unwrap()
})?;
Ok(path)
}
fn prompt() -> Result<Self> {
Files::try_new(
FileDialog::new()
.set_title("Input image")
.set_directory(working_dir())
.add_filter(
"images",
&ImageFormat::all()
.map(ImageFormat::extensions_str)
.flatten()
.collect::<Vec<&'static &'static str>>(),
)
.pick_file()
.context("no input image selected")?,
FileDialog::new()
.set_title("Output image")
.set_directory(working_dir())
.save_file()
.context("no output filename provided")?,
)
}
}
fn main() -> Result<()> {
let dp = DataPath::new(Files::prompt()?);
let img = RawData::<RgbImage>::new(
ImageReader::open(dp.files.input)?
.decode()
.context("can't use this picture")?
.into_rgb8(),
);
let processed = RawData::<RgbImage>::new(
RgbImage::from_raw(
img.width(),
img.height(),
img.par_chunks_exact(3)
.progress_count((img.width() * img.height()).into())
.with_style(ProgressStyle::with_template(
"[{eta}] {bar:40.green/red} {pos}/{len} pixels",
)?)
.map(|px: &[u8]| (px[2] as i32) | ((px[1] as i32) << 8) | ((px[0] as i32) << 16))
.map(U24::new_unchecked)
.map(|x| uparc(x.to_sample()))
.flat_map(|sample: f32| {
let rgb: U24 = sample.to_sample();
let rgo = ((rgb) >> 8.into()) << 8.into();
let roo = ((rgo) >> 16.into()) << 16.into();
[
((roo) >> 16.into()).inner() as u8,
((rgo - roo) >> 8.into()).inner() as u8,
(rgb - rgo).inner() as u8,
]
})
.collect(),
)
.unwrap(),
);
processed.save(dp.files.output)?;
// let bytes: Vec<u8> = (1u8..7).into_iter().collect();
// assert_eq!(
// bytes,
// bytes
// .chunks_exact(3)
// .map(|px: &[u8]| f64::from_ne_bytes(array::from_fn(|i| px[i % px.len()])))
// .flat_map(|px: f64| -> [u8; 3] {
// let px = px.to_ne_bytes();
// array::from_fn(move |i| px[i])
// })
// .collect::<Vec<u8>>()
// );
Ok(())
}
fn working_dir() -> PathBuf {
picture_dir()
.or(download_dir())
.or(home_dir())
.unwrap_or("/".into())
}

View file

@ -15,6 +15,10 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
image = { workspace = true }
dasp_sample = { workspace = true }
bingus = { workspace = true }
fundsp = { workspace = true }
[profile.release]
lto = true

View file

@ -8,12 +8,23 @@
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
</head>
<script>
const download = (content, filename, contentType) => {
if (!contentType) contentType = 'application/octet-stream';
var a = document.createElement('a');
a.setAttribute('visibility', 'hidden');
var blob = new Blob([content], { 'type': contentType });
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
}
const files = () => { return Array.from(document.getElementById("files").files).map(x => x.name); };
</script>
<script type="module">
import init, { greet } from "./pkg/bent.js";
init().then(() => {
document.getElementById("greet-button").onclick = () => {
const name = document.getElementById("name").value;
greet(name);
document.getElementById("verify-button").onclick = () => {
console.log(files());
};
});
</script>
@ -25,10 +36,10 @@
<form onsubmit="return false;">
<p>
<label>Enter your name:</label>
<input type="text" id="name" />
<label>Select some files:</label>
<input type="file" id="files" multiple="true" />
</p>
<button id="greet-button">Greet</button>
<button id="verify-button">Verify</button>
<button type="reset">Reset</button>
</form>
</body>

View file

@ -1,4 +1,8 @@
use wasm_bindgen::prelude::*;
use bingus::rawdata::RawData;
use dasp_sample::U24;
use fundsp::math::uparc;
use image::{ImageReader, RgbImage};
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
#[allow(unused_macros)]
#[macro_use]
pub mod macros;
@ -8,3 +12,49 @@ pub mod js;
pub fn greet(name: &str) {
println!("Hello, {name}!");
}
#[wasm_bindgen(getter_with_clone)]
pub struct ImageFile {
pub name: String,
pub data: Vec<u8>,
}
#[wasm_bindgen]
pub fn process_files(files: Vec<ImageFile>) {
files.into_iter().for_each(|file| {
let img = RawData::<RgbImage>::new(
ImageReader::open(file.name)
.unwrap()
.decode()
.unwrap()
.into_rgb8(),
);
img.par_pixels_mut().for_each(|px| {
let sample = uparc(
U24::new_unchecked((px[2] as i32) | ((px[1] as i32) << 8) | ((px[0] as i32) << 16))
.to_sample(),
);
});
let processed = RawData::<RgbImage>::new(
RgbImage::from_raw(
img.width(),
img.height(),
img.par_chunks_exact(3)
.map(|px: &[u8]| {
(px[2] as i32) | ((px[1] as i32) << 8) | ((px[0] as i32) << 16)
})
.map(U24::new_unchecked)
.map(|x| uparc(x.to_sample()))
.flat_map(|sample: f32| {
let rgb: U24 = sample.to_sample();
let rgo = ((rgb) >> 8.into()) << 8.into();
let roo = ((rgo) >> 16.into()) << 16.into();
vec![roo, rgo, rgb]
})
.collect::<Vec<u8>>(),
)
.unwrap(),
);
let _ = processed;
});
}

View file

@ -1,6 +1,6 @@
[package]
name = "bingus"
version = "0.1.2"
version = "0.6.0"
edition.workspace = true
license.workspace = true
description.workspace = true
@ -10,4 +10,54 @@ keywords.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
derive-new = { workspace = true }
image = { version = "0.25", optional = true }
num = { version = "0", optional = true }
rayon = { version = "1", optional = true }
infer = "0"
thiserror = "2"
derive-new = "0"
derive_wrapper = "0.1"
symphonia = { version = "0.5", features = ["all"], optional = true }
printpdf = { version = "0.8.2", features = [
"bmp",
"dds",
"gif",
"hdr",
"ico",
"jpeg",
"png",
"pnm",
"tga",
"tiff",
"js-sys",
"webp",
], optional = true }
shiva = { version = "1.4.9", optional = true }
anyhow = { version = "1.0", optional = true }
font-kit = { version = "0.14.2", features = [
"loader-freetype-default",
], optional = true }
cfg-if = "1.0.0"
[dev-dependencies]
project-root = "0"
pretty_assertions = "1"
[features]
default = ["all-formats"]
all-formats = ["binary", "documents", "fonts", "pictures", "music", "text"]
binary = []
documents = ["shiva", "printpdf"]
fonts = ["font-kit"]
pictures = ["image"]
music = ["symphonia", "dep:num"]
text = []
shiva = ["dep:shiva", "dep:anyhow"]
printpdf = ["dep:printpdf"]
font-kit = ["dep:font-kit"]
image = ["dep:image", "dep:num"]
symphonia = ["dep:symphonia"]
rayon = ["image?/rayon", "printpdf?/rayon", "dep:rayon"]

2
bingus/src/bin.rs Normal file
View file

@ -0,0 +1,2 @@
#[path = "bin_/bytes.rs"]
mod bytes;

40
bingus/src/bin_/bytes.rs Normal file
View file

@ -0,0 +1,40 @@
use std::{borrow::Cow, convert::Infallible};
use cfg_if::cfg_if;
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use crate::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
impl IntoDataBytes for Bytes {
fn into_data_bytes(self) -> Bytes {
self
}
}
impl TryFromDataBytes for Bytes {
type Error = Infallible;
type Format = ();
fn try_from_data_bytes(
bytes: Bytes,
_: Self::Format,
_: crate::Crop,
) -> Result<Self, Self::Error> {
Ok(bytes)
}
}
impl Bendable for Bytes {
type Unit = u8;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
cfg_if! {
if #[cfg(feature = "rayon")] {
let iter = self.par_iter_mut();
} else {
let iter = self.iter_mut();
}
}
iter.for_each(|e| *e = f(Cow::Borrowed(e)));
self
}
}

6
bingus/src/doc.rs Normal file
View file

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

122
bingus/src/doc/pdf.rs Normal file
View file

@ -0,0 +1,122 @@
use std::borrow::Cow;
use cfg_if::cfg_if;
use printpdf::{
Op, PdfDocument, PdfPage, PdfParseErrorSeverity, PdfParseOptions, PdfSaveOptions, PdfWarnMsg,
};
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{Bendable, IntoDataBytes, TryFromDataBytes};
fn clean_up_warnings(warnings: &mut Vec<PdfWarnMsg>) {
warnings.retain(|warning| {
warning.severity == PdfParseErrorSeverity::Warning
|| warning.severity == PdfParseErrorSeverity::Error
});
}
impl TryFromDataBytes for PdfDocument {
type Error = String;
type Format = ();
fn try_from_data_bytes(
bytes: crate::Bytes,
_format: Self::Format,
_crop: crate::Crop,
) -> Result<Self, Self::Error> {
let mut warnings = Vec::new();
let result = PdfDocument::parse(
&bytes,
&PdfParseOptions {
fail_on_error: false,
},
&mut warnings,
);
clean_up_warnings(&mut warnings);
for warning in warnings {
println!("Warning: {:#?}", warning);
}
result
}
}
impl IntoDataBytes for PdfDocument {
fn into_data_bytes(self) -> crate::Bytes {
let mut warnings = Vec::new();
let result = self.save(
&PdfSaveOptions {
image_optimization: None,
..Default::default()
},
&mut warnings,
);
clean_up_warnings(&mut warnings);
for warning in warnings {
println!("Warning: {:#?}", warning);
}
result
}
}
impl Bendable for PdfDocument {
type Unit = Op;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self {
let PdfDocument {
metadata,
resources,
bookmarks,
pages,
} = self;
PdfDocument {
pages: pages
.into_iter()
.map(|page| PdfPage {
ops: {
cfg_if! {
if #[cfg(feature = "rayon")] {
page.ops.into_par_iter()
} else {
page.ops.into_iter()
}
}
}
.map(|op| f(Cow::Owned(op)))
.collect::<Vec<Op>>(),
..page
})
.collect(),
metadata,
resources,
bookmarks,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn map_integrity() {
let mut warnings = Vec::new();
let original = PdfDocument::parse(
include_bytes!("../../../testing material/doc/pdf/basic-text.pdf"),
&PdfParseOptions {
fail_on_error: true,
},
&mut warnings,
)
.unwrap();
clean_up_warnings(&mut warnings);
for warning in warnings {
println!("Warning: {:#?}", warning);
}
assert_eq!(
format!("{:?}", original),
format!("{:?}", original.clone().map(|u| u.into_owned()))
)
}
}

62
bingus/src/doc/shiva.rs Normal file
View file

@ -0,0 +1,62 @@
use std::borrow::Cow;
use derive_new::new;
pub use shiva::core::DocumentType;
use shiva::core::{bytes::Bytes, Document, Element};
use crate::{Bendable, IntoDataBytes, TryFromDataBytes};
#[derive(new)]
pub struct ShivaDocument {
document: Document,
output_format: DocumentType,
}
#[derive(new)]
pub struct ShivaFormat {
input_format: DocumentType,
output_format: DocumentType,
}
impl TryFromDataBytes for ShivaDocument {
type Error = anyhow::Error;
type Format = ShivaFormat;
fn try_from_data_bytes(
bytes: crate::Bytes,
format: Self::Format,
_crop: crate::Crop,
) -> Result<Self, Self::Error> {
Ok(ShivaDocument::new(
Document::parse(&Bytes::from(bytes), format.input_format)?,
format.output_format,
))
}
}
impl IntoDataBytes for ShivaDocument {
fn into_data_bytes(self) -> crate::Bytes {
self.document
.generate(self.output_format)
.expect("can't crash here! so close!")
.to_vec()
}
}
impl Bendable for ShivaDocument {
type Unit = Element;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self {
ShivaDocument::new(
Document::new(
self.document
.get_all_elements()
.into_iter()
.map(Cow::Borrowed)
.map(f)
.collect(),
),
self.output_format,
)
}
}

2
bingus/src/fnt.rs Normal file
View file

@ -0,0 +1,2 @@
mod fontkit;
pub use fontkit::Font;

49
bingus/src/fnt/fontkit.rs Normal file
View file

@ -0,0 +1,49 @@
use cfg_if::cfg_if;
use font_kit::error::FontLoadingError;
pub use font_kit::font::Font;
use std::{borrow::Cow, sync::Arc};
use crate::{Bendable, IntoDataBytes, TryFromDataBytes};
impl TryFromDataBytes for Font {
type Error = FontLoadingError;
type Format = ();
fn try_from_data_bytes(
bytes: crate::Bytes,
_format: Self::Format,
_crop: crate::Crop,
) -> Result<Self, Self::Error> {
Self::from_bytes(Arc::new(bytes), 0)
}
}
impl IntoDataBytes for Font {
fn into_data_bytes(self) -> crate::Bytes {
self.copy_font_data()
.map(|v| v.as_ref().clone())
.unwrap_or_default()
}
}
impl Bendable for Font {
type Unit = u8;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self {
Self::try_from_data_bytes(
{
let bytes = self.into_data_bytes();
cfg_if! {
if #[cfg(feature = "binary")] {
bytes.map(f)
} else {
bytes.into_iter().map(|e| f(Cow::Owned(e))).collect()
}
}
},
(),
Default::default(),
)
.expect("coudn't get font back from bytes after map")
}
}

3
bingus/src/img.rs Normal file
View file

@ -0,0 +1,3 @@
mod image;
pub use image::ImageBuffer as Image;
pub use image::*;

383
bingus/src/img/image.rs Normal file
View file

@ -0,0 +1,383 @@
use std::{
borrow::Cow,
convert::Infallible,
iter::once,
ops::{Add, Deref, DerefMut, Div, Mul, Sub},
};
use cfg_if::cfg_if;
pub use image::*;
use num::{
traits::{FromBytes, ToBytes},
Zero,
};
#[cfg(feature = "rayon")]
use rayon::iter::ParallelIterator;
use thiserror::Error;
use crate::{Bendable, FromDataBytes, IntoDataBytes, TryFromDataBytes};
impl<P: Pixel> IntoDataBytes for ImageBuffer<P, Vec<P::Subpixel>>
where
Vec<P::Subpixel>: Deref<Target = [P::Subpixel]>,
P::Subpixel: ToBytes,
{
fn into_data_bytes(self) -> crate::Bytes {
self.iter()
.flat_map(|subpixel| subpixel.to_ne_bytes().as_ref().to_vec())
.collect()
}
}
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Dimensions {
pub width: u32,
pub height: u32,
}
impl Div<Dimensions> for Dimensions {
type Output = Dimensions;
fn div(self, rhs: Dimensions) -> Self::Output {
Dimensions {
width: self.width / rhs.width,
height: self.height / rhs.height,
}
}
}
impl Div<u32> for Dimensions {
type Output = Dimensions;
fn div(self, rhs: u32) -> Self::Output {
Dimensions {
width: self.width / rhs,
height: self.height / rhs,
}
}
}
impl Mul<Dimensions> for Dimensions {
type Output = Dimensions;
fn mul(self, rhs: Dimensions) -> Self::Output {
Dimensions {
width: self.width * rhs.width,
height: self.height * rhs.height,
}
}
}
impl Mul<u32> for Dimensions {
type Output = Dimensions;
fn mul(self, rhs: u32) -> Self::Output {
Dimensions {
width: self.width * rhs,
height: self.height * rhs,
}
}
}
impl Add<Dimensions> for Dimensions {
type Output = Dimensions;
fn add(self, rhs: Dimensions) -> Self::Output {
Dimensions {
width: self.width + rhs.width,
height: self.height + rhs.height,
}
}
}
impl Add<u32> for Dimensions {
type Output = Dimensions;
fn add(self, rhs: u32) -> Self::Output {
Dimensions {
width: self.width + rhs,
height: self.height + rhs,
}
}
}
impl Sub<Dimensions> for Dimensions {
type Output = Dimensions;
fn sub(self, rhs: Dimensions) -> Self::Output {
Dimensions {
width: self.width - rhs.width,
height: self.height - rhs.height,
}
}
}
impl Sub<u32> for Dimensions {
type Output = Dimensions;
fn sub(self, rhs: u32) -> Self::Output {
Dimensions {
width: self.width - rhs,
height: self.height - rhs,
}
}
}
impl<P: Pixel> TryFromDataBytes for ImageBuffer<P, Vec<P::Subpixel>>
where
Vec<P::Subpixel>: Deref<Target = [P::Subpixel]>,
P::Subpixel: ToBytes + FromBytes + Zero,
<P::Subpixel as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8]>,
{
type Error = Infallible;
type Format = Dimensions;
fn try_from_data_bytes(
bytes: crate::Bytes,
format: Self::Format,
crop: crate::Crop,
) -> Result<Self, Self::Error> {
ImageBuffer::from_raw(
format.width,
format.height,
match crop {
// TODO: separate outer crop from inner crop
crate::Crop::End => bytes
.chunks_exact(P::Subpixel::zero().to_ne_bytes().as_ref().len())
.map(|p| {
P::Subpixel::from_ne_bytes(
&match <P::Subpixel as FromBytes>::Bytes::try_from(p) {
Ok(v) => v,
Err(_) => unreachable!("you messed up chunk size!"),
},
)
})
.chain(once(P::Subpixel::zero()).cycle())
.take(
format.width as usize * format.height as usize * P::CHANNEL_COUNT as usize,
)
.collect::<Vec<P::Subpixel>>(),
crate::Crop::Start => bytes
.rchunks_exact(P::Subpixel::zero().to_ne_bytes().as_ref().len())
.map(|p| {
P::Subpixel::from_ne_bytes(
&match <P::Subpixel as FromBytes>::Bytes::try_from(p) {
Ok(v) => v,
Err(_) => unreachable!("you messed up chunk size!"),
},
)
})
.chain(once(P::Subpixel::zero()).cycle())
.take(
format.width as usize * format.height as usize * P::CHANNEL_COUNT as usize,
)
.collect::<Vec<P::Subpixel>>(),
},
)
.ok_or_else(|| unreachable!())
}
}
impl<P: Pixel> Bendable for ImageBuffer<P, Vec<P::Subpixel>>
where
Vec<P::Subpixel>: Deref<Target = [P::Subpixel]> + DerefMut,
P::Subpixel: ToBytes + FromBytes + Send + Sync,
<P::Subpixel as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8]>,
P: Send + Sync,
{
type Unit = P;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
cfg_if! {
if #[cfg(feature = "rayon")] {
let iter = self.par_pixels_mut();
} else {
let iter = self.pixels_mut();
}
}
iter.for_each(|p| *p = f(Cow::Borrowed(p)));
self
}
}
impl IntoDataBytes for DynamicImage {
fn into_data_bytes(self) -> crate::Bytes {
self.into_bytes()
}
}
#[derive(Debug, Error)]
#[error("this color type is not supported yet... sorry")]
pub struct UnsupportedColorType;
impl TryFromDataBytes for DynamicImage {
type Format = (Dimensions, ColorType);
type Error = UnsupportedColorType;
fn try_from_data_bytes(
bytes: crate::Bytes,
format: Self::Format,
crop: crate::Crop,
) -> Result<Self, Self::Error> {
match format.1 {
ColorType::L8 => Ok(DynamicImage::ImageLuma8(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::La8 => Ok(DynamicImage::ImageLumaA8(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgb8 => Ok(DynamicImage::ImageRgb8(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgba8 => Ok(DynamicImage::ImageRgba8(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::L16 => Ok(DynamicImage::ImageLuma16(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::La16 => Ok(DynamicImage::ImageLumaA16(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgb16 => Ok(DynamicImage::ImageRgb16(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgba16 => Ok(DynamicImage::ImageRgba16(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgb32F => Ok(DynamicImage::ImageRgb32F(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
ColorType::Rgba32F => Ok(DynamicImage::ImageRgba32F(ImageBuffer::from_data_bytes(
bytes, format.0, crop,
))),
_ => Err(UnsupportedColorType),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(test)]
mod ser_de {
use super::super::{
Dimensions, ImageBuffer, IntoDataBytes, Rgb, Rgb32FImage, RgbImage, RgbaImage,
TryFromDataBytes,
};
#[test]
fn empty() {
let image = RgbImage::new(0, 0);
assert_eq!(
Ok(image.clone()),
RgbImage::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 0,
height: 0
},
Default::default()
)
)
}
#[test]
fn simple() {
let image = RgbImage::from_raw(3, 1, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap();
assert_eq!(
Ok(image.clone()),
RgbImage::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 3,
height: 1
},
Default::default()
)
)
}
#[test]
fn rgba() {
let image =
RgbaImage::from_raw(3, 1, vec![1, 2, 3, 0, 4, 5, 6, 1, 7, 8, 9, 2]).unwrap();
assert_eq!(
Ok(image.clone()),
RgbaImage::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 3,
height: 1
},
Default::default()
)
)
}
#[test]
fn rgb_u16() {
let image = ImageBuffer::<Rgb<u16>, Vec<u16>>::from_raw(
3,
1,
vec![1, 2, 3, 254, 255, 256, 307, 308, 309],
)
.unwrap();
assert_eq!(
Ok(image.clone()),
ImageBuffer::<Rgb<u16>, Vec<u16>>::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 3,
height: 1
},
Default::default()
)
)
}
#[test]
fn rgb_signed() {
let image = ImageBuffer::<Rgb<i16>, Vec<i16>>::from_raw(
3,
1,
vec![1, 2, 3, 254, 255, 256, -307, 308, 309],
)
.unwrap();
assert_eq!(
Ok(image.clone()),
ImageBuffer::<Rgb<i16>, Vec<i16>>::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 3,
height: 1
},
Default::default()
)
)
}
#[test]
fn rgb_f32() {
let image = Rgb32FImage::from_raw(
3,
1,
vec![1.0, 2.0, 3.0, 254.0, 255.0, 256.1, 307.0, 308.0, 309.0],
)
.unwrap();
assert_eq!(
Ok(image.clone()),
Rgb32FImage::try_from_data_bytes(
image.into_data_bytes(),
Dimensions {
width: 3,
height: 1
},
Default::default()
)
)
}
}
#[cfg(test)]
mod effects {
use crate::Bendable;
use super::super::{Pixel, RgbImage};
#[test]
fn fill_with_funny_number() {
let image = RgbImage::new(8, 16);
let new_image = image.clone().map(|p| p.map(|_channel| 42u8));
assert_ne!(image.clone(), new_image);
assert_eq!(42, new_image.get_pixel(1, 2).channels()[1])
}
}
}

View file

@ -1,2 +1,280 @@
#![forbid(unused_crate_dependencies)]
pub mod rawdata;
#[cfg(feature = "binary")]
pub mod bin;
#[cfg(feature = "documents")]
pub mod doc;
#[cfg(feature = "fonts")]
pub mod fnt;
#[cfg(feature = "pictures")]
pub mod img;
#[cfg(feature = "music")]
pub mod snd;
#[cfg(feature = "text")]
pub mod txt;
pub type Bytes = Vec<u8>;
pub mod dynamic {
#[cfg(feature = "text")]
use std::string::FromUtf8Error;
use std::{
borrow::Cow,
fs::File,
io::{self, Cursor, Read, Write},
path::Path,
};
#[cfg(feature = "documents")]
use super::doc::ShivaDocument;
#[cfg(feature = "fonts")]
use super::fnt::Font as FontKitFont;
#[cfg(feature = "pictures")]
use super::img::{self, DynamicImage};
#[cfg(feature = "music")]
use super::snd::{self, Audio};
#[cfg(feature = "text")]
use super::txt::Text;
use super::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
#[cfg(not(feature = "text"))]
use std::marker::PhantomData;
use cfg_if::cfg_if;
#[cfg(feature = "fonts")]
use font_kit::error::FontLoadingError;
pub use infer::*;
#[cfg(feature = "documents")]
use printpdf::PdfDocument;
#[cfg(feature = "documents")]
use shiva::core::{bytes, Document, DocumentType};
use thiserror::Error;
pub enum DynamicBendable<'a> {
#[cfg(feature = "pictures")]
Image(DynamicImage),
#[cfg(feature = "binary")]
Binary(Bytes),
#[cfg(feature = "music")]
Sound(Audio),
#[cfg(feature = "text")]
Text(Text<'a>),
#[cfg(not(feature = "text"))]
Phantom(PhantomData<&'a ()>),
#[cfg(feature = "documents")]
Doc(ShivaDocument),
#[cfg(feature = "documents")]
Archive(PdfDocument),
Meta,
#[cfg(feature = "fonts")]
Font(FontKitFont),
}
#[cfg(feature = "shiva")]
#[derive(Debug, Error)]
#[error("extension is unknown by Shiva")]
pub struct ShivaUnknownExtensionError;
#[cfg(feature = "shiva")]
#[derive(Debug, Error)]
pub enum ShivaError {
#[error("{0}")]
UnknownExtension(#[from] ShivaUnknownExtensionError),
#[error("{0}")]
Anyhow(#[from] anyhow::Error),
}
#[derive(Debug, Error)]
pub enum OpenError {
#[error("io: {0}")]
Io(#[from] io::Error),
#[cfg(feature = "pictures")]
#[error("image: {0}")]
Image(#[from] img::ImageError),
#[cfg(feature = "music")]
#[error("audio: {0}")]
Audio(#[from] snd::AudioOpenError),
#[cfg(feature = "documents")]
#[error("pdf: {0}")]
Pdf(String),
#[cfg(feature = "text")]
#[error("text: {0}")]
Text(#[from] FromUtf8Error),
#[cfg(feature = "documents")]
#[error("document: {0}")]
Document(#[from] ShivaError),
#[cfg(feature = "fonts")]
#[error("font: {0:?}")]
Font(#[from] FontLoadingError),
}
impl TryFromDataBytes for File {
type Error = io::Error;
type Format = Box<dyn AsRef<Path>>;
fn try_from_data_bytes(
bytes: Bytes,
format: Self::Format,
_crop: crate::Crop,
) -> Result<Self, Self::Error>
where
Self: Sized,
{
let mut file = File::create(format.as_ref())?;
file.write_all(&bytes)?;
Ok(file)
}
}
impl IntoDataBytes for File {
fn into_data_bytes(self) -> Bytes {
self.bytes().flatten().collect() // !! will return an empty vec if it can't read the file!
}
}
impl Bendable for File {
type Unit = u8;
/// /!\ may panic with io errors /!\
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
let mut bytes = Vec::new();
self.read_to_end(&mut bytes).expect("couldn't read file");
cfg_if! {
if #[cfg(feature = "binary")] {
self.write_all(&bytes.map(f)).expect("couldn't write file");
} else {
self.write_all(&bytes.into_iter().map(|e| f(Cow::Owned(e))).collect::<Vec<u8>>()).expect("couldn't write file");
}
}
self
}
}
pub type DynamicResult = Result<Option<DynamicBendable<'static>>, OpenError>;
pub fn guess(t: Option<infer::Type>, bytes: Bytes) -> DynamicResult {
use MatcherType::*;
t.map(|t| (t.matcher_type(), t.extension()))
.map(
|(matcher, extension)| -> Result<DynamicBendable, OpenError> {
Ok(match matcher {
#[cfg(feature = "pictures")]
Image => DynamicBendable::Image(img::load_from_memory(&bytes)?),
#[cfg(feature = "music")]
Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?),
#[cfg(feature = "documents")]
Archive if extension == "pdf" => DynamicBendable::Archive(
PdfDocument::try_from_data_bytes(
bytes,
(),
Default::default(),
)
.map_err(OpenError::Pdf)?,
),
#[cfg(feature = "documents")]
Archive | Doc => {
let document_type = DocumentType::from_extension(extension)
.ok_or(ShivaUnknownExtensionError)
.map_err(ShivaError::UnknownExtension)?;
DynamicBendable::Doc(ShivaDocument::new(
Document::parse(
&bytes::Bytes::from(bytes),
document_type,
)
.map_err(ShivaError::Anyhow)?,
document_type,
))
}
#[cfg(feature = "fonts")]
Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes(
bytes,
(),
Default::default(),
)?),
#[cfg(feature = "text")]
Text => DynamicBendable::Text(crate::txt::Text::try_from_data_bytes(
bytes,
(),
Default::default(),
).unwrap()),
#[cfg(feature = "binary")]
_ => DynamicBendable::Binary(bytes),
#[cfg(not(feature = "binary"))]
_ => unimplemented!("no format reader available to open this thing (turn on the 'binary' feature to default to binary data)"),
})
},
)
.transpose()
}
pub fn open_file(path: impl AsRef<Path>) -> DynamicResult {
open(&mut File::open(path)?)
}
pub fn open(source: &mut impl Read) -> DynamicResult {
let contents = {
let mut c = Vec::new();
source.read_to_end(&mut c)?;
c
};
guess(infer::get(&contents), contents)
}
}
use std::{borrow::Cow, convert::Infallible};
pub trait Bendable: TryFromDataBytes + IntoDataBytes {
type Unit;
fn bend_into<T: TryFromDataBytes>(
self,
format: <T as TryFromDataBytes>::Format,
crop: Crop,
) -> Result<T, <T as TryFromDataBytes>::Error> {
T::try_from_data_bytes(self.into_data_bytes(), format, crop)
}
fn bend_from<T: IntoDataBytes>(
b: T,
format: <Self as TryFromDataBytes>::Format,
crop: Crop,
) -> Result<Self, <Self as TryFromDataBytes>::Error> {
Self::try_from_data_bytes(b.into_data_bytes(), format, crop)
}
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self;
}
pub trait IntoDataBytes: Sized {
fn into_data_bytes(self) -> Bytes;
}
#[derive(Default)]
pub enum Crop {
Start,
#[default]
End,
}
pub trait TryFromDataBytes {
type Error;
type Format;
fn try_from_data_bytes(
bytes: Bytes,
format: Self::Format,
crop: Crop,
) -> Result<Self, Self::Error>
where
Self: Sized;
}
pub trait FromDataBytes {
type Format;
fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self
where
Self: Sized;
}
impl<F, T> FromDataBytes for T
where
T: TryFromDataBytes<Error = Infallible, Format = F>,
{
type Format = <T as TryFromDataBytes>::Format;
fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self {
T::try_from_data_bytes(bytes, format, crop).unwrap_or_else(|_| unreachable!())
}
}

View file

@ -1,36 +0,0 @@
use std::ops::Deref;
use derive_new::new;
pub type Bytes = [u8];
#[derive(new)]
pub struct RawData<D: Deref<Target = Bytes>>(D);
impl<D> From<RawData<D>> for Vec<u8>
where
D: Deref<Target = Bytes>,
{
fn from(value: RawData<D>) -> Self {
value.to_owned()
}
}
impl<D> Clone for RawData<D>
where
D: Clone + Deref<Target = Bytes>,
{
fn clone(&self) -> Self {
Self(self.deref().clone())
}
}
impl<D> Deref for RawData<D>
where
D: Deref<Target = Bytes>,
{
type Target = D;
fn deref(&self) -> &Self::Target {
&self.0
}
}

5
bingus/src/snd.rs Normal file
View file

@ -0,0 +1,5 @@
pub use symphonia::core::*;
mod raw;
pub use raw::*;
mod simphonia;
pub use simphonia::*;

103
bingus/src/snd/raw.rs Normal file
View file

@ -0,0 +1,103 @@
use std::{borrow::Cow, convert::Infallible};
use super::sample::Sample;
use cfg_if::cfg_if;
use derive_wrapper::{AsRef, From};
use num::{
traits::{FromBytes, ToBytes},
Zero,
};
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use crate::{Bendable, IntoDataBytes, TryFromDataBytes};
#[derive(From, AsRef)]
pub struct RawSamples<T>(Vec<T>)
where
T: Sample;
impl<T: Sample> RawSamples<T> {
pub fn into_inner(self) -> Vec<T> {
self.0
}
}
impl<T> IntoDataBytes for RawSamples<T>
where
T: Sample + ToBytes,
{
fn into_data_bytes(self) -> crate::Bytes {
self.as_ref()
.iter()
.flat_map(|subpixel| subpixel.to_ne_bytes().as_ref().to_vec())
.collect()
}
}
impl<T> TryFromDataBytes for RawSamples<T>
where
T: Sample + FromBytes + ToBytes + Zero,
<T as FromBytes>::Bytes: Sized + for<'a> TryFrom<&'a [u8]>,
{
type Error = Infallible;
type Format = ();
fn try_from_data_bytes(
bytes: crate::Bytes,
_format: Self::Format,
crop: crate::Crop,
) -> Result<Self, Self::Error> {
Ok(match crop {
crate::Crop::End => bytes
.chunks_exact(T::zero().to_ne_bytes().as_ref().len())
.map(|p| {
T::from_ne_bytes(&match <T as FromBytes>::Bytes::try_from(p) {
Ok(v) => v,
Err(_) => unreachable!("you messed up chunk size!"),
})
})
.collect::<Vec<T>>(),
crate::Crop::Start => bytes
.rchunks_exact(T::zero().to_ne_bytes().as_ref().len())
.map(|p| {
T::from_ne_bytes(&match <T as FromBytes>::Bytes::try_from(p) {
Ok(v) => v,
Err(_) => unreachable!("you messed up chunk size!"),
})
})
.collect::<Vec<T>>(),
}
.into())
}
}
cfg_if! {
if #[cfg(feature = "rayon")] {
impl<T> Bendable for RawSamples<T>
where
T: Sample + FromBytes + ToBytes + Zero + Send,
<T as FromBytes>::Bytes: Sized + for<'a> TryFrom<&'a [u8]>,
for<'a> Vec<T>: IntoParallelRefMutIterator<'a, Item = &'a mut T>,
{
type Unit = T;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
self.0.par_iter_mut().for_each(|e| *e = f(Cow::Borrowed(e)));
self
}
fn format() -> crate::dynamic::Format {
crate::Format::Sound
}
}
} else {
impl<T> Bendable for RawSamples<T>
where
T: Sample + FromBytes + ToBytes + Zero + Send,
<T as FromBytes>::Bytes: Sized + for<'a> TryFrom<&'a [u8]>,
{
type Unit = T;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
self.0.iter_mut().for_each(|e| *e = f(Cow::Borrowed(e)));
self
}
}
}}

191
bingus/src/snd/simphonia.rs Normal file
View file

@ -0,0 +1,191 @@
use std::io::{self, Read};
use derive_new::new;
use symphonia::{
core::{
audio::Signal,
codecs::{Decoder, CODEC_TYPE_NULL},
conv::FromSample,
formats::FormatReader,
io::{MediaSource, MediaSourceStream},
probe::Hint,
sample::{i24, u24},
},
default,
};
use thiserror::Error;
use crate::IntoDataBytes;
use super::{sample::Sample, RawSamples};
/// Audio, unlike DynamicImage, isn't directly bendable
/// because its underlying data format isn't decided automatically
/// so you have to attribute it yourself by turning it into a [RawSamples] struct
/// with your chosen sample format.
#[derive(new)]
pub struct Audio {
reader: Box<dyn FormatReader>,
decoder: Box<dyn Decoder>,
}
#[derive(Debug, Error)]
pub enum AudioOpenError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("symphonia can't open this file: {0}")]
Symphonia(#[from] symphonia::core::errors::Error),
}
impl PartialEq for AudioOpenError {
fn eq(&self, other: &Self) -> bool {
match self {
AudioOpenError::Io(_) => matches!(other, AudioOpenError::Io(_)),
AudioOpenError::Symphonia(_) => matches!(other, AudioOpenError::Symphonia(_)),
}
}
}
impl Audio {
pub fn open(
source: impl MediaSource + 'static,
extension: Option<&str>,
) -> Result<Audio, AudioOpenError> {
let registry = default::get_codecs();
let probe = default::get_probe();
let mss = MediaSourceStream::new(Box::new(source), Default::default());
let reader = probe
.format(
&{
let mut hint = Hint::new();
if let Some(e) = extension {
hint.with_extension(e);
}
hint
},
mss,
&Default::default(),
&Default::default(),
)?
.format;
let decoder = registry.make(
reader
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.map(|t| &t.codec_params)
.unwrap_or(&Default::default()),
&Default::default(),
)?;
Ok(Audio::new(reader, decoder))
}
}
impl IntoDataBytes for Audio {
fn into_data_bytes(self) -> crate::Bytes {
self.reader.into_inner().bytes().flatten().collect()
}
}
macro_rules! dynamic_map(
($dynimage: expr, $image: pat => $action: expr) => ({
match $dynimage {
symphonia::core::audio::AudioBufferRef::U8($image) => symphonia::core::audio::AudioBufferRef::U8($action),
symphonia::core::audio::AudioBufferRef::U16($image) => symphonia::core::audio::AudioBufferRef::U16($action),
symphonia::core::audio::AudioBufferRef::U24($image) => symphonia::core::audio::AudioBufferRef::U24($action),
symphonia::core::audio::AudioBufferRef::U32($image) => symphonia::core::audio::AudioBufferRef::U32($action),
symphonia::core::audio::AudioBufferRef::S8($image) => symphonia::core::audio::AudioBufferRef::S8($action),
symphonia::core::audio::AudioBufferRef::S16($image) => symphonia::core::audio::AudioBufferRef::S16($action),
symphonia::core::audio::AudioBufferRef::S24($image) => symphonia::core::audio::AudioBufferRef::S24($action),
symphonia::core::audio::AudioBufferRef::S32($image) => symphonia::core::audio::AudioBufferRef::S32($action),
symphonia::core::audio::AudioBufferRef::F32($image) => symphonia::core::audio::AudioBufferRef::F32($action),
symphonia::core::audio::AudioBufferRef::F64($image) => symphonia::core::audio::AudioBufferRef::F64($action),
}
});
($dynimage: expr, $image:pat_param, $action: expr) => (
match $dynimage {
symphonia::core::audio::AudioBufferRef::U8($image) => $action,
symphonia::core::audio::AudioBufferRef::U16($image) => $action,
symphonia::core::audio::AudioBufferRef::U24($image) => $action,
symphonia::core::audio::AudioBufferRef::U32($image) => $action,
symphonia::core::audio::AudioBufferRef::S8($image) => $action,
symphonia::core::audio::AudioBufferRef::S16($image) => $action,
symphonia::core::audio::AudioBufferRef::S24($image) => $action,
symphonia::core::audio::AudioBufferRef::S32($image) => $action,
symphonia::core::audio::AudioBufferRef::F32($image) => $action,
symphonia::core::audio::AudioBufferRef::F64($image) => $action,
}
);
);
impl<S> From<Audio> for RawSamples<S>
where
S: Sample
+ FromSample<u8>
+ FromSample<u16>
+ FromSample<u24>
+ FromSample<u32>
+ FromSample<i8>
+ FromSample<i16>
+ FromSample<i24>
+ FromSample<i32>
+ FromSample<f32>
+ FromSample<f64>,
{
fn from(mut value: Audio) -> Self {
RawSamples::from({
let mut result = Vec::new();
while let Ok(packet) = value.reader.next_packet() {
if let Ok(src) = value.decoder.decode(&packet) {
dynamic_map!(
src,
ref audio_buffer,
result.append(
&mut (0usize..src.spec().channels.count())
.flat_map(|ch| audio_buffer
.chan(ch)
.iter()
.map(|s| S::from_sample(*s))
.collect::<Vec<S>>())
.collect::<Vec<S>>()
)
)
}
}
result
})
}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use project_root::get_project_root;
use crate::IntoDataBytes;
use super::Audio;
#[test]
fn open_sample_file() {
let original =
&include_bytes!("../../../testing material/sound/sample-3s.mp3")[52079 - 51826..];
let path = get_project_root()
.expect("can't find project root!")
.join("testing material")
.join("sound")
.join("sample-3s.mp3");
let result = Audio::open(
File::open(&path).expect("can't find test file!"),
path.extension().and_then(|s| s.to_str()),
)
.map(|audio| audio.into_data_bytes());
dbg!(original.len());
if let Ok(ref r) = result {
dbg!(r.len());
}
assert_eq!(Ok(original), result.as_ref().map(Vec::as_slice));
}
}

2
bingus/src/txt.rs Normal file
View file

@ -0,0 +1,2 @@
mod bare;
pub use bare::*;

34
bingus/src/txt/bare.rs Normal file
View file

@ -0,0 +1,34 @@
use std::{borrow::Cow, convert::Infallible};
use crate::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
pub type Text<'a> = Cow<'a, str>;
impl TryFromDataBytes for Text<'_> {
type Error = Infallible;
type Format = ();
fn try_from_data_bytes(
bytes: Bytes,
_format: Self::Format,
_crop: crate::Crop,
) -> Result<Self, Self::Error> {
Ok(String::from_utf8_lossy(&bytes).into_owned().into())
}
}
impl IntoDataBytes for Text<'_> {
fn into_data_bytes(self) -> Bytes {
self.as_bytes().to_vec()
}
}
impl Bendable for Text<'_> {
type Unit = char;
fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self {
self.chars()
.map(|c| f(Cow::Owned(c)))
.collect::<String>()
.into()
}
}

View file

@ -1,14 +1,23 @@
[package]
name = "bong"
version = "0.1.0"
version = "0.2.0"
edition.workspace = true
license.workspace = true
description.workspace = true
authors.workspace = true
repository.workspace = true
keywords.workspace = true
dependencies.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
bingus = "0.6"
strum = { version = "0", features = ["derive"] }
derive-new = "0"
clap = { version = "4.5.40", features = ["derive"] }
derive_wrapper = "0.1.7"
unwrap-infallible = "0.1"
mp3lame-encoder = { version = "0.2", features = ["std"] }
hound = "3.5.1"
flacenc = "0.4.0"

213
bong/src/cli.rs Normal file
View file

@ -0,0 +1,213 @@
use std::{
convert::Infallible,
fs::File,
io::{self, stdin, stdout, Read},
path::PathBuf,
str::FromStr,
};
use anyhow::{anyhow, Context};
use bingus::doc::DocumentType;
use clap::Parser;
use derive_new::new;
use strum::{AsRefStr, Display, EnumString};
#[derive(new, Parser)]
#[clap(version, author, about)]
/// CLI from the Bent project
pub(crate) struct Cli {
/// Input file or standard input ("-").
pub(crate) input: FileOrStd<Stdin>,
// /// List of commands (process or bend to another format)
// pub(crate) commands: Vec<Command>,
#[command(subcommand)]
pub(crate) output_format: OutputFormat,
/// Output file or standard output ("-").
pub(crate) output: FileOrStd<Stdout>,
}
#[derive(Clone, Copy, Parser)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Dimensions {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy, Parser)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct ShivaFormat {
doc_input_format: DocumentType,
doc_output_format: DocumentType,
}
#[derive(Clone, Copy, Parser)]
pub struct Mp3Format {
#[arg(default_value = "44100")]
pub sample_rate: u32,
#[arg(default_value = "128")]
pub bitrate: u16,
#[arg(default_value = "5")]
pub quality: u8,
#[arg(default_value_t)]
pub sample_format: Mp3SampleFormat,
}
#[derive(Clone, Copy, EnumString, Default, Display)]
#[strum(ascii_case_insensitive)]
pub enum Mp3SampleFormat {
U16,
I16,
I32,
#[default]
F32,
F64,
}
#[derive(Clone, Copy, Parser)]
pub struct WavFormat {
#[arg(default_value = "48000")]
pub sample_rate: u32,
#[arg(default_value_t)]
pub sample_format: WavSampleFormat,
}
#[derive(Clone, Copy, EnumString, Display, Default)]
#[strum(ascii_case_insensitive)]
pub enum WavSampleFormat {
I8,
I16,
I32,
#[default]
F32,
}
#[derive(Clone, Copy, Parser)]
pub struct FlacFormat {
#[arg(default_value = "48000")]
pub sample_rate: usize,
#[arg(value_parser = flac_bps_value_parser, default_value = "16")]
pub bps: usize,
}
fn flac_bps_value_parser(input: &str) -> anyhow::Result<usize> {
input.parse().context("not a number").and_then(|n| match n {
8 | 16 | 24 => Ok(n),
_ => Err(anyhow!("should be 8, 16 or 24")),
})
}
impl From<WavSampleFormat> for hound::SampleFormat {
fn from(value: WavSampleFormat) -> Self {
match value {
WavSampleFormat::F32 => hound::SampleFormat::Float,
_ => hound::SampleFormat::Int,
}
}
}
impl From<Dimensions> for bingus::img::Dimensions {
fn from(value: Dimensions) -> Self {
Self {
width: value.width,
height: value.height,
}
}
}
impl From<ShivaFormat> for bingus::doc::ShivaFormat {
fn from(value: ShivaFormat) -> Self {
Self::new(value.doc_input_format, value.doc_output_format)
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct Stdin;
#[derive(Clone, Copy, Default)]
pub(crate) struct Stdout;
pub(crate) trait New {
type Real;
fn new() -> Self::Real;
}
impl New for Stdin {
type Real = std::io::Stdin;
fn new() -> Self::Real {
stdin()
}
}
impl New for Stdout {
type Real = std::io::Stdout;
fn new() -> Self::Real {
stdout()
}
}
impl<S: Clone + Default> FromStr for FileOrStd<S> {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "-" {
Ok(FileOrStd::Std(Default::default()))
} else {
s.parse().map(FileOrStd::File)
}
}
}
#[derive(Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub(crate) enum FileOrStd<S: Clone> {
File(PathBuf),
Std(S),
}
impl<'a, S> FileOrStd<S>
where
S: Clone + New,
<S as New>::Real: Read + 'a,
{
pub(crate) fn reader(self) -> Result<Box<dyn Read + 'a>, io::Error> {
Ok(match self {
FileOrStd::File(path) => Box::new(File::open(path)?),
FileOrStd::Std(_) => Box::new(S::new()),
})
}
}
#[derive(Clone, Parser)]
pub(crate) enum OutputFormat {
Bytes,
Pdf,
ShivaDocument(ShivaFormat),
Font,
Mp3(Mp3Format),
Wav(WavFormat),
Flac(FlacFormat),
Text,
ImageLuma8(Dimensions),
ImageLumaA8(Dimensions),
ImageRgb8(Dimensions),
ImageRgba8(Dimensions),
ImageLuma16(Dimensions),
ImageLumaA16(Dimensions),
ImageRgb16(Dimensions),
ImageRgba16(Dimensions),
ImageRgb32F(Dimensions),
ImageRgba32F(Dimensions),
}
// #[derive(Clone)]
// pub(crate) enum Command {
// Into(Bendable),
// Process(Process),
// }
#[derive(EnumString, AsRefStr, Clone)]
pub(crate) enum Bendable {
#[strum(serialize = "bin", serialize = "binary")]
Binary,
}
// #[derive(Clone)]
// pub(crate) enum Process {}

View file

@ -1,3 +1,546 @@
fn main() {
println!("Hello, world!");
use std::{
borrow::Cow,
fs::File,
io::{Cursor, Write},
};
use anyhow::{anyhow, bail, Context};
use bingus::{
doc::{printpdf::PdfDocument, ShivaDocument},
dynamic::{self, DynamicBendable},
fnt::Font,
img::{
GrayAlphaImage, GrayImage, Image, Luma, LumaA, Rgb, Rgb32FImage, RgbImage, Rgba,
Rgba32FImage, RgbaImage,
},
snd::{conv::IntoSample, sample::i24, RawSamples},
txt::Text,
Bendable, Bytes, FromDataBytes, IntoDataBytes, TryFromDataBytes,
};
use clap::Parser;
use cli::Cli;
use flacenc::{component::BitRepr, error::Verify};
use hound::{WavSpec, WavWriter};
use mp3lame_encoder::{Builder as Mp3EncoderBuilder, DualPcm, FlushGap};
use unwrap_infallible::UnwrapInfallible;
use crate::cli::{FlacFormat, Mp3Format, New, Stdout, WavFormat};
mod cli;
fn main() -> anyhow::Result<()> {
let Cli {
input,
output_format,
output,
} = Cli::parse();
let input_bytes = {
let mut buf = Vec::new();
input
.reader()
.and_then(|mut r| r.read_to_end(&mut buf))
.context("reading input")?;
buf
};
let mut bytes = match dynamic::open(&mut Cursor::new(input_bytes.clone()))
.context("guessing media type / loading media")?
.or(String::from_utf8(input_bytes.clone())
.ok()
.map(Cow::Owned)
.map(DynamicBendable::Text))
.unwrap_or(DynamicBendable::Binary(input_bytes))
{
dynamic::DynamicBendable::Image(dynamic_image) => {
Bytes::bend_from(dynamic_image, (), Default::default())
}
dynamic::DynamicBendable::Binary(b) => Bytes::bend_from(b, (), Default::default()),
dynamic::DynamicBendable::Sound(audio) => Bytes::bend_from(audio, (), Default::default()),
dynamic::DynamicBendable::Text(cow) => Bytes::bend_from(cow, (), Default::default()),
dynamic::DynamicBendable::Doc(shiva_document) => {
Bytes::bend_from(shiva_document, (), Default::default())
}
dynamic::DynamicBendable::Archive(pdf_document) => {
Bytes::bend_from(pdf_document, (), Default::default())
}
dynamic::DynamicBendable::Meta => {
unimplemented!("this is not supposed to happen...")
}
dynamic::DynamicBendable::Font(font) => Bytes::bend_from(font, (), Default::default()),
}
.context("bending input into bytes")?;
match output {
cli::FileOrStd::File(file) => {
match output_format {
cli::OutputFormat::Bytes => {
File::bend_from(bytes, Box::new(file), Default::default())
.context("writing to file")?;
}
cli::OutputFormat::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")?;
}
cli::OutputFormat::ShivaDocument(shiva_format) => {
File::bend_from(
ShivaDocument::bend_from(bytes, shiva_format.into(), Default::default())
.context("bending into a Shiva Document")?,
Box::new(file),
Default::default(),
)
.context("writing Shiva Document to output file")?;
}
cli::OutputFormat::Font => {
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")?;
}
cli::OutputFormat::Mp3(mp3_format) => {
eprintln!("warning: Rust MP3 lame encoder is freaky!");
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")?;
}
cli::OutputFormat::Wav(wav_format) => {
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")?
}
};
}
cli::OutputFormat::Flac(flac_format) => {
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")?;
}
cli::OutputFormat::Text => {
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")?;
}
cli::OutputFormat::ImageLuma8(dimensions) => {
GrayImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageLumaA8(dimensions) => {
GrayAlphaImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgb8(dimensions) => {
RgbImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgba8(dimensions) => {
RgbaImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageLuma16(dimensions) => {
Image::<Luma<u16>, Vec<u16>>::from_data_bytes(
bytes,
dimensions.into(),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageLumaA16(dimensions) => {
Image::<LumaA<u16>, Vec<u16>>::from_data_bytes(
bytes,
dimensions.into(),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgb16(dimensions) => {
Image::<Rgb<u16>, Vec<u16>>::from_data_bytes(
bytes,
dimensions.into(),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgba16(dimensions) => {
Image::<Rgba<u16>, Vec<u16>>::from_data_bytes(
bytes,
dimensions.into(),
Default::default(),
)
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgb32F(dimensions) => {
Rgb32FImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
cli::OutputFormat::ImageRgba32F(dimensions) => {
Rgba32FImage::from_data_bytes(bytes, dimensions.into(), Default::default())
.save(file)
.context("writing image data to output file")?
}
};
}
cli::FileOrStd::Std(_) => Stdout::new()
.write_all(&mut bytes)
.context("writing to stdout")?,
};
Ok(())
}

View file

Before

Width:  |  Height:  |  Size: 24 MiB

After

Width:  |  Height:  |  Size: 24 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 799 KiB

After

Width:  |  Height:  |  Size: 799 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.