recycleactor commited on
Commit
1e154c6
·
verified ·
1 Parent(s): dd124cc

Upload main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +133 -2
src/main.rs CHANGED
@@ -24,6 +24,12 @@ use cbc::Decryptor;
24
  use reqwest::{cookie::Jar, Url};
25
  use reqwest::cookie::CookieStore;
26
  use reqwest::header::ACCEPT;
 
 
 
 
 
 
27
  use std::time::{SystemTime, UNIX_EPOCH};
28
  use tokio::sync::{Mutex, RwLock};
29
  use tokio::time::{timeout, Duration};
@@ -709,7 +715,38 @@ struct StreamParams {
709
  id: u64,
710
  format: Option<String>,
711
  arl: Option<String>,
 
 
 
 
 
712
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
 
714
  async fn stream_info(State(state): State<AppState>, Query(q): Query<StreamParams>) -> impl IntoResponse {
715
  let _ = std::fs::remove_file("debug_dzmedia.log");
@@ -832,7 +869,97 @@ struct SendAudioParams {
832
  title: Option<String>,
833
  performer: Option<String>,
834
  duration: Option<u64>,
 
 
835
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
 
837
  async fn send_audio(
838
  State(state): State<AppState>,
@@ -939,7 +1066,9 @@ async fn send_audio(
939
  bi += 1; pos += bs;
940
  }
941
 
942
- let ext = if used_fmt.contains("FLAC") || content_type.contains("flac") { "flac" } else { "mp3" };
 
 
943
  let mime = if ext == "flac" { "audio/flac" } else { "audio/mpeg" };
944
  let filename = format!("track_{}.{}", q.id, ext);
945
 
@@ -1153,7 +1282,9 @@ async fn download(
1153
  }
1154
 
1155
  // Determine file extension
1156
- let ext = if used_fmt.contains("FLAC") || content_type.contains("flac") { "flac" } else { "mp3" };
 
 
1157
  let filename = format!("track_{}.{}", q.id, ext);
1158
  let ct = if ext == "flac" { "audio/flac" } else { "audio/mpeg" };
1159
 
 
24
  use reqwest::{cookie::Jar, Url};
25
  use reqwest::cookie::CookieStore;
26
  use reqwest::header::ACCEPT;
27
+ use lofty::{
28
+ config::WriteOptions,
29
+ picture::{Picture, PictureType},
30
+ tag::{Accessor, Tag, TagExt, TagType},
31
+ };
32
+ use std::io::Cursor;
33
  use std::time::{SystemTime, UNIX_EPOCH};
34
  use tokio::sync::{Mutex, RwLock};
35
  use tokio::time::{timeout, Duration};
 
715
  id: u64,
716
  format: Option<String>,
717
  arl: Option<String>,
718
+ title: Option<String>,
719
+ performer: Option<String>,
720
+ duration: Option<u64>,
721
+ album: Option<String>,
722
+ cover_url: Option<String>,
723
  }
724
+
725
+ #[derive(Debug, Clone, Default)]
726
+ struct AudioTagMeta {
727
+ title: Option<String>,
728
+ performer: Option<String>,
729
+ album: Option<String>,
730
+ cover_url: Option<String>,
731
+ }
732
+
733
+ fn normalized_opt(value: &Option<String>) -> Option<String> {
734
+ value
735
+ .as_ref()
736
+ .map(|s| s.trim().to_string())
737
+ .filter(|s| !s.is_empty())
738
+ }
739
+
740
+ impl From<&StreamParams> for AudioTagMeta {
741
+ fn from(params: &StreamParams) -> Self {
742
+ Self {
743
+ title: normalized_opt(&params.title),
744
+ performer: normalized_opt(&params.performer),
745
+ album: normalized_opt(&params.album),
746
+ cover_url: normalized_opt(&params.cover_url),
747
+ }
748
+ }
749
+ }
750
 
751
  async fn stream_info(State(state): State<AppState>, Query(q): Query<StreamParams>) -> impl IntoResponse {
752
  let _ = std::fs::remove_file("debug_dzmedia.log");
 
869
  title: Option<String>,
870
  performer: Option<String>,
871
  duration: Option<u64>,
872
+ album: Option<String>,
873
+ cover_url: Option<String>,
874
  }
875
+
876
+ impl From<&SendAudioParams> for AudioTagMeta {
877
+ fn from(params: &SendAudioParams) -> Self {
878
+ Self {
879
+ title: normalized_opt(&params.title),
880
+ performer: normalized_opt(&params.performer),
881
+ album: normalized_opt(&params.album),
882
+ cover_url: normalized_opt(&params.cover_url),
883
+ }
884
+ }
885
+ }
886
+
887
+ async fn fetch_cover_picture(cover_url: &str) -> Option<Picture> {
888
+ let http = reqwest::Client::builder()
889
+ .timeout(Duration::from_secs(20))
890
+ .build()
891
+ .ok()?;
892
+ let resp = http.get(cover_url).send().await.ok()?;
893
+ if !resp.status().is_success() {
894
+ return None;
895
+ }
896
+ let bytes = resp.bytes().await.ok()?;
897
+ let mut picture = Picture::from_reader(&mut Cursor::new(bytes.to_vec())).ok()?;
898
+ picture.set_pic_type(PictureType::CoverFront);
899
+ Some(picture)
900
+ }
901
+
902
+ async fn inject_audio_metadata(mut bytes: Vec<u8>, ext: &str, meta: &AudioTagMeta) -> Vec<u8> {
903
+ let has_text = meta.title.is_some() || meta.performer.is_some() || meta.album.is_some();
904
+ let cover = match meta.cover_url.as_deref() {
905
+ Some(url) => fetch_cover_picture(url).await,
906
+ None => None,
907
+ };
908
+
909
+ if !has_text && cover.is_none() {
910
+ return bytes;
911
+ }
912
+
913
+ let tag_type = if ext.eq_ignore_ascii_case("flac") {
914
+ TagType::VorbisComments
915
+ } else {
916
+ TagType::Id3v2
917
+ };
918
+
919
+ let mut tag = Tag::new(tag_type);
920
+ if let Some(title) = meta.title.clone() {
921
+ tag.set_title(title);
922
+ }
923
+ if let Some(performer) = meta.performer.clone() {
924
+ tag.set_artist(performer);
925
+ }
926
+ if let Some(album) = meta.album.clone() {
927
+ tag.set_album(album);
928
+ }
929
+ if let Some(picture) = cover {
930
+ tag.push_picture(picture);
931
+ }
932
+
933
+ let unique = SystemTime::now()
934
+ .duration_since(UNIX_EPOCH)
935
+ .map(|d| d.as_nanos())
936
+ .unwrap_or(0);
937
+ let temp_path = std::env::temp_dir().join(format!("dzmedia_tagged_{}.{}", unique, ext));
938
+ if let Err(e) = std::fs::write(&temp_path, &bytes) {
939
+ tracing::warn!("metadata temp write failed: {}", e);
940
+ return bytes;
941
+ }
942
+
943
+ let write_options = if ext.eq_ignore_ascii_case("mp3") {
944
+ WriteOptions::new().use_id3v23(true)
945
+ } else {
946
+ WriteOptions::new()
947
+ };
948
+
949
+ let write_result = tag.save_to_path(&temp_path, write_options);
950
+ if let Err(e) = write_result {
951
+ let _ = std::fs::remove_file(&temp_path);
952
+ tracing::warn!("metadata tagging failed: {}", e);
953
+ return bytes;
954
+ }
955
+
956
+ match std::fs::read(&temp_path) {
957
+ Ok(tagged) => bytes = tagged,
958
+ Err(e) => tracing::warn!("metadata temp read failed: {}", e),
959
+ }
960
+ let _ = std::fs::remove_file(&temp_path);
961
+ bytes
962
+ }
963
 
964
  async fn send_audio(
965
  State(state): State<AppState>,
 
1066
  bi += 1; pos += bs;
1067
  }
1068
 
1069
+ let ext = if used_fmt.contains("FLAC") || content_type.contains("flac") { "flac" } else { "mp3" };
1070
+ let meta = AudioTagMeta::from(&q);
1071
+ dec = inject_audio_metadata(dec, ext, &meta).await;
1072
  let mime = if ext == "flac" { "audio/flac" } else { "audio/mpeg" };
1073
  let filename = format!("track_{}.{}", q.id, ext);
1074
 
 
1282
  }
1283
 
1284
  // Determine file extension
1285
+ let ext = if used_fmt.contains("FLAC") || content_type.contains("flac") { "flac" } else { "mp3" };
1286
+ let meta = AudioTagMeta::from(&q);
1287
+ dec = inject_audio_metadata(dec, ext, &meta).await;
1288
  let filename = format!("track_{}.{}", q.id, ext);
1289
  let ct = if ext == "flac" { "audio/flac" } else { "audio/mpeg" };
1290