Refactor: Use jobId_title.ext for downloads & improve UX
Adopt GUID-based {jobId}_{title}.ext filename convention for all downloads, ensuring unique, human-readable, and traceable files. Update DownloadController to use precise jobId-based file matching and generate user-friendly download names. Pass jobId to VideoService for consistent naming; add robust title sanitization. Remove fallback file search logic for predictability. Update README and API docs to reflect new format and workflow. Improve logging and error messages for clarity.
This commit is contained in:
@@ -13,7 +13,7 @@ public interface IVideoService
|
||||
Task<YouTubeVideoDetails?> GetVideoDetailsAsync(string url);
|
||||
bool IsValidYouTubeUrl(string url);
|
||||
string ExtractVideoId(string url);
|
||||
Task<string?> ProcessVideoWithProgressAsync(string url, string format, Action<int, string> progressCallback);
|
||||
Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, Action<int, string> progressCallback);
|
||||
}
|
||||
|
||||
public class VideoService : IVideoService
|
||||
@@ -90,7 +90,7 @@ public class VideoService : IVideoService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> ProcessVideoWithProgressAsync(string url, string format, Action<int, string> progressCallback)
|
||||
public async Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, Action<int, string> progressCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -105,10 +105,15 @@ public class VideoService : IVideoService
|
||||
var downloadDir = Path.Combine(_environment.WebRootPath, "downloads");
|
||||
Directory.CreateDirectory(downloadDir);
|
||||
|
||||
// Generate safe filename
|
||||
var videoId = ExtractVideoId(url);
|
||||
var fileName = $"{videoId}_{DateTime.Now:yyyyMMdd_HHmmss}.%(ext)s";
|
||||
var outputTemplate = Path.Combine(downloadDir, fileName);
|
||||
// Get video details first for better filename
|
||||
var videoDetails = await GetVideoDetailsAsync(url);
|
||||
|
||||
// Generate filename with format: jobId_title.mp3
|
||||
var safeTitle = SanitizeFileName(videoDetails?.Title ?? "video");
|
||||
var customFileName = $"{jobId}_{safeTitle}";
|
||||
var outputTemplate = Path.Combine(downloadDir, $"{customFileName}.%(ext)s");
|
||||
|
||||
_logger.LogInformation("Processing video with filename pattern: {FileName}", customFileName);
|
||||
|
||||
// Build yt-dlp arguments
|
||||
var arguments = new StringBuilder();
|
||||
@@ -183,21 +188,21 @@ public class VideoService : IVideoService
|
||||
throw new Exception($"yt-dlp failed with exit code {process.ExitCode}. Error: {error}");
|
||||
}
|
||||
|
||||
// Find the downloaded file
|
||||
var downloadedFiles = Directory.GetFiles(downloadDir, $"{videoId}_*.*")
|
||||
// Find the downloaded file with our job ID pattern
|
||||
var downloadedFiles = Directory.GetFiles(downloadDir, $"{jobId}_*.*")
|
||||
.Where(f => !f.EndsWith(".part"))
|
||||
.OrderByDescending(File.GetCreationTime)
|
||||
.OrderByDescending(System.IO.File.GetCreationTime)
|
||||
.ToArray();
|
||||
|
||||
if (downloadedFiles.Length == 0)
|
||||
{
|
||||
throw new Exception("No downloaded file found after processing");
|
||||
throw new Exception($"No downloaded file found for job {jobId} after processing");
|
||||
}
|
||||
|
||||
var downloadedFile = downloadedFiles[0];
|
||||
var finalFileName = Path.GetFileName(downloadedFile);
|
||||
|
||||
_logger.LogInformation("Successfully processed video. File: {FileName}", finalFileName);
|
||||
_logger.LogInformation("Successfully processed video. Job: {JobId}, File: {FileName}", jobId, finalFileName);
|
||||
|
||||
progressCallback(100, "Processing completed successfully");
|
||||
|
||||
@@ -205,12 +210,75 @@ public class VideoService : IVideoService
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing video with yt-dlp: {Url}", url);
|
||||
_logger.LogError(ex, "Error processing video with yt-dlp for job {JobId}: {Url}", jobId, url);
|
||||
progressCallback(0, $"Processing failed: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string SanitizeFileName(string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
return "video";
|
||||
}
|
||||
|
||||
// Remove or replace invalid filename characters
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sanitized = new StringBuilder();
|
||||
|
||||
foreach (char c in fileName.Take(50)) // Limit length to 50 characters
|
||||
{
|
||||
if (invalidChars.Contains(c))
|
||||
{
|
||||
// Replace invalid characters with underscores or safe alternatives
|
||||
switch (c)
|
||||
{
|
||||
case '/':
|
||||
case '\\':
|
||||
case '?':
|
||||
case '*':
|
||||
sanitized.Append('_');
|
||||
break;
|
||||
case ':':
|
||||
case '|':
|
||||
sanitized.Append('-');
|
||||
break;
|
||||
case '<':
|
||||
sanitized.Append('(');
|
||||
break;
|
||||
case '>':
|
||||
sanitized.Append(')');
|
||||
break;
|
||||
case '"':
|
||||
sanitized.Append('\'');
|
||||
break;
|
||||
default:
|
||||
sanitized.Append('_');
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (char.IsWhiteSpace(c))
|
||||
{
|
||||
sanitized.Append('_');
|
||||
}
|
||||
else
|
||||
{
|
||||
sanitized.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
var result = sanitized.ToString().Trim('_');
|
||||
|
||||
// Ensure we have a valid filename
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
result = "video";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ParseProgressFromOutput(string output, Action<int, string> progressCallback)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user