Fix clap detection silently analyzing the wrong audio track
Root cause of the reported 'timestamps off by different amounts, sometimes early, sometimes late' bug: _extract_audio_wav() never passed an explicit -map to ffmpeg, so on multi-audio-track files it relied on ffmpeg's own default-stream heuristic -- which has no knowledge of, and isn't guaranteed to agree with, whichever audio track mpv is actually configured to play. A wrong-track clap is essentially unrelated to the true one (not a small timestamp-math error), which explains the inconsistent, file-dependent symptom -- and our single-audio-track synthetic fixtures never exercised this path at all, since they only ever had one track to pick. Fixed by threading an explicit ffmpeg stream index through detect_claps() -> _extract_audio_wav(), sourced in main.py from mpv's own track-list (the ff-index of whichever audio track has selected=True). Detection now always analyzes the exact track the user is listening to, on both 'Detect All' and 'Detect Next'. Also: _probe_audio_start_time() used to silently fall back to 0.0 if ffprobe couldn't parse start_time as a plain float (e.g. N/A) -- same failure class (silently wrong instead of loud). Now emits a Python warning instead, so a genuinely unparseable/unusual container is at least visible rather than silently mis-corrected. Verified two ways: (1) ruled out an initial alternate hypothesis (video stream's own start_time mattering) empirically against the existing single-track fixtures before ruling it out. (2) Built a new two-audio-track fixture (sync_test_MULTITRACK.mp4: track 1 has a beep at 3s, track 2 at 7s) and confirmed end-to-end through the real app -- switching mpv's active track from aid=1 to aid=2 correctly flips which clap gets detected, which is exactly the mechanism this bug required.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Verifies clap detection analyzes whichever audio track mpv is actually
|
||||
playing, not whichever one ffmpeg's own default-stream heuristic would
|
||||
pick. Uses a synthetic fixture with two audio tracks with claps at very
|
||||
different times (3s vs 7s) -- if detection ignored mpv's active track
|
||||
selection, switching mpv to the second track would still incorrectly
|
||||
report the first track's 3s clap instead of the second track's 7s one.
|
||||
|
||||
This directly tests the bug hypothesis from user report: 'sometimes too
|
||||
early, sometimes too late, by different amounts' -- exactly what you'd
|
||||
see if detection sometimes analyzed the wrong audio track on
|
||||
multi-audio-track files, since a wrong-track clap position is essentially
|
||||
unrelated to the true one (not a small timestamp-math error).
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
import main as app_main
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CLIP = str(ROOT / "test_media" / "sync_test_MULTITRACK.mp4")
|
||||
|
||||
|
||||
def wait_for(predicate, qapp, timeout=20):
|
||||
deadline = time.time() + timeout
|
||||
while not predicate() and time.time() < deadline:
|
||||
qapp.processEvents()
|
||||
time.sleep(0.05)
|
||||
return predicate()
|
||||
|
||||
|
||||
def main():
|
||||
qapp = QApplication(sys.argv)
|
||||
win = app_main.MainWindow()
|
||||
win.show()
|
||||
qapp.processEvents()
|
||||
|
||||
win._load(CLIP)
|
||||
qapp.processEvents()
|
||||
time.sleep(0.3)
|
||||
qapp.processEvents()
|
||||
|
||||
mpv = win.source_panel.player.mpv
|
||||
print(f"default aid: {mpv.aid}")
|
||||
default_index = win._current_audio_stream_index()
|
||||
print(f"resolved ff-index for default track: {default_index}")
|
||||
assert default_index == 1, f"expected default track's ff-index to be 1, got {default_index}"
|
||||
|
||||
win.detect_all_button.click()
|
||||
qapp.processEvents()
|
||||
ok = wait_for(lambda: win.detect_all_button.isEnabled(), qapp)
|
||||
assert ok, "detection did not finish in time"
|
||||
markers = win.source_panel.marker_strip.markers()
|
||||
print(f"markers with default track selected: {markers}")
|
||||
assert any(abs(m - 3.0) < 0.5 for m in markers), f"expected a marker near 3.0s (default track's clap): {markers}"
|
||||
assert not any(abs(m - 7.0) < 0.5 for m in markers), f"should not see the OTHER track's 7.0s clap yet: {markers}"
|
||||
|
||||
# Now switch mpv to the second audio track (aid=2), simulating a file
|
||||
# where the user is listening to a non-default track, and rerun
|
||||
# detection -- it must follow the switch, not keep reporting the first
|
||||
# track's clap.
|
||||
mpv.aid = 2
|
||||
qapp.processEvents()
|
||||
time.sleep(0.2)
|
||||
switched_index = win._current_audio_stream_index()
|
||||
print(f"resolved ff-index after switching to aid=2: {switched_index}")
|
||||
assert switched_index == 2, f"expected ff-index 2 after switching tracks, got {switched_index}"
|
||||
|
||||
win.source_panel.marker_strip.clear()
|
||||
win.detect_all_button.click()
|
||||
qapp.processEvents()
|
||||
ok = wait_for(lambda: win.detect_all_button.isEnabled(), qapp)
|
||||
assert ok, "detection did not finish in time"
|
||||
markers = win.source_panel.marker_strip.markers()
|
||||
print(f"markers with second track selected: {markers}")
|
||||
assert any(abs(m - 7.0) < 0.5 for m in markers), f"expected a marker near 7.0s (second track's clap) after switching: {markers}"
|
||||
assert not any(abs(m - 3.0) < 0.5 for m in markers), f"should not see the FIRST track's 3.0s clap after switching: {markers}"
|
||||
|
||||
win.close()
|
||||
qapp.processEvents()
|
||||
print("\nPASS: clap detection follows mpv's actual selected audio track, not ffmpeg's default")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user