feat(waveform): generalize high-res compute to every track (Direction B)

Per-track high-res datum keyed by EntryKey in the renamed track-waveforms vault; computed at upload for all tracks, regenerable per-track via CMS, with a re-runnable backfill. Mix read path repointed so it keeps working.
This commit is contained in:
daniel-c-harvey
2026-06-17 10:18:44 -04:00
parent ad94354632
commit accf20ba57
16 changed files with 612 additions and 155 deletions
@@ -19,21 +19,38 @@
@if (VM.Mode == BrowseMode.Tracks)
{
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.AutoFixHigh"
Disabled="@(_bulkRunning || (_grid?.GetMissingCount() ?? 0) == 0)"
OnClick="GenerateAllMissingAsync">
@if (_bulkRunning)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Generating @_bulkDone / @_bulkTotal…</span>
}
else
{
<span>Generate All Missing (@(_grid?.GetMissingCount() ?? 0))</span>
}
</MudButton>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.AutoFixHigh"
Disabled="@(_bulkRunning || _highResBulkRunning || (_grid?.GetMissingCount() ?? 0) == 0)"
OnClick="GenerateAllMissingAsync">
@if (_bulkRunning)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Generating @_bulkDone / @_bulkTotal…</span>
}
else
{
<span>Generate All Profiles (@(_grid?.GetMissingCount() ?? 0))</span>
}
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Waves"
Disabled="@(_bulkRunning || _highResBulkRunning || (_grid?.GetMissingHighResCount() ?? 0) == 0)"
OnClick="GenerateAllMissingHighResAsync">
@if (_highResBulkRunning)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Backfilling @_highResBulkDone / @_highResBulkTotal…</span>
}
else
{
<span>Backfill High-res (@(_grid?.GetMissingHighResCount() ?? 0))</span>
}
</MudButton>
</MudStack>
}
</MudStack>
@@ -147,11 +164,17 @@
StateHasChanged();
}
// Local state for the parent-owned "Generate All Missing" bulk run.
// Local state for the parent-owned "Generate All Profiles" bulk run.
private bool _bulkRunning;
private int _bulkTotal;
private int _bulkDone;
// Local state for the parent-owned "Backfill High-res" bulk run (phase-12 §8a-new). Independent of
// the profile bulk above; both disable the grid's per-row buttons while either runs.
private bool _highResBulkRunning;
private int _highResBulkTotal;
private int _highResBulkDone;
protected override async Task OnInitializedAsync()
{
// /tracks/archive and /tracks/albums both land on the Releases view (the tab strip); the old
@@ -248,4 +271,71 @@
Snackbar.Add($"Generated {succeeded} profile(s); {failures} failed.", Severity.Warning);
}
}
/// <summary>
/// Backfill the per-track high-res visualizer datum (phase-12 §5) for every track missing one, one
/// request at a time so a large backfill does not flood the API with concurrent WAV decodes. This is
/// the §8a-new backfill mechanism over the generalized track generate action — re-runnable (a second
/// run re-reads status and only retries what is still missing). On completion, refreshes the grid's
/// status maps so the per-row icons reflect the new state.
/// </summary>
private async Task GenerateAllMissingHighResAsync()
{
var statusResult = await CmsTrackService.GetWaveformStatusAsync();
if (!statusResult.Success || statusResult.Value is null)
{
var error = statusResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
Snackbar.Add($"Failed to load waveform status: {error}", Severity.Error);
return;
}
var missing = statusResult.Value.Where(s => !s.HasHighRes).ToList();
if (missing.Count == 0)
{
return;
}
_highResBulkRunning = true;
_highResBulkTotal = missing.Count;
_highResBulkDone = 0;
_grid?.SetBulkRunning(true);
var failures = 0;
foreach (var status in missing)
{
try
{
var result = await CmsTrackService.GenerateHighResWaveformAsync(status.EntryKey);
if (!result.Success)
{
failures++;
}
}
catch (Exception ex)
{
Logger.LogError(ex, "High-res waveform generation failed for {EntryKey}", status.EntryKey);
failures++;
}
_highResBulkDone++;
StateHasChanged();
}
_highResBulkRunning = false;
_grid?.SetBulkRunning(false);
if (_grid is not null)
{
await _grid.RefreshWaveformStatusAsync();
}
var succeeded = missing.Count - failures;
if (failures == 0)
{
Snackbar.Add($"Backfilled {succeeded} high-res datum(s).", Severity.Success);
}
else
{
Snackbar.Add($"Backfilled {succeeded} high-res datum(s); {failures} failed.", Severity.Warning);
}
}
}