feat: wire editor ops (delete/silence/reverse/trim) + undo/redo + save-as

This commit is contained in:
2026-07-02 18:22:39 +02:00
parent a11e4859dd
commit 4d54f29514
2 changed files with 177 additions and 9 deletions
+106 -9
View File
@@ -38,6 +38,8 @@ from core.ffmpeg import (
_RATIOS, resolve_keyframe, apply_keyframes_to_jobs,
build_ffmpeg_command, build_audio_extract_command, build_audio_clip_command,
build_crossfade_merge_command,
build_audio_delete_command, build_audio_silence_command,
build_audio_reverse_command,
audio_edit_filters, probe_duration, detect_hw_encoders,
)
from core.db import ProcessedDB
@@ -4110,6 +4112,7 @@ class AudioEditorDialog(QDialog):
self._versions: list[str] = [path]
self._ver_idx = 0
self._temps: set[str] = set() # rendered version temps to clean up
self._clip_dur = 0.0 # true duration of the current version
self._wave = AudioWaveform()
self._btn_delete = QPushButton("Delete")
@@ -4140,6 +4143,8 @@ class AudioEditorDialog(QDialog):
self._btn_trim):
ops.addWidget(b)
outer.addLayout(ops)
self._status = QLabel("")
outer.addWidget(self._status)
bottom = QHBoxLayout()
for b in (self._btn_undo, self._btn_redo, self._btn_play):
bottom.addWidget(b)
@@ -4156,9 +4161,9 @@ class AudioEditorDialog(QDialog):
def _reload(self) -> None:
"""Decode the current version into the waveform + refresh undo/redo."""
from core.waveform import load_region_samples, peaks
from core.ffmpeg import probe_duration
path = self._current()
dur = probe_duration(path) or 0.0
self._clip_dur = dur
if dur > 0:
samples = load_region_samples(path, 0.0, min(dur, self._EDIT_DECODE_CAP))
self._wave.set_view(0.0, dur)
@@ -4170,15 +4175,107 @@ class AudioEditorDialog(QDialog):
self._btn_undo.setEnabled(self._ver_idx > 0)
self._btn_redo.setEnabled(self._ver_idx < len(self._versions) - 1)
# ── op / history / save handlers — STUBBED (implemented in 3c.3/3c.4) ──
def _on_delete(self): pass
def _on_silence(self): pass
def _on_reverse(self): pass
def _on_trim(self): pass
def _on_undo(self): pass
def _on_redo(self): pass
# ── op / history / save handlers ──
_MIN_OP_SEL = 0.01
def _set_status(self, msg: str) -> None:
self._status.setText(msg)
def _selection_secs(self):
sel = self._wave.selection()
if sel is None:
return None
s = max(0.0, sel[0])
e = min(self._clip_dur, sel[1]) if self._clip_dur > 0 else sel[1]
if e - s < self._MIN_OP_SEL:
return None
return (s, e)
def _apply_op(self, build_fn, whole_clip_ok: bool = True) -> None:
se = self._selection_secs()
if se is None:
self._set_status("Select a region first")
return
s, e = se
if (not whole_clip_ok and s <= 0.001 and self._clip_dur > 0
and e >= self._clip_dur - 0.001):
self._set_status("Can't delete the whole clip")
return
import tempfile
fd, tmp = tempfile.mkstemp(suffix=".wav", prefix="8cut_edit_")
os.close(fd)
cmd = build_fn(self._current(), s, e, tmp)
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
proc = subprocess.run(cmd, capture_output=True, timeout=300)
except Exception:
proc = None
finally:
QApplication.restoreOverrideCursor()
if (proc is not None and proc.returncode == 0
and os.path.exists(tmp) and os.path.getsize(tmp) > 0):
del self._versions[self._ver_idx + 1:] # drop redo history
self._versions.append(tmp)
self._ver_idx += 1
self._temps.add(tmp)
self._reload()
self._set_status("")
else:
try:
os.remove(tmp)
except OSError:
pass
self._set_status("Edit failed")
def _on_delete(self):
self._apply_op(build_audio_delete_command, whole_clip_ok=False)
def _on_silence(self):
self._apply_op(build_audio_silence_command)
def _on_reverse(self):
self._apply_op(build_audio_reverse_command)
def _on_trim(self):
self._apply_op(
lambda inp, s, e, out: build_audio_clip_command(inp, s, e - s, out))
def _on_undo(self):
if self._ver_idx > 0:
self._ver_idx -= 1
self._reload()
def _on_redo(self):
if self._ver_idx < len(self._versions) - 1:
self._ver_idx += 1
self._reload()
def _on_play(self): pass
def _on_save_as(self): pass
def _on_save_as(self):
path, _sel = QFileDialog.getSaveFileName(
self, "Save edited audio", "",
"WAV (*.wav);;MP3 (*.mp3);;FLAC (*.flac);;All files (*)")
if not path:
return
if not os.path.splitext(path)[1]:
ext = ".wav"
if _sel and "*." in _sel:
ext = "." + _sel.split("*.")[1].split(")")[0].strip()
path += ext
dur = probe_duration(self._current()) or 0.0
cmd = build_audio_clip_command(self._current(), 0.0, dur, path)
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
proc = subprocess.run(cmd, capture_output=True, timeout=300)
except Exception:
proc = None
finally:
QApplication.restoreOverrideCursor()
if proc is not None and proc.returncode == 0 and os.path.exists(path):
self._set_status(f"Saved: {os.path.basename(path)}")
else:
self._set_status("Save failed")
class MainWindow(QMainWindow):