Add option to export original images
This commit is contained in:
parent
c8fb228a24
commit
dad0f282c6
5 changed files with 51 additions and 20 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env fish
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
argparse h/help p/print r/release -- $argv
|
argparse h/help p/print o/originals r/release -- $argv
|
||||||
and not set -ql _flag_help
|
and not set -ql _flag_help
|
||||||
or begin
|
or begin
|
||||||
echo "Usage:" (status filename) "[OPTIONS]"
|
echo "Usage:" (status filename) "[OPTIONS]"
|
||||||
|
|
@ -8,6 +8,7 @@ or begin
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " -h, --help Show this help"
|
echo " -h, --help Show this help"
|
||||||
echo " -p, --print Attach to printer at /dev/usb/lp0"
|
echo " -p, --print Attach to printer at /dev/usb/lp0"
|
||||||
|
echo " -o, --originals Export original images"
|
||||||
echo " -r, --release Use 'cargo run --release'"
|
echo " -r, --release Use 'cargo run --release'"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -22,6 +23,11 @@ if set -ql _flag_print
|
||||||
set arg_print -p /dev/usb/lp0
|
set arg_print -p /dev/usb/lp0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
set -l arg_originals
|
||||||
|
if set -ql _flag_originals
|
||||||
|
set arg_originals -o target/originals
|
||||||
|
end
|
||||||
|
|
||||||
cargo run $arg_release \
|
cargo run $arg_release \
|
||||||
--package showbits-thermal-printer \
|
--package showbits-thermal-printer \
|
||||||
-- target/queue -e target/image.png $arg_print
|
-- target/queue -e target/image.png $arg_print $arg_originals
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ Description=Showbits Thermal Printer
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/home/bondrucker/showbits-thermal-printer queue -a 0.0.0.0:8001 -p /dev/usb/lp0
|
ExecStart=/home/bondrucker/showbits-thermal-printer queue -a 0.0.0.0:8001 -p /dev/usb/lp0
|
||||||
|
# ExecStart=/home/bondrucker/showbits-thermal-printer queue -a 0.0.0.0:8001 -p /dev/usb/lp0 -o originals
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
User=bondrucker
|
User=bondrucker
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::io::Cursor;
|
use std::{fs, io::Cursor};
|
||||||
|
|
||||||
use anyhow::{Context, anyhow, bail};
|
use anyhow::{Context, anyhow, bail};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
|
@ -10,6 +10,7 @@ use image::{
|
||||||
DynamicImage, EncodableLayout, ImageDecoder, ImageFormat, ImageReader, Luma, Pixel, RgbaImage,
|
DynamicImage, EncodableLayout, ImageDecoder, ImageFormat, ImageReader, Luma, Pixel, RgbaImage,
|
||||||
imageops,
|
imageops,
|
||||||
};
|
};
|
||||||
|
use jiff::Timestamp;
|
||||||
use mark::dither::{AlgoFloydSteinberg, AlgoStucki, Algorithm, DiffEuclid, Palette};
|
use mark::dither::{AlgoFloydSteinberg, AlgoStucki, Algorithm, DiffEuclid, Palette};
|
||||||
use palette::LinSrgb;
|
use palette::LinSrgb;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
@ -101,17 +102,7 @@ pub async fn post(server: State<Server>, mut multipart: Multipart) -> somehow::R
|
||||||
while let Some(field) = multipart.next_field().await? {
|
while let Some(field) = multipart.next_field().await? {
|
||||||
match field.name() {
|
match field.name() {
|
||||||
Some("image") => {
|
Some("image") => {
|
||||||
let data = field.bytes().await?;
|
image = Some(field.bytes().await?);
|
||||||
|
|
||||||
// https://github.com/image-rs/image/issues/2392#issuecomment-2547393362
|
|
||||||
let mut decoder = ImageReader::new(Cursor::new(data.as_bytes()))
|
|
||||||
.with_guessed_format()?
|
|
||||||
.into_decoder()?;
|
|
||||||
let orientation = decoder.orientation()?;
|
|
||||||
let mut decoded = DynamicImage::from_decoder(decoder)?;
|
|
||||||
decoded.apply_orientation(orientation);
|
|
||||||
|
|
||||||
image = Some(decoded.to_rgba8());
|
|
||||||
}
|
}
|
||||||
Some("title") => {
|
Some("title") => {
|
||||||
data.title = Some(field.text().await?).filter(|it| !it.is_empty());
|
data.title = Some(field.text().await?).filter(|it| !it.is_empty());
|
||||||
|
|
@ -139,10 +130,31 @@ pub async fn post(server: State<Server>, mut multipart: Multipart) -> somehow::R
|
||||||
return Ok(status_code(StatusCode::UNPROCESSABLE_ENTITY));
|
return Ok(status_code(StatusCode::UNPROCESSABLE_ENTITY));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Export original image if requested
|
||||||
|
if let Some(dir) = &server.originals {
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
let path = dir.join(Timestamp::now().as_millisecond().to_string());
|
||||||
|
fs::write(path, &image)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode image data
|
||||||
|
let image = {
|
||||||
|
// https://github.com/image-rs/image/issues/2392#issuecomment-2547393362
|
||||||
|
let mut decoder = ImageReader::new(Cursor::new(image.as_bytes()))
|
||||||
|
.with_guessed_format()?
|
||||||
|
.into_decoder()?;
|
||||||
|
let orientation = decoder.orientation()?;
|
||||||
|
let mut decoded = DynamicImage::from_decoder(decoder)?;
|
||||||
|
decoded.apply_orientation(orientation);
|
||||||
|
decoded.to_rgba8()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dither image
|
||||||
let max_width = Some(384);
|
let max_width = Some(384);
|
||||||
let max_height = Some(1024);
|
let max_height = Some(1024);
|
||||||
let image = dither(image, max_width, max_height, bright, &algo).map_err(somehow::Error)?;
|
let image = dither(image, max_width, max_height, bright, &algo).map_err(somehow::Error)?;
|
||||||
|
|
||||||
|
// Encode dithered image for typst
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
image
|
image
|
||||||
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)
|
.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ struct Args {
|
||||||
/// Export an image of whatever is printed here.
|
/// Export an image of whatever is printed here.
|
||||||
#[arg(long, short)]
|
#[arg(long, short)]
|
||||||
export: Option<PathBuf>,
|
export: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Export the original images printed by the image document, before
|
||||||
|
/// dithering or other manipulation.
|
||||||
|
#[arg(long, short)]
|
||||||
|
originals: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
|
|
@ -44,7 +49,7 @@ fn main() -> anyhow::Result<()> {
|
||||||
let mut drawer = Drawer::new(rx, printer);
|
let mut drawer = Drawer::new(rx, printer);
|
||||||
|
|
||||||
let runtime = Runtime::new()?;
|
let runtime = Runtime::new()?;
|
||||||
runtime.spawn(server::run(tx.clone(), args.address));
|
runtime.spawn(server::run(tx.clone(), args.address, args.originals));
|
||||||
runtime.spawn(async move {
|
runtime.spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let _ = tx.send(Command::Backlog).await;
|
let _ = tx.send(Command::Backlog).await;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ pub mod somehow;
|
||||||
mod r#static;
|
mod r#static;
|
||||||
pub mod statuscode;
|
pub mod statuscode;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::DefaultBodyLimit,
|
extract::DefaultBodyLimit,
|
||||||
|
|
@ -18,6 +20,7 @@ use crate::{documents, drawer::Command};
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
tx: mpsc::Sender<Command>,
|
tx: mpsc::Sender<Command>,
|
||||||
|
pub originals: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
|
@ -28,7 +31,11 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()> {
|
pub async fn run(
|
||||||
|
tx: mpsc::Sender<Command>,
|
||||||
|
addr: String,
|
||||||
|
originals: Option<PathBuf>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// Files
|
// Files
|
||||||
.route("/", get(r#static::get_index))
|
.route("/", get(r#static::get_index))
|
||||||
|
|
@ -47,7 +54,7 @@ pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()>
|
||||||
.route("/api/xkcd", post(documents::xkcd::post))
|
.route("/api/xkcd", post(documents::xkcd::post))
|
||||||
// Rest
|
// Rest
|
||||||
.layer(DefaultBodyLimit::max(32 * 1024 * 1024)) // 32 MiB
|
.layer(DefaultBodyLimit::max(32 * 1024 * 1024)) // 32 MiB
|
||||||
.with_state(Server { tx });
|
.with_state(Server { tx, originals });
|
||||||
|
|
||||||
let listener = TcpListener::bind(addr).await?;
|
let listener = TcpListener::bind(addr).await?;
|
||||||
axum::serve(listener, app).await?;
|
axum::serve(listener, app).await?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue