diff --git a/DeepDrftAPI/Services/UnifiedTrackService.cs b/DeepDrftAPI/Services/UnifiedTrackService.cs index 679bce1..6fcbca8 100644 --- a/DeepDrftAPI/Services/UnifiedTrackService.cs +++ b/DeepDrftAPI/Services/UnifiedTrackService.cs @@ -168,12 +168,13 @@ public class UnifiedTrackService /// /// Replace an existing track's audio in place: look up the SQL row, swap only the vault bytes - /// keyed by its EntryKey, then regenerate both waveform datums from the new audio. Track id, - /// EntryKey, release membership, track number, and all metadata are preserved — nothing in SQL - /// is written. The waveform regen is best-effort (a missing datum renders as a flat seekbar / - /// blank visualizer downstream), so a datum failure is logged and swallowed rather than failing - /// the replace. No release-cardinality cascade applies: the track count is unchanged, so the - /// single-track-Mix case stays intact. + /// keyed by its EntryKey, regenerate both waveform datums from the new audio, then write the + /// new duration to SQL. Track id, EntryKey, release membership, track number, and all other + /// metadata are preserved. The waveform regen is best-effort (a missing datum renders as a flat + /// seekbar / blank visualizer downstream), so a datum failure is logged and swallowed rather than + /// failing the replace. The duration write is not best-effort — a failure is surfaced so derived + /// aggregates (e.g. MixRuntimeSeconds) do not silently go stale. No release-cardinality cascade + /// applies: the track count is unchanged, so the single-track-Mix case stays intact. /// public async Task ReplaceAudioAsync(long trackId, string tempFilePath, CancellationToken ct) { @@ -212,6 +213,20 @@ public class UnifiedTrackService _logger.LogError(ex, "ReplaceAudioAsync: waveform regen failed for {EntryKey}; replace unaffected.", entryKey); } + // Write the new duration to SQL. The vault bytes are already swapped, so this is the + // authoritative metadata update for the replace. A failure here is surfaced (unlike the + // best-effort waveform regen above) because a stale DurationSeconds silently corrupts + // derived aggregates (e.g. MixRuntimeSeconds on the home stats endpoint). + var durationWrite = await _sqlTrackService.UpdateDuration(trackId, newAudio.Duration, ct); + if (!durationWrite.Success) + { + var error = durationWrite.Messages.FirstOrDefault()?.Message ?? "Unknown error"; + _logger.LogError( + "ReplaceAudioAsync: vault swap succeeded but SQL duration update failed for track {TrackId} ({EntryKey}): {Error}", + trackId, entryKey, error); + return Result.CreateFailResult("Audio replaced but duration metadata could not be updated."); + } + return Result.CreatePassResult(); }