182 lines
7.3 KiB
Python
182 lines
7.3 KiB
Python
"""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)
|