feat: side-by-side pinned tabs + optional tab-name in export folder

Side-by-side:
- Right-click a tab → "Show side-by-side" to pin it; 2+ pinned tabs render
  in a horizontal splitter (each with a name header + ✕ to unpin), via a
  QStackedWidget that swaps between the tab view and split view.
- Tabs are now backed by self._pws (source of truth) so persistence and
  layout work regardless of where each list is parented; pinned state saved.
- self._playlist resolves to the last-interacted pane (active pw), so exports
  and edits follow the pane you're acting in. Pinned flag persisted per profile.

Tab → export folder (optional):
- New "Tab→folder" checkbox: when on, the active tab's name is appended to the
  export folder (mp4 → mp4_BatchA). Default "List N" tabs are ignored.
- _export_folder()/_export_base_name() helpers route exports, markers, next
  label, and delete/clear paths so they stay consistent with the tab folder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 16:13:26 +02:00
parent 13c4d3f7f6
commit 251747bb0b
+227 -52
View File
@@ -3134,8 +3134,12 @@ class SnapPreviewWindow(QWidget):
class _PlaylistTabBar(QTabBar): class _PlaylistTabBar(QTabBar):
"""Tab bar whose labels can be renamed by double-clicking.""" """Tab bar whose labels can be renamed by double-clicking.
Right-click a tab to pin/unpin it for the side-by-side view.
"""
tab_renamed = pyqtSignal(int, str) tab_renamed = pyqtSignal(int, str)
pin_toggle_requested = pyqtSignal(int)
def mouseDoubleClickEvent(self, event): def mouseDoubleClickEvent(self, event):
idx = self.tabAt(event.pos()) idx = self.tabAt(event.pos())
@@ -3144,6 +3148,26 @@ class _PlaylistTabBar(QTabBar):
else: else:
super().mouseDoubleClickEvent(event) super().mouseDoubleClickEvent(event)
def contextMenuEvent(self, event):
idx = self.tabAt(event.pos())
if idx < 0:
return
from PyQt6.QtWidgets import QMenu
menu = QMenu(self)
act_pin = menu.addAction("Show side-by-side")
act_pin.setCheckable(True)
pw = None
tw = self.parent()
if hasattr(tw, "widget"):
pw = tw.widget(idx)
act_pin.setChecked(bool(getattr(pw, "_pinned", False)))
act_rename = menu.addAction("Rename…")
chosen = menu.exec(event.globalPos())
if chosen == act_pin:
self.pin_toggle_requested.emit(idx)
elif chosen == act_rename:
self._start_edit(idx)
def _start_edit(self, idx: int) -> None: def _start_edit(self, idx: int) -> None:
editor = QLineEdit(self) editor = QLineEdit(self)
editor.setText(self.tabText(idx)) editor.setText(self.tabText(idx))
@@ -3190,6 +3214,8 @@ class PlaylistWidget(QListWidget):
self._all_subcat_counts: dict[str, int] = {} # profile-wide folder → count self._all_subcat_counts: dict[str, int] = {} # profile-wide folder → count
self._separators_before: set[str] = set() # paths that show a separator row above self._separators_before: set[str] = set() # paths that show a separator row above
self._missing: set[str] = set() # paths not present on disk self._missing: set[str] = set() # paths not present on disk
self._pinned: bool = False # shown in the side-by-side view
self._label: str = "" # tab name (source of truth across views)
self._visible: list[str | None] = [] # rows shown; None = separator row self._visible: list[str | None] = [] # rows shown; None = separator row
self._selected_path: str | None = None self._selected_path: str | None = None
self.itemClicked.connect(self._on_item_clicked) self.itemClicked.connect(self._on_item_clicked)
@@ -3691,12 +3717,15 @@ class MainWindow(QMainWindow):
# Suppress tab persistence until _load_playlist_tabs runs at the end of # Suppress tab persistence until _load_playlist_tabs runs at the end of
# __init__ (the profile combo it needs doesn't exist yet). # __init__ (the profile combo it needs doesn't exist yet).
self._loading_tabs = True self._loading_tabs = True
self._pws: list[PlaylistWidget] = [] # source of truth for all lists
self._active_pw: "PlaylistWidget | None" = None # last-interacted list
self._playlist_tabs = QTabWidget() self._playlist_tabs = QTabWidget()
self._playlist_tabs.setTabBar(_PlaylistTabBar()) self._playlist_tabs.setTabBar(_PlaylistTabBar())
self._playlist_tabs.setTabsClosable(True) self._playlist_tabs.setTabsClosable(True)
self._playlist_tabs.setMovable(True) self._playlist_tabs.setMovable(True)
self._playlist_tabs.setDocumentMode(True) self._playlist_tabs.setDocumentMode(True)
self._playlist_tabs.tabBar().tab_renamed.connect(self._on_tab_renamed) self._playlist_tabs.tabBar().tab_renamed.connect(self._on_tab_renamed)
self._playlist_tabs.tabBar().pin_toggle_requested.connect(self._on_pin_toggle)
self._playlist_tabs.tabCloseRequested.connect(self._on_close_tab) self._playlist_tabs.tabCloseRequested.connect(self._on_close_tab)
self._playlist_tabs.currentChanged.connect(self._on_tab_changed) self._playlist_tabs.currentChanged.connect(self._on_tab_changed)
self._btn_add_tab = QPushButton("+") self._btn_add_tab = QPushButton("+")
@@ -3705,6 +3734,15 @@ class MainWindow(QMainWindow):
self._btn_add_tab.clicked.connect(lambda: self._add_playlist_tab()) self._btn_add_tab.clicked.connect(lambda: self._add_playlist_tab())
self._playlist_tabs.setCornerWidget( self._playlist_tabs.setCornerWidget(
self._btn_add_tab, Qt.Corner.TopRightCorner) self._btn_add_tab, Qt.Corner.TopRightCorner)
# Side-by-side container (shown when 2+ tabs are pinned).
self._split_container = QWidget()
self._split_layout = QHBoxLayout(self._split_container)
self._split_layout.setContentsMargins(0, 0, 0, 0)
self._split_layout.setSpacing(2)
from PyQt6.QtWidgets import QStackedWidget
self._list_stack = QStackedWidget()
self._list_stack.addWidget(self._playlist_tabs) # page 0: tabs
self._list_stack.addWidget(self._split_container) # page 1: side-by-side
# Start with one empty tab; real contents loaded later via _load_playlist_tabs. # Start with one empty tab; real contents loaded later via _load_playlist_tabs.
self._add_playlist_tab("List 1", select=True) self._add_playlist_tab("List 1", select=True)
@@ -3795,6 +3833,16 @@ class MainWindow(QMainWindow):
self._btn_folder.setFixedWidth(30) self._btn_folder.setFixedWidth(30)
self._btn_folder.setToolTip("Browse for output folder") self._btn_folder.setToolTip("Browse for output folder")
self._btn_folder.clicked.connect(self._pick_folder) self._btn_folder.clicked.connect(self._pick_folder)
self._chk_tab_folder = QCheckBox("Tab→folder")
self._chk_tab_folder.setToolTip(
"When on, append the active tab's name to the export folder\n"
"(e.g. mp4 → mp4_BatchA) for files in that tab. Default 'List N'\n"
"tabs are ignored.")
self._chk_tab_folder.setChecked(
self._settings.value("tab_in_folder", "false") == "true")
self._chk_tab_folder.toggled.connect(
lambda v: self._settings.setValue("tab_in_folder", "true" if v else "false"))
self._chk_tab_folder.toggled.connect(self._on_tab_folder_toggled)
self._spn_resize = QSpinBox() self._spn_resize = QSpinBox()
self._spn_resize.setRange(0, 4320) self._spn_resize.setRange(0, 4320)
self._spn_resize.setSingleStep(64) self._spn_resize.setSingleStep(64)
@@ -4124,6 +4172,7 @@ class MainWindow(QMainWindow):
path_row.addWidget(QLabel("Folder:")) path_row.addWidget(QLabel("Folder:"))
path_row.addWidget(self._txt_folder, stretch=1) path_row.addWidget(self._txt_folder, stretch=1)
path_row.addWidget(self._btn_folder) path_row.addWidget(self._btn_folder)
path_row.addWidget(self._chk_tab_folder)
# Row 3 — video + encoding settings # Row 3 — video + encoding settings
settings_row = QHBoxLayout() settings_row = QHBoxLayout()
@@ -4203,7 +4252,7 @@ class MainWindow(QMainWindow):
left_top.addWidget(self._btn_show_hidden) left_top.addWidget(self._btn_show_hidden)
left_layout.addLayout(left_top) left_layout.addLayout(left_top)
left_layout.addWidget(self._playlist_filter) left_layout.addWidget(self._playlist_filter)
left_layout.addWidget(self._playlist_tabs) left_layout.addWidget(self._list_stack)
# Scan results panel (right side) # Scan results panel (right side)
self._scan_panel = ScanResultsPanel(self._db) self._scan_panel = ScanResultsPanel(self._db)
@@ -4408,15 +4457,44 @@ class MainWindow(QMainWindow):
@property @property
def _playlist(self) -> "PlaylistWidget": def _playlist(self) -> "PlaylistWidget":
"""The PlaylistWidget of the currently active file-list tab.""" """The active file list — the last-interacted pane/tab."""
return self._playlist_tabs.currentWidget() if self._active_pw is not None and self._active_pw in self._pws:
return self._active_pw
w = self._playlist_tabs.currentWidget()
if w is not None:
return w
return self._pws[0] if self._pws else None
# ── Export folder (optionally tagged with the active tab name) ──
def _active_tab_name(self) -> str:
"""Sanitized name of the active tab, or "" for default 'List N' tabs."""
pw = self._playlist
label = "" if pw is None else (getattr(pw, "_label", "") or "")
label = label.strip().replace(" ", "_")
if not label or (label.startswith("List_") and label[5:].isdigit()):
return ""
return label
def _export_folder(self) -> str:
"""The export base folder, with the active tab name appended when enabled."""
base = self._txt_folder.text()
if getattr(self, "_chk_tab_folder", None) and self._chk_tab_folder.isChecked():
name = self._active_tab_name()
if name:
base = base.rstrip(os.sep) + "_" + name
return base
def _export_base_name(self) -> str:
return os.path.basename(self._export_folder())
def _on_tab_folder_toggled(self, _checked: bool = False) -> None:
if self._file_path:
self._refresh_markers()
self._refresh_playlist_checks()
self._update_next_label()
# ── File-list tabs ─────────────────────────────────────────── # ── File-list tabs ───────────────────────────────────────────
def _add_playlist_tab(self, label: str | None = None, def _wire_pw(self, pw: "PlaylistWidget") -> None:
files: list[str] | None = None,
separators: list[str] | None = None,
select: bool = True) -> "PlaylistWidget":
pw = PlaylistWidget()
pw.file_selected.connect(self._load_file) pw.file_selected.connect(self._load_file)
pw.hide_requested.connect(self._on_hide_files) pw.hide_requested.connect(self._on_hide_files)
pw.unhide_requested.connect(self._on_unhide_files) pw.unhide_requested.connect(self._on_unhide_files)
@@ -4425,42 +4503,129 @@ class MainWindow(QMainWindow):
pw.disable_all_requested.connect(self._disable_all_subcats) pw.disable_all_requested.connect(self._disable_all_subcats)
pw.enable_all_requested.connect(self._enable_all_subcats) pw.enable_all_requested.connect(self._enable_all_subcats)
pw.separators_changed.connect(self._save_playlist_tabs) pw.separators_changed.connect(self._save_playlist_tabs)
if label is None:
label = f"List {self._playlist_tabs.count() + 1}" def _add_playlist_tab(self, label: str | None = None,
idx = self._playlist_tabs.addTab(pw, label) files: list[str] | None = None,
separators: list[str] | None = None,
select: bool = True) -> "PlaylistWidget":
pw = PlaylistWidget()
self._wire_pw(pw)
pw._label = label or f"List {len(self._pws) + 1}"
self._pws.append(pw)
if separators: if separators:
pw._separators_before = set(separators) pw._separators_before = set(separators)
if files: if files:
# Keep missing files so they're flagged in the list, not silently dropped. # Keep missing files so they're flagged in the list, not silently dropped.
pw.add_files(files, allow_missing=True) pw.add_files(files, allow_missing=True)
if select:
self._playlist_tabs.setCurrentIndex(idx)
if not self._loading_tabs: if not self._loading_tabs:
self._refresh_layout()
if select and not pw._pinned:
self._playlist_tabs.setCurrentWidget(pw)
self._active_pw = pw
self._save_playlist_tabs() self._save_playlist_tabs()
return pw return pw
# ── Layout: tabs vs. side-by-side ────────────────────────────
def _detach_all_pws(self) -> None:
for pw in self._pws:
pw.setParent(None)
def _clear_split_container(self) -> None:
while self._split_layout.count():
item = self._split_layout.takeAt(0)
w = item.widget()
if w is not None:
w.deleteLater()
def _refresh_layout(self) -> None:
"""Render self._pws either as tabs or, when 2+ are pinned, side-by-side."""
pinned = [pw for pw in self._pws if pw._pinned]
prev = self._loading_tabs
self._loading_tabs = True
try:
self._detach_all_pws()
self._playlist_tabs.clear()
self._clear_split_container()
if len(pinned) >= 2:
for pw in self._pws:
if not pw._pinned:
self._playlist_tabs.addTab(pw, pw._label)
splitter = QSplitter(Qt.Orientation.Horizontal)
for pw in pinned:
panel = QWidget()
pl = QVBoxLayout(panel)
pl.setContentsMargins(0, 0, 0, 0)
pl.setSpacing(0)
hdr = QHBoxLayout()
lbl = QLabel(pw._label)
lbl.setStyleSheet("font-weight: bold; padding: 2px 4px;")
btn = QPushButton("")
btn.setFixedWidth(20)
btn.setToolTip("Remove from side-by-side")
btn.clicked.connect(lambda _=False, w=pw: self._on_unpin(w))
hdr.addWidget(lbl, 1)
hdr.addWidget(btn)
pl.addLayout(hdr)
pl.addWidget(pw)
splitter.addWidget(panel)
self._split_layout.addWidget(splitter)
self._list_stack.setCurrentWidget(self._split_container)
else:
for pw in self._pws:
self._playlist_tabs.addTab(pw, pw._label)
self._list_stack.setCurrentWidget(self._playlist_tabs)
finally:
self._loading_tabs = prev
def _on_pin_toggle(self, idx: int) -> None:
pw = self._playlist_tabs.widget(idx)
if pw is None:
return
pw._pinned = not pw._pinned
if pw._pinned and sum(1 for w in self._pws if w._pinned) < 2:
self._show_status("Pin another tab to show them side-by-side", 3500)
self._refresh_layout()
self._save_playlist_tabs()
def _on_unpin(self, pw: "PlaylistWidget") -> None:
pw._pinned = False
self._refresh_layout()
self._save_playlist_tabs()
def _on_filter_changed(self, text: str) -> None: def _on_filter_changed(self, text: str) -> None:
pw = self._playlist pw = self._playlist
if pw is not None: if pw is not None:
pw.set_filter(text) pw.set_filter(text)
def _on_tab_changed(self, _idx: int) -> None: def _on_tab_changed(self, _idx: int) -> None:
if self._loading_tabs or self._playlist is None: if self._loading_tabs:
return return
self._playlist.set_filter(self._playlist_filter.text()) w = self._playlist_tabs.currentWidget()
if w is not None:
self._active_pw = w
w.set_filter(self._playlist_filter.text())
self._apply_playlist_filters() self._apply_playlist_filters()
self._save_playlist_tabs() self._save_playlist_tabs()
def _on_close_tab(self, idx: int) -> None: def _on_close_tab(self, idx: int) -> None:
if self._playlist_tabs.count() <= 1: if len(self._pws) <= 1:
self._show_status("Can't close the last tab", 3000) self._show_status("Can't close the last tab", 3000)
return return
w = self._playlist_tabs.widget(idx) pw = self._playlist_tabs.widget(idx)
self._playlist_tabs.removeTab(idx) if pw is None or pw not in self._pws:
w.deleteLater() return
self._pws.remove(pw)
if self._active_pw is pw:
self._active_pw = None
pw.setParent(None)
pw.deleteLater()
self._refresh_layout()
self._save_playlist_tabs() self._save_playlist_tabs()
def _on_tab_renamed(self, _idx: int, _text: str) -> None: def _on_tab_renamed(self, idx: int, text: str) -> None:
pw = self._playlist_tabs.widget(idx)
if pw is not None:
pw._label = text
self._save_playlist_tabs() self._save_playlist_tabs()
def _playlist_tabs_key(self, profile: str | None = None) -> str: def _playlist_tabs_key(self, profile: str | None = None) -> str:
@@ -4470,26 +4635,28 @@ class MainWindow(QMainWindow):
if self._loading_tabs: if self._loading_tabs:
return return
import json import json
tabs = [] tabs = [{
for i in range(self._playlist_tabs.count()): "label": pw._label,
pw = self._playlist_tabs.widget(i) "files": list(pw._paths),
tabs.append({ "separators": sorted(pw._separators_before),
"label": self._playlist_tabs.tabText(i), "pinned": pw._pinned,
"files": list(pw._paths), } for pw in self._pws]
"separators": sorted(pw._separators_before), cur = self._pws.index(self._active_pw) if self._active_pw in self._pws else 0
}) data = {"tabs": tabs, "current": cur}
data = {"tabs": tabs, "current": self._playlist_tabs.currentIndex()}
self._settings.setValue(self._playlist_tabs_key(profile), json.dumps(data)) self._settings.setValue(self._playlist_tabs_key(profile), json.dumps(data))
def _load_playlist_tabs(self, profile: str | None = None) -> None: def _load_playlist_tabs(self, profile: str | None = None) -> None:
"""Rebuild all file-list tabs for *profile* from settings.""" """Rebuild all file-list tabs for *profile* from settings."""
import json import json
self._loading_tabs = True self._loading_tabs = True
self._active_pw = None
try: try:
while self._playlist_tabs.count(): self._detach_all_pws()
w = self._playlist_tabs.widget(0) self._playlist_tabs.clear()
self._playlist_tabs.removeTab(0) self._clear_split_container()
w.deleteLater() for pw in self._pws:
pw.deleteLater()
self._pws = []
raw = self._settings.value(self._playlist_tabs_key(profile), "") raw = self._settings.value(self._playlist_tabs_key(profile), "")
data = None data = None
if raw: if raw:
@@ -4497,6 +4664,7 @@ class MainWindow(QMainWindow):
data = json.loads(raw) data = json.loads(raw)
except (ValueError, TypeError): except (ValueError, TypeError):
data = None data = None
cur = 0
if not data or not data.get("tabs"): if not data or not data.get("tabs"):
# Legacy fallback: one tab from old session_files/separators. # Legacy fallback: one tab from old session_files/separators.
p = profile or self._profile p = profile or self._profile
@@ -4506,20 +4674,23 @@ class MainWindow(QMainWindow):
seps = [seps] if seps else [] seps = [seps] if seps else []
self._add_playlist_tab( self._add_playlist_tab(
"List 1", files=list(files), "List 1", files=list(files),
separators=seps, select=True) separators=seps, select=False)
else: else:
for t in data["tabs"]: for t in data["tabs"]:
self._add_playlist_tab( pw = self._add_playlist_tab(
t.get("label", "List"), t.get("label", "List"),
files=list(t.get("files", [])), files=list(t.get("files", [])),
separators=t.get("separators", []), select=False) separators=t.get("separators", []), select=False)
cur = min(max(0, data.get("current", 0)), pw._pinned = bool(t.get("pinned"))
self._playlist_tabs.count() - 1) cur = min(max(0, data.get("current", 0)), len(self._pws) - 1)
self._playlist_tabs.setCurrentIndex(cur)
finally: finally:
self._loading_tabs = False self._loading_tabs = False
if self._playlist is not None: self._refresh_layout()
self._playlist.set_filter(self._playlist_filter.text()) if self._pws:
self._active_pw = self._pws[cur]
if not self._active_pw._pinned:
self._playlist_tabs.setCurrentWidget(self._active_pw)
self._active_pw.set_filter(self._playlist_filter.text())
def _on_profile_activated(self, index: int) -> None: def _on_profile_activated(self, index: int) -> None:
text = self._cmb_profile.itemText(index) text = self._cmb_profile.itemText(index)
@@ -4740,6 +4911,10 @@ class MainWindow(QMainWindow):
def _load_file(self, path: str): def _load_file(self, path: str):
if getattr(self, "_loading_tabs", False): if getattr(self, "_loading_tabs", False):
return # ignore auto-selection while rebuilding tabs return # ignore auto-selection while rebuilding tabs
# The list that emitted this becomes the active pane (side-by-side).
sender = self.sender()
if isinstance(sender, PlaylistWidget) and sender in self._pws:
self._active_pw = sender
if not os.path.isfile(path): if not os.path.isfile(path):
self._show_status(f"File not found: {os.path.basename(path)}", 5000) self._show_status(f"File not found: {os.path.basename(path)}", 5000)
return return
@@ -4821,7 +4996,7 @@ class MainWindow(QMainWindow):
# Run DB fuzzy match off the main thread — can be slow on large databases. # Run DB fuzzy match off the main thread — can be slow on large databases.
filename = os.path.basename(self._file_path) filename = os.path.basename(self._file_path)
self._db_worker = _DBWorker(self._db, filename, self._profile, self._db_worker = _DBWorker(self._db, filename, self._profile,
self._txt_folder.text()) self._export_folder())
self._db_worker.result.connect(self._on_db_result) self._db_worker.result.connect(self._on_db_result)
self._db_worker.start() self._db_worker.start()
@@ -4838,7 +5013,7 @@ class MainWindow(QMainWindow):
def _refresh_markers(self) -> None: def _refresh_markers(self) -> None:
filename = os.path.basename(self._file_path) filename = os.path.basename(self._file_path)
folder = self._txt_folder.text() folder = self._export_folder()
markers = self._db.get_markers(filename, self._profile, folder) markers = self._db.get_markers(filename, self._profile, folder)
self._timeline.set_markers(markers) self._timeline.set_markers(markers)
others = self._db.get_other_folder_markers( others = self._db.get_other_folder_markers(
@@ -4850,7 +5025,7 @@ class MainWindow(QMainWindow):
self._timeline.set_other_markers({}) self._timeline.set_other_markers({})
return return
filename = os.path.basename(self._file_path) filename = os.path.basename(self._file_path)
folder = self._txt_folder.text() folder = self._export_folder()
others = self._db.get_other_folder_markers( others = self._db.get_other_folder_markers(
filename, self._profile, folder) filename, self._profile, folder)
self._timeline.set_other_markers(others) self._timeline.set_other_markers(others)
@@ -4890,7 +5065,7 @@ class MainWindow(QMainWindow):
if not deleted: if not deleted:
self._db.delete_by_output_path(output_path) self._db.delete_by_output_path(output_path)
deleted = [output_path] deleted = [output_path]
folder = self._txt_folder.text() folder = self._export_folder()
for path in deleted: for path in deleted:
if os.path.isdir(path): if os.path.isdir(path):
shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, ignore_errors=True)
@@ -4920,7 +5095,7 @@ class MainWindow(QMainWindow):
return return
filename = os.path.basename(self._file_path) filename = os.path.basename(self._file_path)
markers = self._db.get_markers(filename, self._profile) markers = self._db.get_markers(filename, self._profile)
folder = self._txt_folder.text() folder = self._export_folder()
total_files = 0 total_files = 0
for _, _, output_path, _ in markers: for _, _, output_path, _ in markers:
group = self._db.delete_group(output_path) group = self._db.delete_group(output_path)
@@ -5060,7 +5235,7 @@ class MainWindow(QMainWindow):
if reply != QMessageBox.StandardButton.Yes: if reply != QMessageBox.StandardButton.Yes:
return return
# Delete all group clips from disk # Delete all group clips from disk
folder = self._txt_folder.text() folder = self._export_folder()
for path in all_paths: for path in all_paths:
if os.path.isdir(path): if os.path.isdir(path):
shutil.rmtree(path, ignore_errors=True) shutil.rmtree(path, ignore_errors=True)
@@ -5832,7 +6007,7 @@ class MainWindow(QMainWindow):
) )
if reply != QMessageBox.StandardButton.Yes: if reply != QMessageBox.StandardButton.Yes:
return return
folder = self._txt_folder.text() or "" folder = self._export_folder() or ""
vid_dirs: set[str] = set() vid_dirs: set[str] = set()
for p in all_paths: for p in all_paths:
if os.path.isdir(p): if os.path.isdir(p):
@@ -6255,7 +6430,7 @@ class MainWindow(QMainWindow):
self._btn_auto_export.setEnabled(True) self._btn_auto_export.setEnabled(True)
return return
folder = self._txt_folder.text() folder = self._export_folder()
name = self._txt_name.text() or "clip" name = self._txt_name.text() or "clip"
fmt = self._cmb_format.currentText() fmt = self._cmb_format.currentText()
image_sequence = fmt == "WebP sequence" image_sequence = fmt == "WebP sequence"
@@ -6471,7 +6646,7 @@ class MainWindow(QMainWindow):
) )
def _update_next_label(self): def _update_next_label(self):
folder = self._txt_folder.text() folder = self._export_folder()
name = self._txt_name.text() or "clip" name = self._txt_name.text() or "clip"
vid_name = self._get_vid_folder(folder) vid_name = self._get_vid_folder(folder)
vid_folder = os.path.join(folder, vid_name) vid_folder = os.path.join(folder, vid_name)
@@ -6511,7 +6686,7 @@ class MainWindow(QMainWindow):
fmt = self._cmb_format.currentText() fmt = self._cmb_format.currentText()
image_sequence = fmt == "WebP sequence" image_sequence = fmt == "WebP sequence"
folder = self._txt_folder.text() folder = self._export_folder()
if folder_suffix: if folder_suffix:
folder = folder.rstrip(os.sep) + "_" + folder_suffix folder = folder.rstrip(os.sep) + "_" + folder_suffix
os.makedirs(folder, exist_ok=True) os.makedirs(folder, exist_ok=True)
@@ -6777,7 +6952,7 @@ class MainWindow(QMainWindow):
if not groups: if not groups:
self._show_status("No manual exports to re-export") self._show_status("No manual exports to re-export")
return return
folder = self._txt_folder.text() folder = self._export_folder()
spread = self._spn_spread.value() spread = self._spn_spread.value()
clip_dur = self._clip_dur clip_dur = self._clip_dur