Spaces:
Running
Running
Upload soundcloud.rs
Browse files- src/soundcloud.rs +18 -104
src/soundcloud.rs
CHANGED
|
@@ -133,13 +133,9 @@ pub async fn download(Query(params): Query<DownloadParams>) -> impl IntoResponse
|
|
| 133 |
|
| 134 |
let quality = params.quality.as_deref().unwrap_or("best");
|
| 135 |
|
| 136 |
-
//
|
| 137 |
-
//
|
| 138 |
-
let format_arg =
|
| 139 |
-
"256" => "bestaudio[ext=m4a]/bestaudio[ext=aac]/bestaudio[abr<=256]/bestaudio",
|
| 140 |
-
"128" => "bestaudio[abr<=128]/bestaudio",
|
| 141 |
-
_ => "bestaudio[ext=m4a]/bestaudio[ext=aac]/bestaudio", // Приоритет M4A/AAC
|
| 142 |
-
};
|
| 143 |
|
| 144 |
tracing::info!("Using format selector: {}", format_arg);
|
| 145 |
|
|
@@ -225,22 +221,14 @@ pub async fn download(Query(params): Query<DownloadParams>) -> impl IntoResponse
|
|
| 225 |
let actual_file = if tokio::fs::metadata(&temp_file).await.is_ok() {
|
| 226 |
temp_file.clone()
|
| 227 |
} else {
|
| 228 |
-
//
|
| 229 |
-
let
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
}
|
| 237 |
-
}
|
| 238 |
-
match found {
|
| 239 |
-
Some(p) => p,
|
| 240 |
-
None => {
|
| 241 |
-
tracing::error!("Could not find downloaded file with any extension");
|
| 242 |
-
return (StatusCode::INTERNAL_SERVER_ERROR, "File not found").into_response();
|
| 243 |
-
}
|
| 244 |
}
|
| 245 |
};
|
| 246 |
|
|
@@ -254,92 +242,18 @@ pub async fn download(Query(params): Query<DownloadParams>) -> impl IntoResponse
|
|
| 254 |
}
|
| 255 |
};
|
| 256 |
|
| 257 |
-
// Определяем расширение оригинального файла
|
| 258 |
-
let orig_extension = if actual_file.ends_with(".m4a") {
|
| 259 |
-
"m4a"
|
| 260 |
-
} else if actual_file.ends_with(".opus") {
|
| 261 |
-
"opus"
|
| 262 |
-
} else if actual_file.ends_with(".webm") {
|
| 263 |
-
"webm"
|
| 264 |
-
} else if actual_file.ends_with(".aac") {
|
| 265 |
-
"aac"
|
| 266 |
-
} else {
|
| 267 |
-
"mp3"
|
| 268 |
-
};
|
| 269 |
-
|
| 270 |
-
// Определяем тип контента по расширению
|
| 271 |
-
let content_type = if actual_file.ends_with(".m4a") || actual_file.ends_with(".aac") {
|
| 272 |
-
"audio/mp4" // AAC обычно в контейнере M4A
|
| 273 |
-
} else if actual_file.ends_with(".opus") {
|
| 274 |
-
"audio/opus"
|
| 275 |
-
} else if actual_file.ends_with(".webm") {
|
| 276 |
-
"audio/webm"
|
| 277 |
-
} else {
|
| 278 |
-
"audio/mpeg"
|
| 279 |
-
};
|
| 280 |
-
|
| 281 |
-
// Telegram Bot API плохо работает с некоторыми форматами по URL.
|
| 282 |
-
// Конвертируем OPUS, WEBM и M4A в MP3 для совместимости.
|
| 283 |
-
let (final_data, final_content_type, final_extension) =
|
| 284 |
-
if actual_file.ends_with(".opus") || actual_file.ends_with(".webm") || actual_file.ends_with(".m4a") || actual_file.ends_with(".aac") {
|
| 285 |
-
let target_format = if actual_file.ends_with(".m4a") || actual_file.ends_with(".aac") {
|
| 286 |
-
"AAC/M4A"
|
| 287 |
-
} else if actual_file.ends_with(".opus") {
|
| 288 |
-
"OPUS"
|
| 289 |
-
} else {
|
| 290 |
-
"WEBM"
|
| 291 |
-
};
|
| 292 |
-
tracing::info!("Converting {} to MP3 320k for Telegram compatibility", target_format);
|
| 293 |
-
|
| 294 |
-
let mp3_file = format!("{}.mp3", temp_file);
|
| 295 |
-
let convert_output = Command::new("ffmpeg")
|
| 296 |
-
.args([
|
| 297 |
-
"-i", &actual_file,
|
| 298 |
-
"-vn",
|
| 299 |
-
"-ar", "44100",
|
| 300 |
-
"-ac", "2",
|
| 301 |
-
"-b:a", "320k",
|
| 302 |
-
&mp3_file,
|
| 303 |
-
])
|
| 304 |
-
.stdout(Stdio::null())
|
| 305 |
-
.stderr(Stdio::null())
|
| 306 |
-
.output()
|
| 307 |
-
.await;
|
| 308 |
-
|
| 309 |
-
match convert_output {
|
| 310 |
-
Ok(out) if out.status.success() => {
|
| 311 |
-
if let Ok(mp3_data) = tokio::fs::read(&mp3_file).await {
|
| 312 |
-
let _ = tokio::fs::remove_file(&mp3_file).await;
|
| 313 |
-
(mp3_data, "audio/mpeg", "mp3")
|
| 314 |
-
} else {
|
| 315 |
-
// Если к��нвертация не удалась, отдаём оригинал
|
| 316 |
-
(audio_data, content_type, orig_extension)
|
| 317 |
-
}
|
| 318 |
-
}
|
| 319 |
-
_ => {
|
| 320 |
-
// Если конвертация не удалась, отдаём оригинал
|
| 321 |
-
(audio_data, content_type, orig_extension)
|
| 322 |
-
}
|
| 323 |
-
}
|
| 324 |
-
} else {
|
| 325 |
-
(audio_data, content_type, orig_extension)
|
| 326 |
-
};
|
| 327 |
-
|
| 328 |
-
// Определяем расширение для имени файла
|
| 329 |
-
let extension = final_extension;
|
| 330 |
-
|
| 331 |
// Удаляем временный файл
|
| 332 |
let _ = tokio::fs::remove_file(&actual_file).await;
|
| 333 |
|
| 334 |
// Формируем имя файла
|
| 335 |
let filename = if let Some(ref title) = params.title {
|
| 336 |
if let Some(ref performer) = params.performer {
|
| 337 |
-
format!("{} - {}.
|
| 338 |
} else {
|
| 339 |
-
format!("{}.
|
| 340 |
}
|
| 341 |
} else {
|
| 342 |
-
|
| 343 |
};
|
| 344 |
|
| 345 |
let filename_safe = filename
|
|
@@ -348,12 +262,12 @@ pub async fn download(Query(params): Query<DownloadParams>) -> impl IntoResponse
|
|
| 348 |
.take(200)
|
| 349 |
.collect::<String>();
|
| 350 |
|
| 351 |
-
tracing::info!("Sending SoundCloud file: {} ({} bytes,
|
| 352 |
|
| 353 |
let mut response = axum::response::Response::builder()
|
| 354 |
.status(200)
|
| 355 |
-
.header("Content-Type",
|
| 356 |
-
.header("Content-Length",
|
| 357 |
.header("Content-Disposition", format!("inline; filename=\"{}\"", filename_safe))
|
| 358 |
.header("Accept-Ranges", "bytes")
|
| 359 |
.header("Access-Control-Allow-Origin", "*")
|
|
@@ -365,7 +279,7 @@ pub async fn download(Query(params): Query<DownloadParams>) -> impl IntoResponse
|
|
| 365 |
}
|
| 366 |
|
| 367 |
response
|
| 368 |
-
.body(axum::body::Body::from(
|
| 369 |
.unwrap()
|
| 370 |
.into_response()
|
| 371 |
}
|
|
|
|
| 133 |
|
| 134 |
let quality = params.quality.as_deref().unwrap_or("best");
|
| 135 |
|
| 136 |
+
// Скачиваем ТОЛЬКО MP3 в максимальном доступном качестве
|
| 137 |
+
// SoundCloud обычно отдаёт 128kbps MP3 (стандарт) или 256kbps (Go+, редко)
|
| 138 |
+
let format_arg = "bestaudio[ext=mp3]/bestaudio";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
tracing::info!("Using format selector: {}", format_arg);
|
| 141 |
|
|
|
|
| 221 |
let actual_file = if tokio::fs::metadata(&temp_file).await.is_ok() {
|
| 222 |
temp_file.clone()
|
| 223 |
} else {
|
| 224 |
+
// Ищем файл с расширением .mp3
|
| 225 |
+
let mp3_path = format!("{}.mp3", temp_file);
|
| 226 |
+
if tokio::fs::metadata(&mp3_path).await.is_ok() {
|
| 227 |
+
tracing::info!("Found downloaded MP3 file");
|
| 228 |
+
mp3_path
|
| 229 |
+
} else {
|
| 230 |
+
tracing::error!("Could not find downloaded MP3 file");
|
| 231 |
+
return (StatusCode::INTERNAL_SERVER_ERROR, "File not found").into_response();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
}
|
| 233 |
};
|
| 234 |
|
|
|
|
| 242 |
}
|
| 243 |
};
|
| 244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
// Удаляем временный файл
|
| 246 |
let _ = tokio::fs::remove_file(&actual_file).await;
|
| 247 |
|
| 248 |
// Формируем имя файла
|
| 249 |
let filename = if let Some(ref title) = params.title {
|
| 250 |
if let Some(ref performer) = params.performer {
|
| 251 |
+
format!("{} - {}.mp3", performer, title)
|
| 252 |
} else {
|
| 253 |
+
format!("{}.mp3", title)
|
| 254 |
}
|
| 255 |
} else {
|
| 256 |
+
"soundcloud_track.mp3".to_string()
|
| 257 |
};
|
| 258 |
|
| 259 |
let filename_safe = filename
|
|
|
|
| 262 |
.take(200)
|
| 263 |
.collect::<String>();
|
| 264 |
|
| 265 |
+
tracing::info!("Sending SoundCloud file: {} ({} bytes, audio/mpeg)", filename_safe, audio_data.len());
|
| 266 |
|
| 267 |
let mut response = axum::response::Response::builder()
|
| 268 |
.status(200)
|
| 269 |
+
.header("Content-Type", "audio/mpeg")
|
| 270 |
+
.header("Content-Length", audio_data.len().to_string())
|
| 271 |
.header("Content-Disposition", format!("inline; filename=\"{}\"", filename_safe))
|
| 272 |
.header("Accept-Ranges", "bytes")
|
| 273 |
.header("Access-Control-Allow-Origin", "*")
|
|
|
|
| 279 |
}
|
| 280 |
|
| 281 |
response
|
| 282 |
+
.body(axum::body::Body::from(audio_data))
|
| 283 |
.unwrap()
|
| 284 |
.into_response()
|
| 285 |
}
|