Files
TuneRipper/TuneRipper.Api/Controllers/DownloadController.cs
T
Michal Vrba 406cf31b49 Real YouTube download & MP3 conversion with yt-dlp
- Integrate yt-dlp.exe for real YouTube audio download/conversion
- Serve actual MP3 files from wwwroot/downloads via API and static files
- Report real-time progress from yt-dlp to frontend
- Enhance DownloadController: file serving, metadata, deletion endpoints
- Update VideoController to track and return real file info/URLs
- Enable static file serving in Program.cs; update API info/logging
- Update Blazor frontend to use real download URLs and remove old JS logic
- Add README_UPDATED.md with new workflow and troubleshooting
- Improve error handling, logging, and file cleanup throughout backend
- Update .gitignore for downloads and yt-dlp.exe
2025-12-12 09:54:59 +01:00

222 lines
7.5 KiB
C#

using Microsoft.AspNetCore.Mvc;
namespace TuneRipper.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DownloadController : ControllerBase
{
private readonly IWebHostEnvironment _environment;
private readonly ILogger<DownloadController> _logger;
public DownloadController(IWebHostEnvironment environment, ILogger<DownloadController> logger)
{
_environment = environment;
_logger = logger;
}
[HttpGet("{jobId}")]
public IActionResult DownloadFile(string jobId)
{
if (string.IsNullOrWhiteSpace(jobId))
{
return BadRequest("Job ID is required");
}
try
{
var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads");
if (!Directory.Exists(downloadsDir))
{
_logger.LogWarning("Downloads directory not found: {Path}", downloadsDir);
return NotFound("Downloads directory not found");
}
// Find files related to this job ID
var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*")
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
.OrderByDescending(System.IO.File.GetCreationTime)
.ToArray();
if (jobFiles.Length == 0)
{
// Fallback: look for any audio files that might match the pattern
var audioFiles = Directory.GetFiles(downloadsDir, "*.*")
.Where(f => f.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) ||
f.EndsWith(".m4a", StringComparison.OrdinalIgnoreCase) ||
f.EndsWith(".webm", StringComparison.OrdinalIgnoreCase))
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
.OrderByDescending(System.IO.File.GetCreationTime)
.ToArray();
if (audioFiles.Length == 0)
{
_logger.LogWarning("No files found for job ID: {JobId}", jobId);
return NotFound("File not found");
}
jobFiles = audioFiles.Take(1).ToArray();
}
var filePath = jobFiles[0];
var fileName = Path.GetFileName(filePath);
if (!System.IO.File.Exists(filePath))
{
_logger.LogWarning("File not found: {FilePath}", filePath);
return NotFound("File not found");
}
_logger.LogInformation("Serving file: {FileName} for job: {JobId}", fileName, jobId);
// Determine content type based on file extension
var contentType = Path.GetExtension(fileName).ToLowerInvariant() switch
{
".mp3" => "audio/mpeg",
".m4a" => "audio/mp4",
".webm" => "audio/webm",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
_ => "application/octet-stream"
};
// Create a safe download filename
var safeFileName = fileName;
if (fileName.Contains("_"))
{
// Try to create a more user-friendly filename
var parts = fileName.Split('_');
if (parts.Length >= 2)
{
var extension = Path.GetExtension(fileName);
safeFileName = $"TuneRipper_{parts[0]}{extension}";
}
}
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return File(fileStream, contentType, safeFileName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error downloading file for job ID: {JobId}", jobId);
return StatusCode(500, $"Error downloading file: {ex.Message}");
}
}
[HttpGet("info/{jobId}")]
public IActionResult GetDownloadInfo(string jobId)
{
if (string.IsNullOrWhiteSpace(jobId))
{
return BadRequest("Job ID is required");
}
try
{
var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads");
if (!Directory.Exists(downloadsDir))
{
return NotFound("Downloads directory not found");
}
// Find files related to this job ID
var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*")
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
.OrderByDescending(System.IO.File.GetCreationTime)
.ToArray();
if (jobFiles.Length == 0)
{
return NotFound("File not found");
}
var filePath = jobFiles[0];
var fileName = Path.GetFileName(filePath);
var fileInfo = new FileInfo(filePath);
// Return download metadata
var info = new
{
jobId,
fileName,
fileSize = FormatFileSize(fileInfo.Length),
fileSizeBytes = fileInfo.Length,
format = Path.GetExtension(fileName).TrimStart('.').ToUpperInvariant(),
createdAt = fileInfo.CreationTime,
lastModified = fileInfo.LastWriteTime,
downloadUrl = $"/api/download/{jobId}"
};
return Ok(info);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting download info for job ID: {JobId}", jobId);
return StatusCode(500, $"Error getting download info: {ex.Message}");
}
}
[HttpDelete("{jobId}")]
public IActionResult DeleteFile(string jobId)
{
if (string.IsNullOrWhiteSpace(jobId))
{
return BadRequest("Job ID is required");
}
try
{
var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads");
if (!Directory.Exists(downloadsDir))
{
return NotFound("Downloads directory not found");
}
// Find and delete files related to this job ID
var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*");
var deletedCount = 0;
foreach (var filePath in jobFiles)
{
try
{
System.IO.File.Delete(filePath);
deletedCount++;
_logger.LogInformation("Deleted file: {FilePath}", filePath);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to delete file: {FilePath}", filePath);
}
}
if (deletedCount == 0)
{
return NotFound("No files found to delete");
}
return Ok(new { message = $"Deleted {deletedCount} file(s)", deletedCount });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting files for job ID: {JobId}", jobId);
return StatusCode(500, $"Error deleting files: {ex.Message}");
}
}
private static string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
}