feat(audio): add MP3 and FLAC upload support via format-routed processors
AudioProcessorRouter dispatches by extension; vault stores original bytes with correct MIME type.
This commit is contained in:
@@ -166,11 +166,12 @@ public class TrackController : ControllerBase
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
// POST api/track/upload: raw WAV in (multipart/form-data) + metadata → persisted TrackDto out.
|
||||
// Used by the CMS upload flow on DeepDrftManager; that host proxies the upload here so it never
|
||||
// touches the vault disk path or SQL directly. UnifiedTrackService owns the two-database write.
|
||||
// POST api/track/upload: raw audio in (multipart/form-data) + metadata → persisted TrackDto out.
|
||||
// Accepts .wav, .mp3, and .flac. Used by the CMS upload flow on DeepDrftManager; that host
|
||||
// proxies the upload here so it never touches the vault disk path or SQL directly.
|
||||
// UnifiedTrackService owns the two-database write.
|
||||
//
|
||||
// RequestSizeLimit/MultipartBodyLengthLimit set to 1 GB: WAV uploads can be tens to hundreds
|
||||
// RequestSizeLimit/MultipartBodyLengthLimit set to 1 GB: audio uploads can be tens to hundreds
|
||||
// of MB and the framework defaults (~28 MB) reject them outright. The IFormFile path streams
|
||||
// the body to a temp file once Kestrel surfaces it, so the limit is the per-request ceiling,
|
||||
// not a buffered allocation.
|
||||
@@ -179,7 +180,7 @@ public class TrackController : ControllerBase
|
||||
[RequestSizeLimit(1_073_741_824)]
|
||||
[RequestFormLimits(MultipartBodyLengthLimit = 1_073_741_824)]
|
||||
public async Task<ActionResult<DeepDrftModels.DTOs.TrackDto>> UploadTrack(
|
||||
[FromForm] IFormFile? wav,
|
||||
[FromForm] IFormFile? audioFile,
|
||||
[FromForm] string? trackName,
|
||||
[FromForm] string? artist,
|
||||
[FromForm] string? album,
|
||||
@@ -190,11 +191,11 @@ public class TrackController : ControllerBase
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("UploadTrack called: trackName={TrackName}, artist={Artist}, fileName={FileName}, size={Size}",
|
||||
trackName, artist, originalFileName, wav?.Length);
|
||||
trackName, artist, originalFileName, audioFile?.Length);
|
||||
|
||||
if (wav is null || wav.Length == 0)
|
||||
if (audioFile is null || audioFile.Length == 0)
|
||||
{
|
||||
return BadRequest("WAV file is required");
|
||||
return BadRequest("Audio file is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trackName))
|
||||
@@ -207,9 +208,10 @@ public class TrackController : ControllerBase
|
||||
return BadRequest("artist is required");
|
||||
}
|
||||
|
||||
if (!string.Equals(Path.GetExtension(wav.FileName), ".wav", StringComparison.OrdinalIgnoreCase))
|
||||
var uploadExtension = Path.GetExtension(audioFile.FileName).ToLowerInvariant();
|
||||
if (uploadExtension is not (".wav" or ".mp3" or ".flac"))
|
||||
{
|
||||
return BadRequest("Uploaded file must have a .wav extension");
|
||||
return BadRequest("Uploaded file must have a .wav, .mp3, or .flac extension");
|
||||
}
|
||||
|
||||
DateOnly? parsedReleaseDate = null;
|
||||
@@ -222,16 +224,17 @@ public class TrackController : ControllerBase
|
||||
parsedReleaseDate = parsed;
|
||||
}
|
||||
|
||||
// AudioProcessor.ProcessWavFileAsync requires a path ending in .wav and reads from disk.
|
||||
// Path.GetTempFileName() yields .tmp, which fails that check — generate our own .wav path.
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".wav");
|
||||
// The processor router selects by extension and reads from disk, so the temp file must carry
|
||||
// the upload's real extension. Path.GetTempFileName() yields .tmp, which the router rejects —
|
||||
// generate our own path preserving the validated .wav/.mp3/.flac extension.
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + uploadExtension);
|
||||
|
||||
try
|
||||
{
|
||||
await using (var tempStream = new FileStream(
|
||||
tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None,
|
||||
bufferSize: 81920, useAsync: true))
|
||||
await using (var uploadStream = wav.OpenReadStream())
|
||||
await using (var uploadStream = audioFile.OpenReadStream())
|
||||
{
|
||||
await uploadStream.CopyToAsync(tempStream, cancellationToken);
|
||||
}
|
||||
@@ -249,7 +252,7 @@ public class TrackController : ControllerBase
|
||||
|
||||
if (!result.Success || result.Value is null)
|
||||
{
|
||||
var error = result.Messages.FirstOrDefault()?.Message ?? "Failed to process and store WAV";
|
||||
var error = result.Messages.FirstOrDefault()?.Message ?? "Failed to process and store audio";
|
||||
_logger.LogWarning("UploadTrack: UnifiedTrackService failed for {TrackName}: {Error}", trackName, error);
|
||||
return StatusCode(500, error);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,10 @@ public class UnifiedTrackService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a WAV into the vault, then persist its metadata to SQL. On success the returned
|
||||
/// DTO carries the SQL-assigned Id. If the vault write succeeds but the SQL persist fails,
|
||||
/// the audio is orphaned under EntryKey — logged loudly so it is recoverable manually.
|
||||
/// Process a supported audio file (.wav, .mp3, .flac) into the vault, then persist its metadata
|
||||
/// to SQL. On success the returned DTO carries the SQL-assigned Id. If the vault write succeeds
|
||||
/// but the SQL persist fails, the audio is orphaned under EntryKey — logged loudly so it is
|
||||
/// recoverable manually.
|
||||
/// </summary>
|
||||
public async Task<ResultContainer<TrackDto>> UploadAsync(
|
||||
string tempFilePath,
|
||||
@@ -52,7 +53,7 @@ public class UnifiedTrackService
|
||||
string? originalFileName,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var unpersisted = await _contentTrackContentService.AddTrackFromWavAsync(
|
||||
var unpersisted = await _contentTrackContentService.AddTrackAsync(
|
||||
tempFilePath, trackName, artist, album, genre, releaseDate, originalFileName: originalFileName);
|
||||
|
||||
if (unpersisted is null)
|
||||
|
||||
@@ -15,6 +15,9 @@ namespace DeepDrftAPI
|
||||
{
|
||||
// Audio services
|
||||
builder.Services.AddSingleton<AudioProcessor>();
|
||||
builder.Services.AddSingleton<Mp3AudioProcessor>();
|
||||
builder.Services.AddSingleton<FlacAudioProcessor>();
|
||||
builder.Services.AddSingleton<AudioProcessorRouter>();
|
||||
builder.Services.AddSingleton<TrackContentService>();
|
||||
|
||||
// Image services
|
||||
|
||||
Reference in New Issue
Block a user