"""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)