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
This commit is contained in:
Michal Vrba
2025-12-12 09:54:59 +01:00
parent 3a5de85cd4
commit 406cf31b49
8 changed files with 660 additions and 65 deletions
+28 -20
View File
@@ -22,6 +22,7 @@ public class VideoController : ControllerBase
public DownloadProgress Progress { get; set; } = new();
public ProcessVideoResponse? Result { get; set; }
public YouTubeVideoDetails? VideoDetails { get; set; } // Add video details to job
public string? DownloadedFileName { get; set; } // Store actual downloaded file name
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
@@ -165,7 +166,7 @@ public class VideoController : ControllerBase
{
_logger.LogInformation("Processing video for job {JobId}", jobId);
// Step 1: Fetch video details (NEW FIRST STEP)
// Step 1: Fetch video details
job.Progress.Status = "Processing";
job.Progress.Message = "Fetching video details...";
job.Progress.PercentageComplete = 5;
@@ -186,41 +187,48 @@ public class VideoController : ControllerBase
_logger.LogInformation("Job {JobId}: Video details retrieved - Title: {Title}", jobId, videoDetails.Title);
// Continue with remaining processing steps
var remainingSteps = new[]
{
("Analyzing audio stream", 30),
("Downloading audio data", 60),
("Converting to MP3", 85),
("Finalizing", 100)
};
// Step 2: Process video with yt-dlp
job.Progress.Message = "Starting download and conversion...";
job.Progress.PercentageComplete = 20;
var downloadedFileName = await _videoService.ProcessVideoWithProgressAsync(
job.Request.Url!,
job.Request.Format ?? "mp3",
(percentage, message) =>
{
// Update progress from yt-dlp
job.Progress.PercentageComplete = Math.Max(20, percentage); // Ensure minimum 20%
job.Progress.Message = message;
_logger.LogDebug("Job {JobId}: Progress {Percentage}% - {Message}", jobId, percentage, message);
}
);
foreach (var (message, percentage) in remainingSteps)
if (string.IsNullOrEmpty(downloadedFileName))
{
job.Progress.Message = message;
job.Progress.PercentageComplete = percentage;
_logger.LogInformation("Job {JobId}: {Message} ({Percentage}%)", jobId, message, percentage);
// Simulate processing time
await Task.Delay(Random.Shared.Next(1000, 3000));
throw new Exception("Processing completed but no file was generated");
}
// Store the downloaded file name
job.DownloadedFileName = downloadedFileName;
// Complete processing
job.Progress.Status = "Completed";
job.Progress.Message = "Processing completed successfully";
job.Progress.PercentageComplete = 100;
// Create download URL for the actual file
var downloadUrl = $"/downloads/{Uri.EscapeDataString(downloadedFileName)}";
job.Result = new ProcessVideoResponse
{
Success = true,
Message = "Video processed successfully",
FileName = $"{videoDetails.Title?.Replace(" ", "_") ?? "video"}.{job.Request.Format ?? "mp3"}",
DownloadUrl = $"/api/download/{jobId}",
FileName = downloadedFileName,
DownloadUrl = downloadUrl,
VideoDetails = videoDetails
};
_logger.LogInformation("Successfully completed processing for job {JobId}", jobId);
_logger.LogInformation("Successfully completed processing for job {JobId}. File: {FileName}", jobId, downloadedFileName);
}
catch (Exception ex)
{