Skip to content

Commit eb606d3

Browse files
committed
update
1 parent afb36d0 commit eb606d3

File tree

6 files changed

+127
-17
lines changed

6 files changed

+127
-17
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
*.xml
44
*.ass
5+
6+
/static

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
*.xml
44
*.ass
5+
6+
/static

Cargo.lock

Lines changed: 58 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ quick_xml = ["quick-xml"]
1818
native-tls = ["reqwest/native-tls", "biliapi/native-tls"]
1919
rustls = ["reqwest/rustls-tls", "biliapi/rustls"]
2020

21-
web = ["actix-web", "tempfile", "portpicker", "open"]
21+
web = ["actix-web", "tempfile", "portpicker", "open", "percent-encoding", "actix-files"]
2222

2323
[dependencies]
2424
anyhow = "1.0.56"
@@ -48,3 +48,7 @@ actix-web = { version = "4.3.1", optional = true }
4848
tempfile = { version = "3.7.1", optional = true }
4949
portpicker = { version = "0.1.1", optional = true }
5050
open = { version = "5.0.0", optional = true }
51+
percent-encoding = { version = "2.3.0", optional = true }
52+
actix-files = { version = "0.6.2", optional = true }
53+
# actix-cors = "0.6.4"
54+

src/canvas/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct Config {
2525
#[serde(skip_deserializing)]
2626
pub bottom_percentage: f64,
2727
/// 透明度
28+
#[serde(rename = "alpha", deserialize_with = "deserialize_alpha_to_opacity")]
2829
pub opacity: u8,
2930
/// 是否加粗,1代表是,0代表否
3031
pub bold: bool,
@@ -33,6 +34,17 @@ pub struct Config {
3334
/// 时间轴偏移
3435
pub time_offset: f64,
3536
}
37+
fn deserialize_alpha_to_opacity<'de, D>(deserializer: D) -> Result<u8, D::Error>
38+
where
39+
D: serde::Deserializer<'de>,
40+
{
41+
use serde::de::Deserialize;
42+
let alpha = f64::deserialize(deserializer)?;
43+
if !(0.0..=1.0).contains(&alpha) {
44+
return Err(serde::de::Error::custom("alpha must be between 0 and 1"));
45+
}
46+
Ok(255 - (alpha * 255.) as u8)
47+
}
3648

3749
impl Config {
3850
pub fn canvas(self) -> Canvas {

src/web.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use actix_web::{web, HttpResponse};
44
use anyhow::{bail, Context};
55
use biliapi::Request;
66
use danmu2ass::{bilibili::DanmakuElem, CanvasConfig, InputType};
7+
use log::info;
78
use serde::Deserialize;
89
use serde_json::json;
910

1011
#[derive(Debug, Deserialize)]
11-
#[serde(tag = "type", content = "content")]
12+
#[serde(tag = "type", content = "content", rename_all = "snake_case")]
1213
pub enum Source {
1314
Xml { content: String, title: String },
1415
Url { url: String },
@@ -21,20 +22,31 @@ pub struct ConvertRequest {
2122
denylist: Option<HashSet<String>>,
2223
}
2324

24-
#[actix_web::get("/convert}")]
2525
async fn convert(request: web::Json<ConvertRequest>) -> HttpResponse {
2626
let req = request.into_inner();
27-
match req.source {
27+
let mut output = Vec::<u8>::new();
28+
let title = match req.source {
2829
Source::Xml { content, title } => {
30+
info!("parsing {} bytes in xml", content.len());
2931
let parser = danmu2ass::Parser::new(content.as_bytes());
30-
let mut output = Vec::<u8>::new();
31-
let r = danmu2ass::convert(parser, title, &mut output, req.config, &req.denylist);
32+
let r = danmu2ass::convert(
33+
parser,
34+
title.clone(),
35+
&mut output,
36+
req.config,
37+
&req.denylist,
38+
);
3239
if let Err(e) = r {
3340
return HttpResponse::BadRequest().json(json!({
3441
"errmsg": format!("{e:#?}")
3542
}));
3643
}
37-
HttpResponse::Ok().content_type("text/plain").body(output)
44+
let ass_title = title
45+
.as_str()
46+
.strip_suffix(".xml")
47+
.map(ToString::to_string)
48+
.unwrap_or(title);
49+
ass_title
3850
}
3951
Source::Url { url } => {
4052
let input_type: InputType = url.parse().unwrap();
@@ -47,10 +59,10 @@ async fn convert(request: web::Json<ConvertRequest>) -> HttpResponse {
4759
}));
4860
}
4961
};
50-
let mut output = Vec::<u8>::new();
62+
log::info!("download {} danmu, title={}", danmu.len(), title);
5163
let r = danmu2ass::convert(
5264
danmu.into_iter().map(|i| Ok(i.into())),
53-
title,
65+
title.clone(),
5466
&mut output,
5567
req.config,
5668
&req.denylist,
@@ -60,9 +72,16 @@ async fn convert(request: web::Json<ConvertRequest>) -> HttpResponse {
6072
"errmsg": format!("{e:#?}")
6173
}));
6274
}
63-
HttpResponse::Ok().content_type("text/plain").body(output)
75+
title
6476
}
65-
}
77+
};
78+
let title =
79+
percent_encoding::percent_encode(title.as_bytes(), percent_encoding::NON_ALPHANUMERIC);
80+
let content_disposition = format!("attachment; filename=\"{title}.ass\"");
81+
HttpResponse::Ok()
82+
.append_header(("Content-Type", "text/plain; charset=utf-8"))
83+
.append_header(("Content-Disposition", content_disposition))
84+
.body(output)
6685
}
6786

6887
async fn run_input_type(input_type: InputType) -> anyhow::Result<(String, Vec<DanmakuElem>)> {
@@ -113,11 +132,26 @@ async fn run_input_type(input_type: InputType) -> anyhow::Result<(String, Vec<Da
113132
}
114133
}
115134

135+
fn files_service() -> actix_files::Files {
136+
actix_files::Files::new("/", "./static")
137+
.index_file("index.html")
138+
.prefer_utf8(true)
139+
}
140+
116141
pub async fn run_server() -> anyhow::Result<()> {
117-
let port = portpicker::pick_unused_port().context("pick port failed")?;
118-
let fut = actix_web::HttpServer::new(|| actix_web::App::new().service(convert))
119-
.bind(("127.0.0.1", port))?
120-
.run();
142+
let port = if portpicker::is_free(8081) {
143+
8081
144+
} else {
145+
portpicker::pick_unused_port().context("pick port failed")?
146+
};
147+
let fut = actix_web::HttpServer::new(move || {
148+
actix_web::App::new()
149+
// .wrap(actix_cors::Cors::permissive())
150+
.route("/convert", web::post().to(convert))
151+
.default_service(files_service())
152+
})
153+
.bind(("127.0.0.1", port))?
154+
.run();
121155
let handle = tokio::spawn(fut);
122156
// open
123157
match open::that(format!("http://127.0.0.1:{port}")) {

0 commit comments

Comments
 (0)