games / reflections on Minecraft

This commit is contained in:
りき萌 2024-12-24 23:32:15 +01:00
parent 161fb6a442
commit 93c24859d0
51 changed files with 743 additions and 55 deletions

View file

@ -32,4 +32,5 @@ tracing.workspace = true
tracing-chrome = "0.7.2"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
ulid = "1.0.0"
webp = "0.3.0"
xmlparser = "0.13.6"

View file

@ -1,14 +1,15 @@
use std::sync::Arc;
use std::{io::Cursor, sync::Arc};
use axum::{
body::Bytes,
debug_handler,
extract::{Query, State},
extract::{DefaultBodyLimit, Query, State},
response::IntoResponse,
routing::post,
Json, Router,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{
dirs::Dirs,
@ -18,10 +19,11 @@ use crate::{
pub fn router<S>(dirs: Arc<Dirs>) -> Router<S> {
Router::new()
.route("/", post(picture_upload))
.layer(DefaultBodyLimit::disable())
.with_state(dirs)
}
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
struct PictureUpload {
label: String,
format: String,
@ -31,6 +33,16 @@ struct PictureUpload {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
enum Compression {
Lossless,
GameScreenshot,
}
impl Compression {
pub fn output_format(self) -> Option<&'static str> {
match self {
Compression::Lossless => None,
Compression::GameScreenshot => Some("image/webp"),
}
}
}
#[derive(Serialize)]
@ -40,12 +52,48 @@ enum Response {
Error(String),
}
fn compress(image_data: &[u8], compression: Compression) -> anyhow::Result<Vec<u8>> {
match compression {
Compression::Lossless => Ok(image_data.to_vec()),
Compression::GameScreenshot => {
info!("decompressing original image");
let decompressed = image::ImageReader::new(Cursor::new(image_data))
.with_guessed_format()?
.decode()?
.to_rgba8();
info!("compressing to webp");
let compressed = webp::Encoder::new(
&decompressed,
webp::PixelLayout::Rgba,
decompressed.width(),
decompressed.height(),
)
.encode(85.0)
.to_vec();
Ok(compressed)
}
}
}
async fn write_compressed(
image_data: &[u8],
compression: Compression,
edit_path: EditPath,
) -> anyhow::Result<()> {
let compressed = compress(image_data, compression)?;
Edit::Write(edit_path, compressed).apply().await?;
Ok(())
}
#[debug_handler]
async fn picture_upload(
State(dirs): State<Arc<Dirs>>,
Query(mut params): Query<PictureUpload>,
image: Bytes,
) -> impl IntoResponse {
info!(?params, "uploading picture");
let ulid = ulid::Generator::new()
.generate_with_source(&mut rand::thread_rng())
.expect("failed to generate ulid");
@ -57,16 +105,15 @@ async fn picture_upload(
let file_name = VPathBuf::new(format!(
"{ulid}-{}.{}",
params.label,
get_extension(&params.format).unwrap_or("unknown")
get_extension(params.compression.output_format().unwrap_or(&params.format))
.unwrap_or("unknown")
));
let Some(edit_path) = vfs::query::<EditPath>(&dirs.pic, &file_name) else {
return Json(Response::Error(format!("{file_name} is not editable")));
};
let result = match params.compression {
Compression::Lossless => Edit::Write(edit_path, image.to_vec()).apply().await,
};
let result = write_compressed(&image, params.compression, edit_path).await;
info!(?result, "done processing");
Json(match result {
Ok(()) => Response::Ulid(ulid.to_string()),
Err(error) => Response::Error(error.to_string()),

View file

@ -105,12 +105,6 @@ pub struct JavaScript {
pub import_roots: Vec<ImportRoot>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Markup {
Markdown,
Djot,
}
impl Config {
#[instrument(name = "Config::autopopulate_emoji", skip(self))]
pub fn autopopulate_emoji(&mut self, dir: &dyn Dir) -> anyhow::Result<()> {

View file

@ -247,7 +247,7 @@ impl<'a> Writer<'a> {
Container::Image(..) => {
self.img_alt_text += 1;
if self.img_alt_text == 1 {
out.push_str(r#"<img class="pic""#);
out.push_str(r#"<img class="pic" loading="lazy""#);
} else {
return Ok(());
}