Store waveform peaks inline in audio_versions (JSONB columns) so WaveSurfer
can render the waveform immediately on page load without waiting for audio
decode. Adds a 100-point mini-waveform for version selector thumbnails.
Backend:
- Migration 0006: adds waveform_peaks and waveform_peaks_mini JSONB columns
- Worker generates both resolutions (500-pt full, 100-pt mini) during transcode
and stores them directly in DB — replaces file-based waveform_url approach
- AudioVersionRead schema exposes both fields inline (no extra HTTP round-trip)
- GET /versions/{id}/waveform reads from DB; adds ?resolution=mini support
Frontend:
- audioService.initialize() accepts peaks and calls ws.load(url, Float32Array)
so waveform renders instantly without audio decode
- useWaveform hook threads peaks option through to audioService
- PlayerPanel passes waveform_peaks from the active version to the hook
- New MiniWaveform SVG component (no WaveSurfer) renders mini peaks in the
version selector buttons
Fix: docker-compose.dev.yml now runs alembic upgrade head before starting
the API server, so a fresh volume gets the full schema automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Unit tests for AudioVersionRead schema — waveform peaks serialization."""
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from rehearsalhub.db.models import AudioVersion
|
|
from rehearsalhub.schemas.audio_version import AudioVersionRead
|
|
|
|
|
|
def _make_version(peaks=None, peaks_mini=None) -> MagicMock:
|
|
"""Build a mock AudioVersion ORM object."""
|
|
v = MagicMock(spec=AudioVersion)
|
|
v.id = uuid.uuid4()
|
|
v.song_id = uuid.uuid4()
|
|
v.version_number = 1
|
|
v.label = None
|
|
v.nc_file_path = "/bands/test/v1.wav"
|
|
v.nc_file_etag = "abc123"
|
|
v.cdn_hls_base = None
|
|
v.waveform_url = None
|
|
v.waveform_peaks = peaks
|
|
v.waveform_peaks_mini = peaks_mini
|
|
v.duration_ms = 5000
|
|
v.format = "wav"
|
|
v.file_size_bytes = 1024
|
|
v.analysis_status = "done"
|
|
v.uploaded_by = None
|
|
v.uploaded_at = datetime.now(timezone.utc)
|
|
return v
|
|
|
|
|
|
def test_audio_version_read_includes_waveform_peaks():
|
|
peaks = [float(i) / 500 for i in range(500)]
|
|
peaks_mini = [float(i) / 100 for i in range(100)]
|
|
v = _make_version(peaks=peaks, peaks_mini=peaks_mini)
|
|
|
|
schema = AudioVersionRead.model_validate(v)
|
|
|
|
assert schema.waveform_peaks is not None
|
|
assert len(schema.waveform_peaks) == 500
|
|
assert schema.waveform_peaks_mini is not None
|
|
assert len(schema.waveform_peaks_mini) == 100
|
|
|
|
|
|
def test_audio_version_read_peaks_default_null():
|
|
v = _make_version(peaks=None, peaks_mini=None)
|
|
|
|
schema = AudioVersionRead.model_validate(v)
|
|
|
|
assert schema.waveform_peaks is None
|
|
assert schema.waveform_peaks_mini is None
|
|
|
|
|
|
def test_audio_version_read_peaks_values_preserved():
|
|
peaks = [0.0, 0.5, 1.0]
|
|
v = _make_version(peaks=peaks, peaks_mini=[0.25, 0.75])
|
|
|
|
schema = AudioVersionRead.model_validate(v)
|
|
|
|
assert schema.waveform_peaks == [0.0, 0.5, 1.0]
|
|
assert schema.waveform_peaks_mini == [0.25, 0.75]
|