diff --git a/DeepDrftManager/Components/Pages/Index.razor b/DeepDrftManager/Components/Pages/Index.razor
index 9dcc89a..3b3a06d 100644
--- a/DeepDrftManager/Components/Pages/Index.razor
+++ b/DeepDrftManager/Components/Pages/Index.razor
@@ -15,11 +15,5 @@
Href="/tracks">
Tracks
-
- Waveform Pre-Processing
-
diff --git a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
index a23f809..fdee66d 100644
--- a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
+++ b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor
@@ -1,6 +1,7 @@
@page "/tracks"
@using System.Net
@using DeepDrftManager.Services
+@using DeepDrftModels.DTOs
@attribute [Authorize]
@inject ICmsTrackService CmsTrackService
@inject IDialogService DialogService
@@ -10,80 +11,182 @@
Tracks — DeepDrft CMS
-
- Tracks
-
-
- Waveform Pre-Processing
-
-
- Add Track
-
-
-
+ Tracks
-
-
- No tracks found.
-
-
- Loading tracks…
-
-
- Track Name
- Artist
- Album
- Genre
- Release Date
- Entry Key
- Actions
-
-
- @context.TrackName
- @context.Artist
- @(context.Album ?? "—")
- @(context.Genre ?? "—")
- @(context.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")
- @context.EntryKey
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Add Track
+
+
+
+
+
+ No tracks found.
+
+
+ Loading tracks…
+
+
+ Track Name
+ Artist
+ Album
+ Genre
+ Release Date
+ Entry Key
+ Actions
+
+
+ @context.TrackName
+ @context.Artist
+ @(context.Album ?? "—")
+ @(context.Genre ?? "—")
+ @(context.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")
+ @context.EntryKey
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Waveform Pre-Processing
+
+ Generate loudness profiles for tracks that predate the waveform seeker.
+
+
+
+ @if (_bulkRunning)
+ {
+
+ Generating @_bulkDone / @_bulkTotal…
+ }
+ else
+ {
+ Generate All Missing (@_missingCount)
+ }
+
+
+
+
+
+ No tracks found.
+
+
+ Loading waveform status…
+
+
+ Track Name
+ Entry Key
+ Profile
+ Actions
+
+
+ @context.TrackName
+
+ @context.EntryKey
+
+
+ @if (context.HasProfile)
+ {
+ Stored
+ }
+ else
+ {
+ Missing
+ }
+
+
+ @if (!context.HasProfile)
+ {
+
+ @if (IsGenerating(context.EntryKey))
+ {
+
+ Generating…
+ }
+ else
+ {
+ Generate
+ }
+
+ }
+
+
+
+
+
@code {
+ // Track list fields
private MudTable? _table;
+ // Waveform fields
+ private List _waveformRows = new();
+ private readonly HashSet _generating = new();
+ private bool _waveformLoading = true;
+ private bool _bulkRunning;
+ private int _bulkTotal;
+ private int _bulkDone;
+
+ private int _missingCount => _waveformRows.Count(r => !r.HasProfile);
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadWaveformStatus();
+ }
+
+ // ── Track list methods ──────────────────────────────────────────────────
+
private async Task> LoadServerData(TableState state, CancellationToken cancellationToken)
{
var pageNumber = state.Page + 1; // MudTable is 0-based, service is 1-based.
@@ -137,4 +240,114 @@
Snackbar.Add("Delete failed — please try again.", Severity.Error);
}
}
+
+ // ── Waveform pre-processing methods ────────────────────────────────────
+
+ private async Task LoadWaveformStatus()
+ {
+ _waveformLoading = true;
+ var result = await CmsTrackService.GetWaveformStatusAsync();
+ _waveformLoading = false;
+
+ if (!result.Success || result.Value is null)
+ {
+ var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
+ Snackbar.Add($"Failed to load waveform status: {error}", Severity.Error);
+ _waveformRows = new List();
+ return;
+ }
+
+ _waveformRows = result.Value.OrderBy(r => r.HasProfile).ThenBy(r => r.TrackName).ToList();
+ }
+
+ private bool IsGenerating(string entryKey) => _generating.Contains(entryKey);
+
+ private async Task GenerateOne(WaveformStatusDto row)
+ {
+ if (!await GenerateForRow(row))
+ {
+ return;
+ }
+
+ Snackbar.Add($"Generated profile for '{row.TrackName}'.", Severity.Success);
+ }
+
+ private async Task GenerateAllMissing()
+ {
+ var missing = _waveformRows.Where(r => !r.HasProfile).ToList();
+ if (missing.Count == 0)
+ {
+ return;
+ }
+
+ _bulkRunning = true;
+ _bulkTotal = missing.Count;
+ _bulkDone = 0;
+ var failures = 0;
+
+ // Sequential by design: one request at a time so a large backfill does not flood the API
+ // with concurrent WAV decodes.
+ foreach (var row in missing)
+ {
+ if (!await GenerateForRow(row))
+ {
+ failures++;
+ }
+ _bulkDone++;
+ StateHasChanged();
+ }
+
+ _bulkRunning = false;
+
+ var succeeded = missing.Count - failures;
+ if (failures == 0)
+ {
+ Snackbar.Add($"Generated {succeeded} profile(s).", Severity.Success);
+ }
+ else
+ {
+ Snackbar.Add($"Generated {succeeded} profile(s); {failures} failed.", Severity.Warning);
+ }
+ }
+
+ ///
+ /// Runs generation for a single row, flipping its status on success. Returns false on failure
+ /// (a snackbar is raised here for the per-row path; the bulk path aggregates a summary). Marks
+ /// the row busy for the duration so its button shows a spinner and stays disabled.
+ ///
+ private async Task GenerateForRow(WaveformStatusDto row)
+ {
+ _generating.Add(row.EntryKey);
+ StateHasChanged();
+ try
+ {
+ var result = await CmsTrackService.GenerateWaveformProfileAsync(row.EntryKey);
+ if (result.Success)
+ {
+ row.HasProfile = true;
+ return true;
+ }
+
+ var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
+ if (!_bulkRunning)
+ {
+ Snackbar.Add($"Generate failed for '{row.TrackName}': {error}", Severity.Error);
+ }
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Waveform generation failed for {EntryKey}", row.EntryKey);
+ if (!_bulkRunning)
+ {
+ Snackbar.Add($"Generate failed for '{row.TrackName}' — please try again.", Severity.Error);
+ }
+ return false;
+ }
+ finally
+ {
+ _generating.Remove(row.EntryKey);
+ StateHasChanged();
+ }
+ }
}
diff --git a/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor b/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor
deleted file mode 100644
index 6cbe582..0000000
--- a/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor
+++ /dev/null
@@ -1,91 +0,0 @@
-@page "/tracks/preprocessing"
-@attribute [Authorize]
-
-Waveform Pre-Processing — DeepDrft CMS
-
-
-
-
- Waveform Pre-Processing
-
- Generate loudness profiles for tracks that predate the waveform seeker.
-
-
-
- @if (_bulkRunning)
- {
-
- Generating @_bulkDone / @_bulkTotal…
- }
- else
- {
- Generate All Missing (@_missingCount)
- }
-
-
-
-
-
- No tracks found.
-
-
- Loading waveform status…
-
-
- Track Name
- Entry Key
- Profile
- Actions
-
-
- @context.TrackName
-
- @context.EntryKey
-
-
- @if (context.HasProfile)
- {
- Stored
- }
- else
- {
- Missing
- }
-
-
- @if (!context.HasProfile)
- {
-
- @if (IsGenerating(context.EntryKey))
- {
-
- Generating…
- }
- else
- {
- Generate
- }
-
- }
-
-
-
-
diff --git a/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor.cs b/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor.cs
deleted file mode 100644
index 41da1c8..0000000
--- a/DeepDrftManager/Components/Pages/Tracks/TrackPreProcessing.razor.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using DeepDrftManager.Services;
-using DeepDrftModels.DTOs;
-using Microsoft.AspNetCore.Components;
-using MudBlazor;
-
-namespace DeepDrftManager.Components.Pages.Tracks;
-
-public partial class TrackPreProcessing : ComponentBase
-{
- private List _rows = new();
- private readonly HashSet _generating = new();
- private bool _loading = true;
- private bool _bulkRunning;
- private int _bulkTotal;
- private int _bulkDone;
-
- private int _missingCount => _rows.Count(r => !r.HasProfile);
-
- [Inject] private ICmsTrackService CmsTrackService { get; set; } = default!;
- [Inject] private ISnackbar Snackbar { get; set; } = default!;
- [Inject] private ILogger Logger { get; set; } = default!;
-
- protected override async Task OnInitializedAsync()
- {
- await LoadStatus();
- }
-
- private async Task LoadStatus()
- {
- _loading = true;
- var result = await CmsTrackService.GetWaveformStatusAsync();
- _loading = false;
-
- if (!result.Success || result.Value is null)
- {
- var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
- Snackbar.Add($"Failed to load waveform status: {error}", Severity.Error);
- _rows = new List();
- return;
- }
-
- _rows = result.Value.OrderBy(r => r.HasProfile).ThenBy(r => r.TrackName).ToList();
- }
-
- private bool IsGenerating(string entryKey) => _generating.Contains(entryKey);
-
- private async Task GenerateOne(WaveformStatusDto row)
- {
- if (!await GenerateForRow(row))
- {
- return;
- }
-
- Snackbar.Add($"Generated profile for '{row.TrackName}'.", Severity.Success);
- }
-
- private async Task GenerateAllMissing()
- {
- var missing = _rows.Where(r => !r.HasProfile).ToList();
- if (missing.Count == 0)
- {
- return;
- }
-
- _bulkRunning = true;
- _bulkTotal = missing.Count;
- _bulkDone = 0;
- var failures = 0;
-
- // Sequential by design: one request at a time so a large backfill does not flood the API
- // with concurrent WAV decodes.
- foreach (var row in missing)
- {
- if (!await GenerateForRow(row))
- {
- failures++;
- }
- _bulkDone++;
- StateHasChanged();
- }
-
- _bulkRunning = false;
-
- var succeeded = missing.Count - failures;
- if (failures == 0)
- {
- Snackbar.Add($"Generated {succeeded} profile(s).", Severity.Success);
- }
- else
- {
- Snackbar.Add($"Generated {succeeded} profile(s); {failures} failed.", Severity.Warning);
- }
- }
-
- ///
- /// Runs generation for a single row, flipping its status on success. Returns false on failure
- /// (a snackbar is raised here for the per-row path; the bulk path aggregates a summary). Marks
- /// the row busy for the duration so its button shows a spinner and stays disabled.
- ///
- private async Task GenerateForRow(WaveformStatusDto row)
- {
- _generating.Add(row.EntryKey);
- StateHasChanged();
- try
- {
- var result = await CmsTrackService.GenerateWaveformProfileAsync(row.EntryKey);
- if (result.Success)
- {
- row.HasProfile = true;
- return true;
- }
-
- var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
- if (!_bulkRunning)
- {
- Snackbar.Add($"Generate failed for '{row.TrackName}': {error}", Severity.Error);
- }
- return false;
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Waveform generation failed for {EntryKey}", row.EntryKey);
- if (!_bulkRunning)
- {
- Snackbar.Add($"Generate failed for '{row.TrackName}' — please try again.", Severity.Error);
- }
- return false;
- }
- finally
- {
- _generating.Remove(row.EntryKey);
- StateHasChanged();
- }
- }
-}