Phase 9 Wave 2: api/release endpoint family — medium-aware reads + metadata writes
Adds ReleaseRepository/ReleaseManager (IReleaseService) for paged medium-filtered release reads and Session/Mix satellite writes, UnifiedReleaseService orchestrating vault+SQL, and ReleaseController (5 endpoints). Refactors WaveformProfileService for configurable bucketCount/vaultName (backward-compatible) and adds the mix-waveforms vault. Promotes brittle error-string literals to named constants (MixHasNoTrackMessage, MixTrackNoAudioMessage) on UnifiedReleaseService.
This commit is contained in:
@@ -20,4 +20,10 @@ public static class VaultConstants
|
||||
/// from <c>TrackEntity.ImagePath</c>.
|
||||
/// </summary>
|
||||
public const string Images = "images";
|
||||
|
||||
/// <summary>
|
||||
/// Vault name for Mix high-resolution waveform datums, keyed by the mix track's EntryKey.
|
||||
/// Distinct from WaveformProfiles (player-bar low-res); same pipeline at higher resolution.
|
||||
/// </summary>
|
||||
public const string MixWaveforms = "mix-waveforms";
|
||||
}
|
||||
@@ -39,12 +39,22 @@ public class WaveformProfileService
|
||||
|
||||
/// <summary>
|
||||
/// Computes the loudness profile from <paramref name="wavBytes"/> and stores it under
|
||||
/// <paramref name="entryKey"/>. Returns false (and logs) on any failure — a missing profile
|
||||
/// is handled gracefully downstream, so callers on the upload path log-and-continue rather
|
||||
/// than failing the upload. Does not throw for expected failure modes.
|
||||
/// <paramref name="entryKey"/> in <paramref name="vaultName"/> (defaults to
|
||||
/// <see cref="VaultConstants.WaveformProfiles"/> when null). Bucket resolution defaults to
|
||||
/// <see cref="WaveformProfileOptions.BucketCount"/> (512) when <paramref name="bucketCount"/> is null;
|
||||
/// pass a higher value (e.g., 2048) for the Mix high-res datum. Returns false (and logs) on any
|
||||
/// failure — a missing profile is handled gracefully downstream, so callers on the upload path
|
||||
/// log-and-continue rather than failing the upload. Does not throw for expected failure modes.
|
||||
/// </summary>
|
||||
public async Task<bool> ComputeAndStoreAsync(ReadOnlyMemory<byte> wavBytes, string entryKey)
|
||||
public async Task<bool> ComputeAndStoreAsync(
|
||||
ReadOnlyMemory<byte> wavBytes,
|
||||
string entryKey,
|
||||
int? bucketCount = null,
|
||||
string? vaultName = null)
|
||||
{
|
||||
var effectiveBucketCount = bucketCount ?? _options.BucketCount;
|
||||
var effectiveVaultName = vaultName ?? VaultConstants.WaveformProfiles;
|
||||
|
||||
try
|
||||
{
|
||||
var pcm = _audioProcessor.TryExtractPcm(wavBytes.Span);
|
||||
@@ -62,15 +72,14 @@ public class WaveformProfileService
|
||||
value.Channels,
|
||||
value.SampleRate,
|
||||
value.BitsPerSample,
|
||||
_options.BucketCount);
|
||||
effectiveBucketCount);
|
||||
|
||||
var quantized = Quantize(profile);
|
||||
|
||||
await EnsureVaultAsync();
|
||||
await EnsureVaultAsync(effectiveVaultName);
|
||||
|
||||
var binary = new MediaBinary(new MediaBinaryParams(quantized, quantized.Length, ProfileExtension));
|
||||
var stored = await _fileDatabase.RegisterResourceAsync(
|
||||
VaultConstants.WaveformProfiles, entryKey, binary);
|
||||
var stored = await _fileDatabase.RegisterResourceAsync(effectiveVaultName, entryKey, binary);
|
||||
|
||||
if (!stored)
|
||||
{
|
||||
@@ -88,14 +97,15 @@ public class WaveformProfileService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the stored quantized profile bytes for a track, or null if no profile is stored
|
||||
/// (existing tracks predate profiling, and computation may have failed). Each byte is a
|
||||
/// peak-normalized loudness value in [0, 255].
|
||||
/// Returns the stored quantized profile bytes for a track from <paramref name="vaultName"/>
|
||||
/// (defaults to <see cref="VaultConstants.WaveformProfiles"/> when null), or null if no profile
|
||||
/// is stored (existing tracks predate profiling, and computation may have failed). Each byte is
|
||||
/// a peak-normalized loudness value in [0, 255].
|
||||
/// </summary>
|
||||
public async Task<byte[]?> GetProfileAsync(string entryKey)
|
||||
public async Task<byte[]?> GetProfileAsync(string entryKey, string? vaultName = null)
|
||||
{
|
||||
var binary = await _fileDatabase.LoadResourceAsync<MediaBinary>(
|
||||
VaultConstants.WaveformProfiles, entryKey);
|
||||
vaultName ?? VaultConstants.WaveformProfiles, entryKey);
|
||||
return binary?.Buffer;
|
||||
}
|
||||
|
||||
@@ -113,11 +123,11 @@ public class WaveformProfileService
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private async Task EnsureVaultAsync()
|
||||
private async Task EnsureVaultAsync(string vaultName)
|
||||
{
|
||||
if (!_fileDatabase.HasVault(VaultConstants.WaveformProfiles))
|
||||
if (!_fileDatabase.HasVault(vaultName))
|
||||
{
|
||||
await _fileDatabase.CreateVaultAsync(VaultConstants.WaveformProfiles, MediaVaultType.Media);
|
||||
await _fileDatabase.CreateVaultAsync(vaultName, MediaVaultType.Media);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user