From a200a60734befb32749518828e71267dedf51d4c Mon Sep 17 00:00:00 2001 From: sbin Date: Tue, 6 Oct 2026 17:04:06 +0900 Subject: [PATCH] Fix garbled duration in info() output for files of 10 minutes or longer `_SoundFileInfo._duration_str` formatted hours and minutes with `.0g`, which keeps one significant digit and switches to exponent notation from 10 on (15 minutes was shown as `2e+01:0.000 min`), and padded the seconds with `05.3f`, which is too narrow to ever pad. Use `.0f` / `02.0f` / `06.3f`, and round to milliseconds before splitting so that 119.9996 s is not shown as `01:60.000 min`. Co-Authored-By: Claude Opus 5.5 --- soundfile.py | 7 ++++--- tests/test_soundfile.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/soundfile.py b/soundfile.py index 6db0b4b6..0bf3a6e2 100644 --- a/soundfile.py +++ b/soundfile.py @@ -459,12 +459,13 @@ def __init__(self, file, verbose): @property def _duration_str(self): - hours, rest = divmod(self.duration, 3600) + # round first, otherwise 119.9996 s is shown as "01:60.000 min" + hours, rest = divmod(round(self.duration, 3), 3600) minutes, seconds = divmod(rest, 60) if hours >= 1: - duration = f"{hours:.0g}:{minutes:02.0g}:{seconds:05.3f} h" + duration = f"{hours:.0f}:{minutes:02.0f}:{seconds:06.3f} h" elif minutes >= 1: - duration = f"{minutes:02.0g}:{seconds:05.3f} min" + duration = f"{minutes:02.0f}:{seconds:06.3f} min" elif seconds <= 1: duration = f"{self.frames:d} samples" else: diff --git a/tests/test_soundfile.py b/tests/test_soundfile.py index 1222721d..0c93f66d 100644 --- a/tests/test_soundfile.py +++ b/tests/test_soundfile.py @@ -720,6 +720,23 @@ def test_extra_info(sf_stereo_r): assert 'WAVE_FORMAT_IEEE_FLOAT' in sf_stereo_r.extra_info +@pytest.mark.parametrize("frames, samplerate, duration", [ + (4, 8, "4 samples"), + (12, 8, "1.500 s"), + (61 * 8, 8, "01:01.000 min"), + (900 * 8, 8, "15:00.000 min"), + (3661 * 8 + 4, 8, "1:01:01.500 h"), + (11707 * 8 + 4, 8, "3:15:07.500 h"), + (36000 * 8, 8, "10:00:00.000 h"), + # 119.9996 s must not be shown as "01:60.000 min": + (1199996, 10000, "02:00.000 min"), +]) +def test_info_duration(tmp_path, frames, samplerate, duration): + filename = str(tmp_path / 'duration.wav') + sf.write(filename, np.zeros(frames, dtype='int16'), samplerate) + assert f"duration: {duration}\n" in repr(sf.info(filename)) + + def test_mode_should_be_in_write_mode(sf_stereo_w): assert sf_stereo_w.mode == 'w' assert sf_stereo_w.frames == 0