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
+30 -22
View File
@@ -123,7 +123,7 @@
<span class="download-title">@download.Title</span>
<span class="download-date">@download.CreatedAt.ToString("MMM dd, yyyy HH:mm")</span>
</div>
<button class="download-link-btn" @onclick="() => DownloadFile(download.FileName)">
<button class="download-link-btn" @onclick="() => DownloadFile(download.DownloadUrl)">
<span class="download-icon">⬇️</span>
Download MP3
</button>
@@ -475,6 +475,7 @@
{
public string Title { get; set; } = "";
public string FileName { get; set; } = "";
public string DownloadUrl { get; set; } = "";
public DateTime CreatedAt { get; set; }
}
@@ -564,7 +565,7 @@
var result = await ApiService.GetProcessingResultAsync(jobId);
if (result?.Success == true)
{
await CompleteProcessing(result);
await CompleteProcessing(result, jobId);
}
else
{
@@ -603,7 +604,7 @@
}
}
private async Task CompleteProcessing(ProcessVideoResponse result)
private async Task CompleteProcessing(ProcessVideoResponse result, string jobId)
{
try
{
@@ -616,10 +617,24 @@
// Use the actual video title from API
string title = currentVideoDetails?.Title ?? result.VideoDetails?.Title ?? ExtractTitleFromUrl(youtubeUrl);
// Create the download URL - either from the result or construct it from the job ID
string downloadUrl;
if (!string.IsNullOrEmpty(result.DownloadUrl))
{
// If the result contains a direct download URL, use it
downloadUrl = result.DownloadUrl.StartsWith("http") ? result.DownloadUrl : $"https://localhost:7160{result.DownloadUrl}";
}
else
{
// Fallback: construct download URL from job ID
downloadUrl = $"https://localhost:7160/api/download/{jobId}";
}
downloadLinks.Add(new DownloadItem
{
Title = title,
FileName = result.FileName ?? "sample.mp3",
DownloadUrl = downloadUrl,
CreatedAt = DateTime.Now
});
@@ -655,9 +670,19 @@
return $"Audio File {DateTime.Now:HHmmss}";
}
private async Task DownloadFile(string fileName)
private async Task DownloadFile(string downloadUrl)
{
await JSRuntime.InvokeVoidAsync("downloadFile", fileName);
try
{
// Use the browser to navigate directly to the download URL
await JSRuntime.InvokeVoidAsync("open", downloadUrl, "_blank");
}
catch (Exception ex)
{
Logger.LogError(ex, "Error downloading file from URL: {DownloadUrl}", downloadUrl);
apiErrorMessage = $"Error downloading file: {ex.Message}";
StateHasChanged();
}
}
private async Task HandleKeyPress(KeyboardEventArgs e)
@@ -667,21 +692,4 @@
await DownloadMp3();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("eval", @"
window.downloadFile = function(fileName) {
const link = document.createElement('a');
link.href = fileName;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
");
}
}
}