Add CMS waveform pre-processing panel with backfill endpoints

GET api/track/waveform-status and POST api/track/{id}/waveform (ApiKey);
CmsTrackService methods; TrackPreProcessing page with per-row and
sequential bulk generation; nav links from TrackList and Index.
This commit is contained in:
daniel-c-harvey
2026-06-05 17:56:25 -04:00
parent 1b493434d6
commit 6e25ad3085
8 changed files with 418 additions and 7 deletions
@@ -72,6 +72,39 @@ public class TrackController : ControllerBase
return Ok(result.Value);
}
// GET api/track/waveform-status ([ApiKeyAuthorize])
// Admin backfill view: returns every track with a flag for whether a waveform profile is
// stored in the WaveformProfiles vault. The catalogue is small enough that the CMS panel reads
// the whole list unpaged. Declared before the parameterized "{trackId}" route so the literal
// segment is never treated as a trackId.
[ApiKeyAuthorize]
[HttpGet("waveform-status")]
public async Task<ActionResult> GetWaveformStatus()
{
var tracks = await _sqlTrackService.GetAll();
if (!tracks.Success || tracks.Value is null)
{
var error = tracks.Messages.FirstOrDefault()?.Message ?? "Unknown error";
_logger.LogError("GetWaveformStatus failed to load tracks: {Error}", error);
return StatusCode(500, "Failed to load tracks");
}
var status = new List<WaveformStatusDto>(tracks.Value.Count);
foreach (var track in tracks.Value)
{
var profile = await _waveformProfileService.GetProfileAsync(track.EntryKey);
status.Add(new WaveformStatusDto
{
TrackId = track.Id,
EntryKey = track.EntryKey,
TrackName = track.TrackName,
HasProfile = profile is not null,
});
}
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.
@@ -375,6 +408,32 @@ public class TrackController : ControllerBase
});
}
// POST api/track/{trackId}/waveform ([ApiKeyAuthorize])
// Admin backfill: compute and store a waveform profile for an existing track from its vault
// audio. trackId is the EntryKey. 404 when no audio is stored under that key; 500 when the
// WAV cannot be decoded or the vault write fails. Used by the CMS PreProcessing panel for
// tracks that predate the WaveformSeeker feature.
[ApiKeyAuthorize]
[HttpPost("{trackId}/waveform")]
public async Task<ActionResult> GenerateWaveform(string trackId)
{
var audio = await _trackContentService.GetAudioBinaryAsync(trackId);
if (audio is null)
{
_logger.LogWarning("GenerateWaveform: no audio in vault for {TrackId}", trackId);
return NotFound();
}
var stored = await _waveformProfileService.ComputeAndStoreAsync(audio.Buffer, trackId);
if (!stored)
{
_logger.LogError("GenerateWaveform: profile computation/storage failed for {TrackId}", trackId);
return StatusCode(500, "Failed to generate waveform profile.");
}
return Ok();
}
[ApiKeyAuthorize]
[HttpPut("{trackId}")]
public async Task<ActionResult> PutTrack(string trackId, [FromBody] AudioBinaryDto track)