Version 3.4.0: Edit tab (edit list, large subtitle, replacements with effects and presets), Sounds tab only browses, ackit fx (pedalboard + own reverb) and replace --fx, first Linux build, catalog without a mod package
This commit is contained in:
181
app/preview.py
Normal file
181
app/preview.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""Sounds tab: preview of the selected sound (listen, read its subtitle) with the way into the Edit tab. No
|
||||
editing here: editing is the Edit tab's job."""
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from ackit import audio, subtitles, workspace as W
|
||||
from . import icons, theme
|
||||
from .editor import STATE_TEXT
|
||||
from .waveform import Waveform
|
||||
|
||||
|
||||
class PreviewPanel(QWidget):
|
||||
edit_requested = Signal() # open the selected sounds in the Edit tab
|
||||
add_requested = Signal() # add them to the edit list (stay here)
|
||||
|
||||
def __init__(self, ctx, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ctx = ctx # main window: .ws, .runner, .player, .language
|
||||
self.sound = None
|
||||
self.orig = self.installed = None
|
||||
self.playing_what = None
|
||||
self._build()
|
||||
self.set_sound(None)
|
||||
|
||||
def _build(self):
|
||||
self.setObjectName("EditorPanel")
|
||||
lay = QHBoxLayout(self)
|
||||
lay.setContentsMargins(8, 6, 8, 6)
|
||||
lay.setSpacing(12)
|
||||
info = QVBoxLayout()
|
||||
info.setSpacing(3)
|
||||
self.title = QLabel()
|
||||
self.title.setObjectName("EditorTitle")
|
||||
self.title.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||
f = self.title.font()
|
||||
f.setPointSizeF(f.pointSizeF() * 1.2)
|
||||
f.setBold(True)
|
||||
self.title.setFont(f)
|
||||
self.meta = QLabel()
|
||||
self.meta.setObjectName("Muted")
|
||||
self.meta.setWordWrap(True)
|
||||
self.state = QLabel()
|
||||
self.sub = QLabel()
|
||||
self.sub.setWordWrap(True)
|
||||
self.sub.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
|
||||
for w in (self.title, self.meta, self.state, self.sub):
|
||||
info.addWidget(w)
|
||||
info.addStretch(1)
|
||||
lay.addLayout(info, 3)
|
||||
|
||||
# player: the transport buttons together, the waveforms under them (click = play from there)
|
||||
player = QVBoxLayout()
|
||||
player.setSpacing(4)
|
||||
tr = QHBoxLayout()
|
||||
tr.setSpacing(6)
|
||||
self.b_orig = self._btn("Play original", "play", lambda: self.play("orig"), "Play the game's original (Space)",
|
||||
icons.STEEL)
|
||||
self.b_inst = self._btn("Play installed", "play", lambda: self.play("inst"),
|
||||
"Play what the game has now (your replacement)", "#8f4a4e")
|
||||
self.b_stop = self._btn("Stop", "stop", self.stop, "Stop (Esc)")
|
||||
for b in (self.b_orig, self.b_inst, self.b_stop):
|
||||
tr.addWidget(b)
|
||||
tr.addStretch(1)
|
||||
player.addLayout(tr)
|
||||
waves = QGridLayout()
|
||||
waves.setVerticalSpacing(3)
|
||||
waves.setHorizontalSpacing(6)
|
||||
self.wf_orig, self.wf_inst = Waveform(), Waveform()
|
||||
for i, (w, kind, name) in enumerate(((self.wf_orig, "orig", "Original"), (self.wf_inst, "inst", "Installed"))):
|
||||
w.accent, w.bg = theme.WAVE[kind], theme.WAVE_BG
|
||||
w.setMinimumHeight(40)
|
||||
lab = QLabel(name)
|
||||
lab.setObjectName("Muted")
|
||||
waves.addWidget(lab, i, 0)
|
||||
waves.addWidget(w, i, 1)
|
||||
waves.setColumnStretch(1, 1)
|
||||
player.addLayout(waves)
|
||||
self.wf_orig.seekRequested.connect(lambda t: self.play("orig", t))
|
||||
self.wf_inst.seekRequested.connect(lambda t: self.play("inst", t))
|
||||
lay.addLayout(player, 4)
|
||||
|
||||
# editing: into the Edit tab (on the right, apart from the player)
|
||||
acts = QVBoxLayout()
|
||||
acts.setSpacing(6)
|
||||
self.b_edit = self._btn("Edit", "edit", self.edit_requested.emit,
|
||||
"Open the selected sound(s) in the Edit tab (Enter)", "#ffffff")
|
||||
self.b_edit.setObjectName("Primary")
|
||||
self.b_add = self._btn("Add to edit list", "add", self.add_requested.emit,
|
||||
"Put the selected sounds on the Edit tab's list and stay here")
|
||||
for b in (self.b_edit, self.b_add):
|
||||
acts.addWidget(b)
|
||||
acts.addStretch(1)
|
||||
lay.addSpacing(8)
|
||||
lay.addLayout(acts, 1)
|
||||
self.ctx.player.position.connect(self._pos)
|
||||
self.ctx.player.playing.connect(lambda on: None if on else self._clear_playheads())
|
||||
|
||||
@staticmethod
|
||||
def _btn(text, icon, slot, tip, color=None):
|
||||
b = QPushButton(text)
|
||||
b.setIcon(icons.icon(icon, color or icons.TEXT))
|
||||
b.clicked.connect(lambda _=False: slot())
|
||||
b.setToolTip(tip)
|
||||
return b
|
||||
|
||||
def set_sound(self, s):
|
||||
self.stop()
|
||||
self.sound, self.orig, self.installed = s, None, None
|
||||
for b in (self.b_orig, self.b_inst, self.b_edit, self.b_add, self.b_stop):
|
||||
b.setEnabled(s is not None)
|
||||
self.wf_orig.set_audio(None, 1, "select a sound" if s is None else "loading…", busy=s is not None)
|
||||
self.wf_inst.set_audio(None, 1, "")
|
||||
if s is None:
|
||||
self.title.setText("No sound selected")
|
||||
self.meta.setText("Select a sound in the list to listen to it; Edit opens it for changing.")
|
||||
self.state.setText("")
|
||||
self.sub.setText("")
|
||||
return
|
||||
self.title.setText(s.name)
|
||||
who = s.character or (("mentions " + s.characters_inferred) if s.characters_inferred else "—")
|
||||
self.meta.setText("%s / %s • %s • %.2f s, %d Hz, %s • %s"
|
||||
% (s.group, s.category, who, s.seconds, s.rate,
|
||||
{1: "mono", 2: "stereo"}.get(s.channels, "%d ch" % s.channels), s.key))
|
||||
ws = self.ctx.ws
|
||||
subs = ws.texts.for_sound(s, self.ctx.language) if ws else []
|
||||
self.sub.setText("“%s”" % " / ".join(ws.texts.current(_key(x)) or x.text for x in subs) if subs else "")
|
||||
key = s.key
|
||||
|
||||
def work(job):
|
||||
cur = ws.current_bytes(s)
|
||||
st = ws.state(s, cur)
|
||||
o = audio.decode_wem(ws.original_bytes(s))
|
||||
return key, st, o, audio.decode_wem(cur) if st != W.ORIGINAL else None
|
||||
|
||||
def failed(e, tb):
|
||||
self.wf_orig.set_audio(None, 1, "could not load: %s" % e)
|
||||
|
||||
self.ctx.runner.start(work, self._loaded, failed)
|
||||
|
||||
def _loaded(self, r):
|
||||
key, st, o, inst = r
|
||||
if not self.sound or self.sound.key != key:
|
||||
return
|
||||
self.orig, self.installed = o, inst
|
||||
self.wf_orig.set_audio(*o)
|
||||
self.wf_inst.set_audio(*(inst or (None, 1)), placeholder="same as the original")
|
||||
self.b_inst.setEnabled(inst is not None)
|
||||
self.state.setText(STATE_TEXT.get(st, st))
|
||||
|
||||
def play(self, what, start=0.0):
|
||||
a = {"orig": self.orig, "inst": self.installed}.get(what)
|
||||
if not a:
|
||||
return
|
||||
self.playing_what = what
|
||||
self.ctx.player.owner = self
|
||||
self.ctx.player.play(a[0], a[1], start, label=what)
|
||||
|
||||
def toggle_play(self):
|
||||
if self.ctx.player.is_playing():
|
||||
self.stop()
|
||||
else:
|
||||
self.play("orig")
|
||||
|
||||
def stop(self):
|
||||
self.ctx.player.stop()
|
||||
self._clear_playheads()
|
||||
|
||||
def _clear_playheads(self):
|
||||
for w in (self.wf_orig, self.wf_inst):
|
||||
w.set_playhead(None)
|
||||
|
||||
def _pos(self, t):
|
||||
if getattr(self.ctx.player, "owner", None) is not self:
|
||||
return
|
||||
wf = {"orig": self.wf_orig, "inst": self.wf_inst}.get(self.playing_what)
|
||||
if wf:
|
||||
wf.set_playhead(t)
|
||||
|
||||
|
||||
def _key(sub):
|
||||
return subtitles.key(sub.lang, sub.speech, sub.line)
|
||||
Reference in New Issue
Block a user