Add server-side waveform loudness profiling on track upload

ILoudnessAlgorithm strategy (RmsLoudnessAlgorithm first impl), WaveformProfileService
stores quantized byte[] sidecar in new MediaFileVault (profiles vault), wired into
UnifiedTrackService.UploadAsync; failure is logged and swallowed. WaveformProfileDto
and WaveformProfileOptions in shared projects.
This commit is contained in:
daniel-c-harvey
2026-06-05 16:38:02 -04:00
parent eed99df0dd
commit fa57861dbf
16 changed files with 544 additions and 10 deletions
@@ -219,6 +219,32 @@ public class AudioVault : MediaVault
}
}
/// <summary>
/// Concrete vault for plain <see cref="MediaBinary"/> entries (vault type
/// <see cref="MediaVaultType.Media"/>) — bytes plus an extension, no audio/image-specific
/// metadata. Used for sidecar artifacts such as waveform loudness profiles. The base
/// <see cref="MediaVault"/> already handles Media-typed storage via the registry; this only
/// provides the concrete factory the Image and Audio vaults also provide.
/// </summary>
public class MediaFileVault : MediaVault
{
private MediaFileVault(string rootPath, VaultIndex index, IndexFactoryService? factoryService = null)
: base(rootPath, index, factoryService) { }
public static async Task<MediaFileVault?> FromAsync(string rootPath, IndexFactoryService? factoryService = null)
{
var factory = factoryService ?? new IndexFactoryService();
var index = await factory.LoadOrCreateVaultIndexAsync(rootPath, MediaVaultType.Media);
if (index != null)
{
return new MediaFileVault(rootPath, (VaultIndex)index, factory);
}
return null;
}
}
/// <summary>
/// An open read-only stream over a vault entry plus the extension needed to
/// resolve its MIME type. Caller owns the stream and must dispose it.
@@ -11,6 +11,7 @@ public static class MediaVaultFactory
{
return mediaType switch
{
MediaVaultType.Media => await MediaFileVault.FromAsync(rootPath, factoryService),
MediaVaultType.Image => await ImageVault.FromAsync(rootPath, factoryService),
MediaVaultType.Audio => await AudioVault.FromAsync(rootPath, factoryService),
_ => null
@@ -31,7 +31,8 @@ public class SimpleMediaTypeRegistry : IMediaTypeRegistry
dto => MediaBinary.From(dto),
binary => new MediaBinaryDto(binary),
(key, ext, _) => new MetaData(key, ext),
(binary, meta) => new MediaBinaryParams(binary.Buffer, binary.Size, meta.Extension));
(binary, meta) => new MediaBinaryParams(binary.Buffer, binary.Size, meta.Extension),
async path => await MediaFileVault.FromAsync(path));
RegisterType<ImageBinary, ImageBinaryParams, ImageBinaryDto, ImageMetaData>(
MediaVaultType.Image,