Problem
SpectrogramData.from_audio_id() (idtap/spectrogram.py) computes the true seconds-per-frame as recording_duration / time_frames using the audio recording's database entry. When client.get_audio_recording(audio_id) fails (e.g. HTTP 404 for user-uploaded audio that has no entry at /api/audioRecording/{id}), the except swallows the error and the instance silently falls back to DEFAULT_TIME_RESOLUTION = 0.015080 s/frame.
That default is close but not exact. Observed on transcription 6a6a7ca9cba5b283f2cc15f9 (audio 68f6ca1f9d93a4cd2923711e):
- spectrogram frames: 21,344
- transcription duration (
piece.dur_tot): 322.344 s
- true resolution: 322.344 / 21344 = 0.015102 s/frame
- fallback resolution: 0.015080 s/frame (~0.15% fast)
The error accumulates linearly: by 4:25 into the recording, spectrogram content queried via crop_time() is ~0.4 s early relative to transcription time — about one short phrase — while the web editor (which stretches the spectrogram across the true audio duration) shows it correctly. In a figure built from crop_time(phrase.start_time, ...), each phrase box displayed the next phrase's audio content.
Verified empirically: the onset frame of a known band, multiplied by the corrected resolution, lands at 265.09 s — exactly the phrase boundary where the editor shows that band beginning.
Suggested fix
In SpectrogramData.from_piece(), when the recording lookup fell back to the default (and the piece is not an excerpt), recover the resolution from the transcription, since a transcription spans its full audio file:
spec = cls.from_audio_id(piece.audio_id, client)
if (spec._time_resolution == cls.DEFAULT_TIME_RESOLUTION
and getattr(piece, 'excerpt_range', None) is None):
dur = getattr(piece, 'dur_tot', None)
if dur and abs(spec.duration - dur) / dur < 0.05: # sanity guard
spec._time_resolution = dur / spec.shape[1]
return spec
Also worth considering: log a warning when the DB lookup fails instead of failing silent, so the fallback is at least visible.
Downstream workaround to revert once fixed
DN-Book-Figures/visualization/lyric_contour_timeline_packed.py currently applies this same rescaling itself right after SpectrogramData.from_piece(...) (guarded by piece.excerpt_range is None and the 5% duration agreement check). Once this is fixed in the API, that block should be removed — and the regenerated figures manually re-checked against the editor (e.g. book_figures/vilayat_khan_shrutis/figure_03.png, where box 1's low band must start flush at its left edge at 4:25.1).
Problem
SpectrogramData.from_audio_id()(idtap/spectrogram.py) computes the true seconds-per-frame asrecording_duration / time_framesusing the audio recording's database entry. Whenclient.get_audio_recording(audio_id)fails (e.g. HTTP 404 for user-uploaded audio that has no entry at/api/audioRecording/{id}), theexceptswallows the error and the instance silently falls back toDEFAULT_TIME_RESOLUTION = 0.015080s/frame.That default is close but not exact. Observed on transcription
6a6a7ca9cba5b283f2cc15f9(audio68f6ca1f9d93a4cd2923711e):piece.dur_tot): 322.344 sThe error accumulates linearly: by 4:25 into the recording, spectrogram content queried via
crop_time()is ~0.4 s early relative to transcription time — about one short phrase — while the web editor (which stretches the spectrogram across the true audio duration) shows it correctly. In a figure built fromcrop_time(phrase.start_time, ...), each phrase box displayed the next phrase's audio content.Verified empirically: the onset frame of a known band, multiplied by the corrected resolution, lands at 265.09 s — exactly the phrase boundary where the editor shows that band beginning.
Suggested fix
In
SpectrogramData.from_piece(), when the recording lookup fell back to the default (and the piece is not an excerpt), recover the resolution from the transcription, since a transcription spans its full audio file:Also worth considering: log a warning when the DB lookup fails instead of failing silent, so the fallback is at least visible.
Downstream workaround to revert once fixed
DN-Book-Figures/visualization/lyric_contour_timeline_packed.pycurrently applies this same rescaling itself right afterSpectrogramData.from_piece(...)(guarded bypiece.excerpt_range is Noneand the 5% duration agreement check). Once this is fixed in the API, that block should be removed — and the regenerated figures manually re-checked against the editor (e.g.book_figures/vilayat_khan_shrutis/figure_03.png, where box 1's low band must start flush at its left edge at 4:25.1).