feat: play/stop audition of the current audio region
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4533,6 +4533,12 @@ class MainWindow(QMainWindow):
|
||||
self._btn_wave_refresh.setToolTip(
|
||||
"Decode the current audio area and draw its waveform")
|
||||
self._btn_wave_refresh.clicked.connect(self._on_wave_refresh)
|
||||
self._btn_audio_play = QPushButton("▶ Play")
|
||||
self._btn_audio_play.setCheckable(True)
|
||||
self._btn_audio_play.setToolTip(
|
||||
"Audition the current audio area (with the edits applied)")
|
||||
self._btn_audio_play.toggled.connect(self._on_audio_audition)
|
||||
self._audition_proc = None # QProcess while auditioning, else None
|
||||
self._transport_row = transport_row
|
||||
|
||||
# Row 1b — subcategory (subprofile) export buttons live on their own
|
||||
@@ -4838,6 +4844,7 @@ class MainWindow(QMainWindow):
|
||||
# Row 4 — read-only waveform strip for the current audio area.
|
||||
g.addWidget(self._wave, 4, 0, 1, 4)
|
||||
g.addWidget(self._btn_wave_refresh, 5, 0, 1, 2)
|
||||
g.addWidget(self._btn_audio_play, 5, 2, 1, 2)
|
||||
g.addWidget(self._btn_extract_audio, 6, 0, 1, 4)
|
||||
g.setColumnStretch(4, 1) # spare gutter column clusters label/field pairs to the left
|
||||
g.setRowStretch(7, 1) # anchor content to the top
|
||||
@@ -6523,6 +6530,84 @@ class MainWindow(QMainWindow):
|
||||
elif preview < dur:
|
||||
self._show_status(f"Waveform shows first {preview:.0f}s of {dur:.0f}s", 4000)
|
||||
|
||||
def _current_edit_filters(self) -> list[str]:
|
||||
"""ffmpeg -af chain for the current edit controls (shared by extract +
|
||||
audition so they never drift)."""
|
||||
return audio_edit_filters(
|
||||
duration=self._spn_audio_len.value(),
|
||||
fade_in=self._spn_fade_in.value(),
|
||||
fade_out=self._spn_fade_out.value(),
|
||||
normalize=self._chk_normalize.isChecked(),
|
||||
gain_db=self._spn_gain.value())
|
||||
|
||||
def _on_audio_audition(self, playing: bool) -> None:
|
||||
"""Toggle audition of the current region (with edits) via ffplay."""
|
||||
if not playing:
|
||||
self._stop_audition()
|
||||
return
|
||||
if not self._file_path:
|
||||
self._btn_audio_play.setChecked(False)
|
||||
return
|
||||
import tempfile
|
||||
from PyQt6.QtCore import QProcess
|
||||
start = self._cursor
|
||||
dur = self._spn_audio_len.value()
|
||||
edit_filters = self._current_edit_filters()
|
||||
tmp = os.path.join(tempfile.gettempdir(), "8cut_audition.wav")
|
||||
cmd = build_audio_clip_command(self._file_path, start, dur, tmp,
|
||||
filters=edit_filters or None)
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
self._show_status("Auditioning…")
|
||||
try:
|
||||
proc = subprocess.run(cmd, capture_output=True, timeout=120)
|
||||
except Exception:
|
||||
proc = None
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
if proc is None or proc.returncode != 0 or not os.path.exists(tmp):
|
||||
self._btn_audio_play.setChecked(False)
|
||||
self._show_status("Audition render failed", 3000)
|
||||
return
|
||||
self._audition_proc = QProcess(self)
|
||||
self._audition_proc.finished.connect(self._on_audition_finished)
|
||||
self._audition_proc.errorOccurred.connect(self._on_audition_error)
|
||||
self._audition_proc.start(
|
||||
_bin("ffplay"), ["-autoexit", "-nodisp", "-loglevel", "error", tmp])
|
||||
self._btn_audio_play.setText("■ Stop")
|
||||
|
||||
def _on_audition_error(self, _err) -> None:
|
||||
self._show_status("Audio playback unavailable (ffplay not found)", 4000)
|
||||
self._teardown_audition()
|
||||
|
||||
def _on_audition_finished(self, *_a) -> None:
|
||||
self._teardown_audition()
|
||||
|
||||
def _teardown_audition(self) -> None:
|
||||
proc = self._audition_proc
|
||||
self._audition_proc = None
|
||||
if proc is not None:
|
||||
proc.deleteLater()
|
||||
self._btn_audio_play.setText("▶ Play")
|
||||
# Snap the toggle back without re-entering start logic.
|
||||
if self._btn_audio_play.isChecked():
|
||||
self._btn_audio_play.blockSignals(True)
|
||||
self._btn_audio_play.setChecked(False)
|
||||
self._btn_audio_play.blockSignals(False)
|
||||
|
||||
def _stop_audition(self) -> None:
|
||||
proc = self._audition_proc
|
||||
self._audition_proc = None
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.finished.disconnect()
|
||||
proc.errorOccurred.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
proc.kill()
|
||||
proc.waitForFinished(100)
|
||||
proc.deleteLater()
|
||||
self._btn_audio_play.setText("▶ Play")
|
||||
|
||||
def _on_extract_audio(self) -> None:
|
||||
"""Extract an exact-length audio slice starting at the playhead and
|
||||
prompt for where to save it (format follows the chosen extension)."""
|
||||
@@ -6556,12 +6641,7 @@ class MainWindow(QMainWindow):
|
||||
if not os.path.splitext(path)[1]:
|
||||
path += ext
|
||||
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
||||
edit_filters = audio_edit_filters(
|
||||
duration=dur,
|
||||
fade_in=self._spn_fade_in.value(),
|
||||
fade_out=self._spn_fade_out.value(),
|
||||
normalize=self._chk_normalize.isChecked(),
|
||||
gain_db=self._spn_gain.value())
|
||||
edit_filters = self._current_edit_filters()
|
||||
cmd = build_audio_clip_command(self._file_path, start, dur, path,
|
||||
filters=edit_filters or None)
|
||||
self._btn_extract_audio.setEnabled(False)
|
||||
|
||||
Reference in New Issue
Block a user