feat: merge save + preview (crossfade render, duration guard, ffplay preview)
This commit is contained in:
@@ -459,3 +459,70 @@ def test_merge_add_stores_duration_role(win, tmp_path):
|
||||
win._merge_add_paths([str(a)])
|
||||
# empty fixture -> ffprobe fails -> duration is None; M4 must tolerate None
|
||||
assert win._merge_list.item(0).data(Qt.ItemDataRole.UserRole + 1) is None
|
||||
|
||||
|
||||
def test_merge_save_builds_command(win, tmp_path, monkeypatch):
|
||||
import main as m, pytest
|
||||
from PyQt6.QtCore import Qt
|
||||
a = tmp_path / "a.wav"; b = tmp_path / "b.wav"; a.write_bytes(b""); b.write_bytes(b"")
|
||||
win._merge_list.clear()
|
||||
win._merge_add_paths([str(a), str(b)])
|
||||
win._merge_list.item(0).setData(Qt.ItemDataRole.UserRole + 1, 5.0) # long enough
|
||||
win._merge_list.item(1).setData(Qt.ItemDataRole.UserRole + 1, 5.0)
|
||||
win._spn_crossfade.setValue(0.75)
|
||||
seen = {}
|
||||
class _Stop(Exception): pass
|
||||
def fake(clips, xf, out):
|
||||
seen.update(clips=clips, xf=xf, out=out); raise _Stop
|
||||
monkeypatch.setattr(m, "build_crossfade_merge_command", fake)
|
||||
monkeypatch.setattr(m.QFileDialog, "getSaveFileName",
|
||||
staticmethod(lambda *a, **k: (str(tmp_path / "m.wav"), "")))
|
||||
with pytest.raises(_Stop):
|
||||
win._on_merge_save()
|
||||
assert seen["clips"] == [str(a), str(b)]
|
||||
assert seen["xf"] == 0.75
|
||||
assert seen["out"].endswith(".wav")
|
||||
|
||||
|
||||
def test_merge_save_blocks_short_clip(win, tmp_path, monkeypatch):
|
||||
import main as m
|
||||
from PyQt6.QtCore import Qt
|
||||
a = tmp_path / "a.wav"; b = tmp_path / "b.wav"; a.write_bytes(b""); b.write_bytes(b"")
|
||||
win._merge_list.clear()
|
||||
win._merge_add_paths([str(a), str(b)])
|
||||
win._merge_list.item(0).setData(Qt.ItemDataRole.UserRole + 1, 0.1) # shorter than crossfade
|
||||
win._merge_list.item(1).setData(Qt.ItemDataRole.UserRole + 1, 5.0)
|
||||
win._spn_crossfade.setValue(0.5)
|
||||
calls = {"n": 0}
|
||||
monkeypatch.setattr(m, "build_crossfade_merge_command",
|
||||
lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) or [])
|
||||
monkeypatch.setattr(m.QFileDialog, "getSaveFileName",
|
||||
staticmethod(lambda *a, **k: (_ for _ in ()).throw(AssertionError("dialog opened"))))
|
||||
win._on_merge_save() # guard must block before dialog/builder
|
||||
assert calls["n"] == 0
|
||||
|
||||
|
||||
def test_merge_save_empty_is_noop(win):
|
||||
win._merge_list.clear()
|
||||
win._on_merge_save() # no raise, no dialog
|
||||
|
||||
|
||||
def test_merge_preview_blocks_short_clip(win, tmp_path, monkeypatch):
|
||||
import main as m
|
||||
from PyQt6.QtCore import Qt
|
||||
a = tmp_path / "a.wav"; b = tmp_path / "b.wav"; a.write_bytes(b""); b.write_bytes(b"")
|
||||
win._merge_list.clear()
|
||||
win._merge_add_paths([str(a), str(b)])
|
||||
win._merge_list.item(0).setData(Qt.ItemDataRole.UserRole + 1, 0.1)
|
||||
win._merge_list.item(1).setData(Qt.ItemDataRole.UserRole + 1, 5.0)
|
||||
win._spn_crossfade.setValue(0.5)
|
||||
calls = {"n": 0}
|
||||
monkeypatch.setattr(m, "build_crossfade_merge_command",
|
||||
lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) or [])
|
||||
win._on_merge_preview()
|
||||
assert calls["n"] == 0
|
||||
|
||||
|
||||
def test_merge_preview_empty_is_noop(win):
|
||||
win._merge_list.clear()
|
||||
win._on_merge_preview()
|
||||
|
||||
Reference in New Issue
Block a user