recycleactor commited on
Commit
a117d37
·
verified ·
1 Parent(s): f59f4a0

Upload main.rs

Browse files
Files changed (1) hide show
  1. 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 stream(State(state): State<AppState>, Query(q): Query<StreamParams>) -> impl IntoResponse {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 upstream = match http.get(url).send().await {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 mut block_index: u64 = 0;
 
 
 
 
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
- yield Ok::<Bytes, BoxErr>(Bytes::from(std::mem::take(&mut carry)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448
  }
449
  };
450
 
451
  let body = Body::from_stream(out);
452
- (StatusCode::OK, [(CONTENT_TYPE, "audio/mpeg")], body).into_response()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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))