mirror of
https://github.com/LordMZTE/mcstat.git
synced 2024-05-05 06:11:11 +02:00
added base64 image decoding in-house
This commit is contained in:
parent
dd790d8adf
commit
27517a6994
|
@ -9,9 +9,9 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
asciify = "0.1.6"
|
asciify = "0.1.6"
|
||||||
|
base64 = "0.13.0"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
image = "0.23.14"
|
image = "0.23.14"
|
||||||
image-base64 = "0.1.0"
|
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
smart-default = "0.6.0"
|
smart-default = "0.6.0"
|
||||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -2,7 +2,9 @@
|
||||||
extern crate smart_default;
|
extern crate smart_default;
|
||||||
|
|
||||||
use crate::output::Table;
|
use crate::output::Table;
|
||||||
|
use anyhow::{anyhow, bail, Context};
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
|
use image::{DynamicImage, ImageFormat};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
@ -76,3 +78,30 @@ pub fn get_table<'a>(
|
||||||
Itertools::intersperse(entries.map(|x| x.0), "\n").collect()
|
Itertools::intersperse(entries.map(|x| x.0), "\n").collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parses a base64 formatted image
|
||||||
|
pub fn parse_base64_image(data: String) -> anyhow::Result<DynamicImage> {
|
||||||
|
let (header, data) = data
|
||||||
|
.split_once(",")
|
||||||
|
.context("Couldn't parse base64 image due to missing format header.")?;
|
||||||
|
let (data_type, image_format) = header
|
||||||
|
.split_once("/")
|
||||||
|
.context("Failed to parse base64 image, header has invalid format.")?;
|
||||||
|
let image_format = image_format
|
||||||
|
.split(";")
|
||||||
|
.next()
|
||||||
|
.context("Failed to parse base64 image, header has invalid format.")?;
|
||||||
|
|
||||||
|
if data_type != "data:image" {
|
||||||
|
bail!("base64 image is not an image! Has type {}", data_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let format = ImageFormat::from_extension(image_format).context(format!(
|
||||||
|
"Failed to parse base64 image due to unknown image type: {}",
|
||||||
|
image_format
|
||||||
|
))?;
|
||||||
|
let data =
|
||||||
|
base64::decode(data).map_err(|e| anyhow!("Failed to decode base64 image data: {}", e))?;
|
||||||
|
image::load(Cursor::new(data), format)
|
||||||
|
.map_err(|e| anyhow!("Failed to load base64 image: {}", e))
|
||||||
|
}
|
||||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -1,16 +1,21 @@
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use asciify::AsciiBuilder;
|
use asciify::AsciiBuilder;
|
||||||
use async_minecraft_ping::{ConnectionConfig, ServerDescription, StatusResponse};
|
use async_minecraft_ping::{ConnectionConfig, ServerDescription, StatusResponse};
|
||||||
use image::ImageFormat;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor};
|
||||||
use time::{Duration, Instant};
|
use time::{Duration, Instant};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
use mcstat::{get_table, none_if_empty, output::Table, remove_formatting, AsciiConfig};
|
use mcstat::{
|
||||||
|
get_table,
|
||||||
|
none_if_empty,
|
||||||
|
output::Table,
|
||||||
|
parse_base64_image,
|
||||||
|
remove_formatting,
|
||||||
|
AsciiConfig,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -46,7 +51,12 @@ struct Opt {
|
||||||
#[structopt(long, short, help = "print mod list")]
|
#[structopt(long, short, help = "print mod list")]
|
||||||
mods: bool,
|
mods: bool,
|
||||||
|
|
||||||
#[structopt(long, short = "v", requires = "mods", help = "also prints mod versions")]
|
#[structopt(
|
||||||
|
long,
|
||||||
|
short = "v",
|
||||||
|
requires = "mods",
|
||||||
|
help = "also prints mod versions"
|
||||||
|
)]
|
||||||
modversions: bool,
|
modversions: bool,
|
||||||
|
|
||||||
#[structopt(long, help = "displays forge mod channels if the server sends them")]
|
#[structopt(long, help = "displays forge mod channels if the server sends them")]
|
||||||
|
@ -74,7 +84,12 @@ struct Opt {
|
||||||
)]
|
)]
|
||||||
deep: bool,
|
deep: bool,
|
||||||
|
|
||||||
#[structopt(long, short = "n", requires = "image", help = "inverts ascii art favicon")]
|
#[structopt(
|
||||||
|
long,
|
||||||
|
short = "n",
|
||||||
|
requires = "image",
|
||||||
|
help = "inverts ascii art favicon"
|
||||||
|
)]
|
||||||
invert: bool,
|
invert: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,17 +109,20 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
// create timeout for server connection
|
// create timeout for server connection
|
||||||
let (raw_response, ping) = time::timeout(Duration::from_millis(opt.timeout), async {
|
let (raw_response, ping) = time::timeout(Duration::from_millis(opt.timeout), async {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let mut con = config.connect().await?;
|
let mut con = config.connect().await?;
|
||||||
// we end the timer here, because at this point, we've sent ONE request to the server,
|
// we end the timer here, because at this point, we've sent ONE request to the
|
||||||
// and we don't want to send 2, since then we get double the ping.
|
// server, and we don't want to send 2, since then we get double the
|
||||||
// the connect function may have some processing which may take some time, but it
|
// ping. the connect function may have some processing which may take
|
||||||
// shouldn't make an impact at this code runs at rust speed.
|
// some time, but it shouldn't make an impact at this code runs at rust
|
||||||
let end_time = Instant::now();
|
// speed.
|
||||||
|
let end_time = Instant::now();
|
||||||
|
|
||||||
let status = con.status_raw().await?;
|
let status = con.status_raw().await?;
|
||||||
Result::<_, anyhow::Error>::Ok((status, end_time - start_time))
|
Result::<_, anyhow::Error>::Ok((status, end_time - start_time))
|
||||||
}).await.context("Connection to server timed out.")??;
|
})
|
||||||
|
.await
|
||||||
|
.context("Connection to server timed out.")??;
|
||||||
|
|
||||||
if opt.raw {
|
if opt.raw {
|
||||||
println!("{}", raw_response);
|
println!("{}", raw_response);
|
||||||
|
@ -157,12 +175,7 @@ async fn main() -> Result<()> {
|
||||||
/// returns the asciifyed image from base64
|
/// returns the asciifyed image from base64
|
||||||
/// returns Err if the base64 image is invalid
|
/// returns Err if the base64 image is invalid
|
||||||
async fn asciify_base64_image(favicon: String, config: AsciiConfig) -> Result<String> {
|
async fn asciify_base64_image(favicon: String, config: AsciiConfig) -> Result<String> {
|
||||||
let img = image_base64::from_base64(favicon);
|
let image = parse_base64_image(favicon)?;
|
||||||
// TODO for some reason, image_base64 returns the format as string (and using
|
|
||||||
// regex!) which is useless and also inefficient, so Png is temporarily
|
|
||||||
// hardcoded. we should probably stop using this library
|
|
||||||
let image =
|
|
||||||
image::load(Cursor::new(img), ImageFormat::Png).context("image has invalid format")?;
|
|
||||||
|
|
||||||
let builder = config.apply(AsciiBuilder::new_from_image(image));
|
let builder = config.apply(AsciiBuilder::new_from_image(image));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue