Spaces:
Paused
Paused
Upload main.rs
Browse files- src/main.rs +343 -8
src/main.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
use axum::{
|
| 2 |
routing::{get, post},
|
| 3 |
http::StatusCode,
|
|
|
|
| 4 |
response::{IntoResponse, Html},
|
| 5 |
extract::{Json, State, Query, Form},
|
| 6 |
Router,
|
|
@@ -14,6 +15,7 @@ use std::sync::Arc;
|
|
| 14 |
use bytes::Bytes;
|
| 15 |
use futures_util::StreamExt;
|
| 16 |
use axum::body::Body;
|
|
|
|
| 17 |
use blowfish::Blowfish;
|
| 18 |
use cipher::{KeyIvInit, BlockDecryptMut};
|
| 19 |
use cipher::block_padding::NoPadding;
|
|
@@ -88,6 +90,55 @@ fn extract_media_url(media_json: &serde_json::Value) -> String {
|
|
| 88 |
String::new()
|
| 89 |
}
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
async fn media_url_for_track(
|
| 92 |
client: &mut APIClient,
|
| 93 |
id: u32,
|
|
@@ -96,7 +147,7 @@ async fn media_url_for_track(
|
|
| 96 |
let resp: Result<DeezerTrackList, APIError> = client
|
| 97 |
.api_call(
|
| 98 |
"song.getListData",
|
| 99 |
-
&json!({"sng_ids":[id],"array_default":["SNG_ID","TRACK_TOKEN"]}),
|
| 100 |
)
|
| 101 |
.await;
|
| 102 |
|
|
@@ -172,6 +223,23 @@ async fn upstream_media_url(id: u32, formats: &Vec<Format>) -> Result<String, St
|
|
| 172 |
Ok(url)
|
| 173 |
}
|
| 174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
#[derive(Deserialize)]
|
| 176 |
struct DeezerTrackList {
|
| 177 |
data: Vec<DeezerTrack>
|
|
@@ -180,7 +248,8 @@ struct DeezerTrackList {
|
|
| 180 |
#[derive(Deserialize)]
|
| 181 |
#[allow(non_snake_case)]
|
| 182 |
struct DeezerTrack {
|
| 183 |
-
TRACK_TOKEN: String
|
|
|
|
| 184 |
}
|
| 185 |
|
| 186 |
async fn root() -> &'static str {
|
|
@@ -316,7 +385,131 @@ struct StreamParams {
|
|
| 316 |
arl: Option<String>,
|
| 317 |
}
|
| 318 |
|
| 319 |
-
async fn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
let formats = match q.format.as_deref().unwrap_or("AUTO") {
|
| 321 |
"FLAC" => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 322 |
"MP3_320" => vec![Format::MP3_320, Format::MP3_128],
|
|
@@ -394,12 +587,65 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
|
|
| 394 |
}
|
| 395 |
};
|
| 396 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
let http = reqwest::Client::builder()
|
| 398 |
.timeout(std::time::Duration::from_secs(60))
|
| 399 |
.build()
|
| 400 |
.unwrap();
|
| 401 |
|
| 402 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
Ok(r) => r,
|
| 404 |
Err(_) => {
|
| 405 |
return (StatusCode::SERVICE_UNAVAILABLE, "Upstream request failed".to_string())
|
|
@@ -417,11 +663,18 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
|
|
| 417 |
|
| 418 |
let key = blowfish_key(q.id);
|
| 419 |
let mut carry: Vec<u8> = Vec::with_capacity(4096);
|
| 420 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
|
| 422 |
let mut upstream = upstream.bytes_stream();
|
| 423 |
|
| 424 |
let out = async_stream::stream! {
|
|
|
|
|
|
|
|
|
|
| 425 |
while let Some(chunk_res) = upstream.next().await {
|
| 426 |
let chunk = match chunk_res {
|
| 427 |
Ok(c) => c,
|
|
@@ -439,17 +692,98 @@ async fn stream(State(state): State<AppState>, Query(q): Query<StreamParams>) ->
|
|
| 439 |
let _ = decrypt_stripe(&mut block, &key);
|
| 440 |
}
|
| 441 |
block_index += 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
yield Ok::<Bytes, BoxErr>(Bytes::from(block));
|
| 443 |
}
|
|
|
|
| 444 |
}
|
| 445 |
|
| 446 |
-
if !carry.is_empty() {
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
}
|
| 449 |
};
|
| 450 |
|
| 451 |
let body = Body::from_stream(out);
|
| 452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
}
|
| 454 |
|
| 455 |
#[derive(Debug, Deserialize)]
|
|
@@ -1354,6 +1688,7 @@ async fn main() {
|
|
| 1354 |
.route("/get_url", post(get_url))
|
| 1355 |
.route("/fetch", get(fetch))
|
| 1356 |
.route("/stream", get(stream))
|
|
|
|
| 1357 |
.route("/user_data", post(user_data))
|
| 1358 |
.route("/playlists", post(playlists))
|
| 1359 |
.route("/playlist", post(playlist_tracks))
|
|
|
|
| 1 |
use axum::{
|
| 2 |
routing::{get, post},
|
| 3 |
http::StatusCode,
|
| 4 |
+
http::{HeaderMap, header::{RANGE, CONTENT_RANGE, ACCEPT_RANGES, CONTENT_LENGTH}},
|
| 5 |
response::{IntoResponse, Html},
|
| 6 |
extract::{Json, State, Query, Form},
|
| 7 |
Router,
|
|
|
|
| 15 |
use bytes::Bytes;
|
| 16 |
use futures_util::StreamExt;
|
| 17 |
use axum::body::Body;
|
| 18 |
+
use axum::http::HeaderValue;
|
| 19 |
use blowfish::Blowfish;
|
| 20 |
use cipher::{KeyIvInit, BlockDecryptMut};
|
| 21 |
use cipher::block_padding::NoPadding;
|
|
|
|
| 90 |
String::new()
|
| 91 |
}
|
| 92 |
|
| 93 |
+
fn extract_media_format(media_json: &serde_json::Value) -> String {
|
| 94 |
+
let data = media_json.get("data").and_then(|v| v.as_array());
|
| 95 |
+
let Some(data0) = data.and_then(|a| a.get(0)) else {
|
| 96 |
+
return String::new();
|
| 97 |
+
};
|
| 98 |
+
let media_arr = data0.get("media").and_then(|v| v.as_array());
|
| 99 |
+
let Some(media_arr) = media_arr else {
|
| 100 |
+
return String::new();
|
| 101 |
+
};
|
| 102 |
+
for m in media_arr {
|
| 103 |
+
let has_url = m
|
| 104 |
+
.get("sources")
|
| 105 |
+
.and_then(|v| v.as_array())
|
| 106 |
+
.and_then(|a| a.get(0))
|
| 107 |
+
.and_then(|s| s.get("url"))
|
| 108 |
+
.and_then(|u| u.as_str())
|
| 109 |
+
.map(|u| !u.trim().is_empty())
|
| 110 |
+
.unwrap_or(false);
|
| 111 |
+
if !has_url {
|
| 112 |
+
continue;
|
| 113 |
+
}
|
| 114 |
+
if let Some(s) = m.get("format").and_then(|v| v.as_str()) {
|
| 115 |
+
let s = s.trim();
|
| 116 |
+
if !s.is_empty() {
|
| 117 |
+
return s.to_string();
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
if let Some(s) = m.get("format_name").and_then(|v| v.as_str()) {
|
| 121 |
+
let s = s.trim();
|
| 122 |
+
if !s.is_empty() {
|
| 123 |
+
return s.to_string();
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
if let Some(n) = m.get("format").and_then(|v| v.as_i64()) {
|
| 127 |
+
return n.to_string();
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
String::new()
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
fn total_from_content_range(v: &str) -> Option<u64> {
|
| 134 |
+
let v = v.trim();
|
| 135 |
+
let (_, rest) = v.split_once('/')?;
|
| 136 |
+
if rest.trim() == "*" {
|
| 137 |
+
return None;
|
| 138 |
+
}
|
| 139 |
+
rest.trim().parse::<u64>().ok()
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
async fn media_url_for_track(
|
| 143 |
client: &mut APIClient,
|
| 144 |
id: u32,
|
|
|
|
| 147 |
let resp: Result<DeezerTrackList, APIError> = client
|
| 148 |
.api_call(
|
| 149 |
"song.getListData",
|
| 150 |
+
&json!({"sng_ids":[id],"array_default":["SNG_ID","TRACK_TOKEN","DURATION"]}),
|
| 151 |
)
|
| 152 |
.await;
|
| 153 |
|
|
|
|
| 223 |
Ok(url)
|
| 224 |
}
|
| 225 |
|
| 226 |
+
async fn public_track_duration(id: u32) -> Option<u32> {
|
| 227 |
+
let client = reqwest::Client::builder()
|
| 228 |
+
.timeout(std::time::Duration::from_secs(10))
|
| 229 |
+
.build()
|
| 230 |
+
.ok()?;
|
| 231 |
+
let r = client
|
| 232 |
+
.get(format!("https://api.deezer.com/track/{id}"))
|
| 233 |
+
.send()
|
| 234 |
+
.await
|
| 235 |
+
.ok()?;
|
| 236 |
+
if !r.status().is_success() {
|
| 237 |
+
return None;
|
| 238 |
+
}
|
| 239 |
+
let v: serde_json::Value = r.json().await.ok()?;
|
| 240 |
+
v.get("duration").and_then(|d| d.as_u64()).map(|d| d as u32)
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
#[derive(Deserialize)]
|
| 244 |
struct DeezerTrackList {
|
| 245 |
data: Vec<DeezerTrack>
|
|
|
|
| 248 |
#[derive(Deserialize)]
|
| 249 |
#[allow(non_snake_case)]
|
| 250 |
struct DeezerTrack {
|
| 251 |
+
TRACK_TOKEN: String,
|
| 252 |
+
DURATION: Option<u32>
|
| 253 |
}
|
| 254 |
|
| 255 |
async fn root() -> &'static str {
|
|
|
|
| 385 |
arl: Option<String>,
|
| 386 |
}
|
| 387 |
|
| 388 |
+
async fn stream_info(State(state): State<AppState>, Query(q): Query<StreamParams>) -> impl IntoResponse {
|
| 389 |
+
let formats = match q.format.as_deref().unwrap_or("AUTO") {
|
| 390 |
+
"FLAC" => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 391 |
+
"MP3_320" => vec![Format::MP3_320, Format::MP3_128],
|
| 392 |
+
"MP3_MISC" => vec![Format::MP3_MISC, Format::MP3_128],
|
| 393 |
+
"MP3_128" => vec![Format::MP3_128],
|
| 394 |
+
"AUTO" | _ => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 395 |
+
};
|
| 396 |
+
|
| 397 |
+
let requested = q.format.clone().unwrap_or_else(|| "AUTO".to_string());
|
| 398 |
+
let mut url: String = String::new();
|
| 399 |
+
let mut used: String = String::new();
|
| 400 |
+
let mut last_err: Option<String> = None;
|
| 401 |
+
|
| 402 |
+
if let Ok(text) = upstream_get_url_text(&formats, &vec![q.id]).await {
|
| 403 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&text) {
|
| 404 |
+
url = extract_media_url(&v);
|
| 405 |
+
used = extract_media_format(&v);
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
if url.is_empty() {
|
| 410 |
+
if let Some(arl) = q.arl.clone().filter(|s| !s.trim().is_empty()) {
|
| 411 |
+
let client = api_client_for_arl(&state, Some(arl)).await;
|
| 412 |
+
let res = {
|
| 413 |
+
let mut client = client.lock().await;
|
| 414 |
+
let resp: Result<DeezerTrackList, APIError> = client
|
| 415 |
+
.api_call(
|
| 416 |
+
"song.getListData",
|
| 417 |
+
&json!({"sng_ids":[q.id],"array_default":["SNG_ID","TRACK_TOKEN","DURATION"]}),
|
| 418 |
+
)
|
| 419 |
+
.await;
|
| 420 |
+
let track_list = resp.map_err(|e| e.to_string())?;
|
| 421 |
+
if track_list.data.is_empty() {
|
| 422 |
+
return Err("No valid ID".to_string());
|
| 423 |
+
}
|
| 424 |
+
let track_token = track_list.data[0].TRACK_TOKEN.as_str();
|
| 425 |
+
let media_resp = client
|
| 426 |
+
.get_media(&formats, vec![track_token])
|
| 427 |
+
.await
|
| 428 |
+
.map_err(|e| e.to_string())?;
|
| 429 |
+
let media_json: serde_json::Value = media_resp
|
| 430 |
+
.json()
|
| 431 |
+
.await
|
| 432 |
+
.map_err(|_| "Bad media response".to_string())?;
|
| 433 |
+
let url = extract_media_url(&media_json);
|
| 434 |
+
let used = extract_media_format(&media_json);
|
| 435 |
+
if url.is_empty() {
|
| 436 |
+
return Err("No url in media response".to_string());
|
| 437 |
+
}
|
| 438 |
+
Ok::<(String, String), String>((url, used))
|
| 439 |
+
}
|
| 440 |
+
.await;
|
| 441 |
+
match res {
|
| 442 |
+
Ok((u, f)) => {
|
| 443 |
+
url = u;
|
| 444 |
+
used = f;
|
| 445 |
+
}
|
| 446 |
+
Err(e) => last_err = Some(e),
|
| 447 |
+
}
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
if url.is_empty() {
|
| 452 |
+
return json_error(
|
| 453 |
+
StatusCode::SERVICE_UNAVAILABLE,
|
| 454 |
+
last_err.unwrap_or_else(|| "stream_info:no_url".to_string()),
|
| 455 |
+
);
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
let duration = public_track_duration(q.id).await.unwrap_or(0);
|
| 459 |
+
let http = reqwest::Client::builder()
|
| 460 |
+
.timeout(std::time::Duration::from_secs(15))
|
| 461 |
+
.build()
|
| 462 |
+
.unwrap();
|
| 463 |
+
let r = http
|
| 464 |
+
.get(url.clone())
|
| 465 |
+
.header(RANGE, "bytes=0-0")
|
| 466 |
+
.send()
|
| 467 |
+
.await;
|
| 468 |
+
|
| 469 |
+
let mut total_bytes: u64 = 0;
|
| 470 |
+
let mut mime: String = String::new();
|
| 471 |
+
if let Ok(r) = r {
|
| 472 |
+
if let Some(ct) = r.headers().get(CONTENT_TYPE).and_then(|v| v.to_str().ok()) {
|
| 473 |
+
mime = ct.to_string();
|
| 474 |
+
}
|
| 475 |
+
if let Some(cr) = r
|
| 476 |
+
.headers()
|
| 477 |
+
.get(CONTENT_RANGE)
|
| 478 |
+
.and_then(|v| v.to_str().ok())
|
| 479 |
+
.and_then(total_from_content_range)
|
| 480 |
+
{
|
| 481 |
+
total_bytes = cr;
|
| 482 |
+
} else if let Some(cl) = r.headers().get(CONTENT_LENGTH).and_then(|v| v.to_str().ok()) {
|
| 483 |
+
total_bytes = cl.parse::<u64>().unwrap_or(0);
|
| 484 |
+
}
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
let bitrate_kbps = if duration > 0 && total_bytes > 0 {
|
| 488 |
+
((total_bytes as f64) * 8.0 / (duration as f64) / 1000.0).round() as u64
|
| 489 |
+
} else {
|
| 490 |
+
0
|
| 491 |
+
};
|
| 492 |
+
|
| 493 |
+
(
|
| 494 |
+
StatusCode::OK,
|
| 495 |
+
Json(json!({
|
| 496 |
+
"id": q.id,
|
| 497 |
+
"requested": requested,
|
| 498 |
+
"used": used,
|
| 499 |
+
"duration": duration,
|
| 500 |
+
"bytes": total_bytes,
|
| 501 |
+
"bitrate_kbps": bitrate_kbps,
|
| 502 |
+
"mime": mime
|
| 503 |
+
})),
|
| 504 |
+
)
|
| 505 |
+
.into_response()
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
async fn stream(
|
| 509 |
+
State(state): State<AppState>,
|
| 510 |
+
Query(q): Query<StreamParams>,
|
| 511 |
+
headers: HeaderMap,
|
| 512 |
+
) -> impl IntoResponse {
|
| 513 |
let formats = match q.format.as_deref().unwrap_or("AUTO") {
|
| 514 |
"FLAC" => vec![Format::FLAC, Format::MP3_320, Format::MP3_128],
|
| 515 |
"MP3_320" => vec![Format::MP3_320, Format::MP3_128],
|
|
|
|
| 587 |
}
|
| 588 |
};
|
| 589 |
|
| 590 |
+
fn parse_range_header(v: &str) -> Option<(u64, Option<u64>)> {
|
| 591 |
+
let v = v.trim();
|
| 592 |
+
let v = v.strip_prefix("bytes=")?;
|
| 593 |
+
let mut it = v.splitn(2, '-');
|
| 594 |
+
let a = it.next()?.trim();
|
| 595 |
+
let b = it.next().unwrap_or("").trim();
|
| 596 |
+
if a.is_empty() {
|
| 597 |
+
return None;
|
| 598 |
+
}
|
| 599 |
+
let start = a.parse::<u64>().ok()?;
|
| 600 |
+
let end = if b.is_empty() {
|
| 601 |
+
None
|
| 602 |
+
} else {
|
| 603 |
+
let e = b.parse::<u64>().ok()?;
|
| 604 |
+
Some(e)
|
| 605 |
+
};
|
| 606 |
+
Some((start, end))
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
fn parse_total_from_content_range(v: &str) -> Option<u64> {
|
| 610 |
+
let v = v.trim();
|
| 611 |
+
let (_, rest) = v.split_once('/')?;
|
| 612 |
+
if rest.trim() == "*" {
|
| 613 |
+
return None;
|
| 614 |
+
}
|
| 615 |
+
rest.trim().parse::<u64>().ok()
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
let req_range = headers
|
| 619 |
+
.get(RANGE)
|
| 620 |
+
.and_then(|v| v.to_str().ok())
|
| 621 |
+
.and_then(parse_range_header);
|
| 622 |
+
|
| 623 |
let http = reqwest::Client::builder()
|
| 624 |
.timeout(std::time::Duration::from_secs(60))
|
| 625 |
.build()
|
| 626 |
.unwrap();
|
| 627 |
|
| 628 |
+
let block_size: u64 = 2048;
|
| 629 |
+
let mut block_index: u64 = 0;
|
| 630 |
+
let mut drop_bytes: usize = 0;
|
| 631 |
+
let mut remaining: Option<u64> = None;
|
| 632 |
+
|
| 633 |
+
let mut req = http.get(url);
|
| 634 |
+
if let Some((start, end)) = req_range {
|
| 635 |
+
let aligned_start = start - (start % block_size);
|
| 636 |
+
drop_bytes = (start - aligned_start) as usize;
|
| 637 |
+
block_index = aligned_start / block_size;
|
| 638 |
+
if let Some(e) = end {
|
| 639 |
+
if e >= start {
|
| 640 |
+
remaining = Some(e - start + 1);
|
| 641 |
+
}
|
| 642 |
+
req = req.header(RANGE, format!("bytes={}-{}", aligned_start, e));
|
| 643 |
+
} else {
|
| 644 |
+
req = req.header(RANGE, format!("bytes={}-", aligned_start));
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
let upstream = match req.send().await {
|
| 649 |
Ok(r) => r,
|
| 650 |
Err(_) => {
|
| 651 |
return (StatusCode::SERVICE_UNAVAILABLE, "Upstream request failed".to_string())
|
|
|
|
| 663 |
|
| 664 |
let key = blowfish_key(q.id);
|
| 665 |
let mut carry: Vec<u8> = Vec::with_capacity(4096);
|
| 666 |
+
let upstream_headers = upstream.headers().clone();
|
| 667 |
+
let upstream_total = upstream_headers
|
| 668 |
+
.get(CONTENT_RANGE)
|
| 669 |
+
.and_then(|v| v.to_str().ok())
|
| 670 |
+
.and_then(parse_total_from_content_range);
|
| 671 |
|
| 672 |
let mut upstream = upstream.bytes_stream();
|
| 673 |
|
| 674 |
let out = async_stream::stream! {
|
| 675 |
+
let mut drop_bytes = drop_bytes;
|
| 676 |
+
let mut remaining = remaining;
|
| 677 |
+
let mut done = false;
|
| 678 |
while let Some(chunk_res) = upstream.next().await {
|
| 679 |
let chunk = match chunk_res {
|
| 680 |
Ok(c) => c,
|
|
|
|
| 692 |
let _ = decrypt_stripe(&mut block, &key);
|
| 693 |
}
|
| 694 |
block_index += 1;
|
| 695 |
+
if drop_bytes > 0 {
|
| 696 |
+
if drop_bytes >= block.len() {
|
| 697 |
+
drop_bytes -= block.len();
|
| 698 |
+
continue;
|
| 699 |
+
}
|
| 700 |
+
block = block.split_off(drop_bytes);
|
| 701 |
+
drop_bytes = 0;
|
| 702 |
+
}
|
| 703 |
+
if let Some(rem) = remaining {
|
| 704 |
+
if rem == 0 {
|
| 705 |
+
done = true;
|
| 706 |
+
break;
|
| 707 |
+
}
|
| 708 |
+
if (block.len() as u64) > rem {
|
| 709 |
+
block.truncate(rem as usize);
|
| 710 |
+
remaining = Some(0);
|
| 711 |
+
yield Ok::<Bytes, BoxErr>(Bytes::from(block));
|
| 712 |
+
done = true;
|
| 713 |
+
break;
|
| 714 |
+
} else {
|
| 715 |
+
remaining = Some(rem - block.len() as u64);
|
| 716 |
+
}
|
| 717 |
+
}
|
| 718 |
yield Ok::<Bytes, BoxErr>(Bytes::from(block));
|
| 719 |
}
|
| 720 |
+
if done { break; }
|
| 721 |
}
|
| 722 |
|
| 723 |
+
if !done && !carry.is_empty() {
|
| 724 |
+
let mut tail = std::mem::take(&mut carry);
|
| 725 |
+
if drop_bytes > 0 {
|
| 726 |
+
if drop_bytes < tail.len() {
|
| 727 |
+
tail = tail.split_off(drop_bytes);
|
| 728 |
+
drop_bytes = 0;
|
| 729 |
+
} else {
|
| 730 |
+
tail.clear();
|
| 731 |
+
}
|
| 732 |
+
}
|
| 733 |
+
if !tail.is_empty() {
|
| 734 |
+
if let Some(rem) = remaining {
|
| 735 |
+
if rem > 0 {
|
| 736 |
+
if (tail.len() as u64) > rem {
|
| 737 |
+
tail.truncate(rem as usize);
|
| 738 |
+
}
|
| 739 |
+
yield Ok::<Bytes, BoxErr>(Bytes::from(tail));
|
| 740 |
+
}
|
| 741 |
+
} else {
|
| 742 |
+
yield Ok::<Bytes, BoxErr>(Bytes::from(tail));
|
| 743 |
+
}
|
| 744 |
+
}
|
| 745 |
}
|
| 746 |
};
|
| 747 |
|
| 748 |
let body = Body::from_stream(out);
|
| 749 |
+
let mut resp = axum::response::Response::new(body);
|
| 750 |
+
resp.headers_mut()
|
| 751 |
+
.insert(CONTENT_TYPE, HeaderValue::from_static("audio/mpeg"));
|
| 752 |
+
resp.headers_mut()
|
| 753 |
+
.insert(ACCEPT_RANGES, HeaderValue::from_static("bytes"));
|
| 754 |
+
|
| 755 |
+
if let Some((start, end)) = req_range {
|
| 756 |
+
if let Some(total) = upstream_total {
|
| 757 |
+
let end_for_hdr = end.unwrap_or_else(|| total.saturating_sub(1));
|
| 758 |
+
if end_for_hdr >= start {
|
| 759 |
+
let _ = resp.headers_mut().insert(
|
| 760 |
+
CONTENT_RANGE,
|
| 761 |
+
HeaderValue::from_str(&format!("bytes {}-{}/{}", start, end_for_hdr, total))
|
| 762 |
+
.unwrap_or_else(|_| HeaderValue::from_static("bytes 0-0/*")),
|
| 763 |
+
);
|
| 764 |
+
let len = end_for_hdr.saturating_sub(start).saturating_add(1);
|
| 765 |
+
let _ = resp
|
| 766 |
+
.headers_mut()
|
| 767 |
+
.insert(CONTENT_LENGTH, HeaderValue::from_str(&len.to_string()).unwrap());
|
| 768 |
+
*resp.status_mut() = StatusCode::PARTIAL_CONTENT;
|
| 769 |
+
}
|
| 770 |
+
} else if let Some(end_for_hdr) = end {
|
| 771 |
+
if end_for_hdr >= start {
|
| 772 |
+
let _ = resp.headers_mut().insert(
|
| 773 |
+
CONTENT_RANGE,
|
| 774 |
+
HeaderValue::from_str(&format!("bytes {}-{}/{}", start, end_for_hdr, "*"))
|
| 775 |
+
.unwrap_or_else(|_| HeaderValue::from_static("bytes 0-0/*")),
|
| 776 |
+
);
|
| 777 |
+
let len = end_for_hdr.saturating_sub(start).saturating_add(1);
|
| 778 |
+
let _ = resp
|
| 779 |
+
.headers_mut()
|
| 780 |
+
.insert(CONTENT_LENGTH, HeaderValue::from_str(&len.to_string()).unwrap());
|
| 781 |
+
*resp.status_mut() = StatusCode::PARTIAL_CONTENT;
|
| 782 |
+
}
|
| 783 |
+
}
|
| 784 |
+
}
|
| 785 |
+
|
| 786 |
+
resp
|
| 787 |
}
|
| 788 |
|
| 789 |
#[derive(Debug, Deserialize)]
|
|
|
|
| 1688 |
.route("/get_url", post(get_url))
|
| 1689 |
.route("/fetch", get(fetch))
|
| 1690 |
.route("/stream", get(stream))
|
| 1691 |
+
.route("/stream_info", get(stream_info))
|
| 1692 |
.route("/user_data", post(user_data))
|
| 1693 |
.route("/playlists", post(playlists))
|
| 1694 |
.route("/playlist", post(playlist_tracks))
|