Release v1.0.9: persist UI layout and fix SxxExx double-S renames.
Save window geometry, splitter sizes, and preview column widths on exit; restore on launch. Fix episode rewrite duplicating the season letter. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+11
-6
@@ -13,7 +13,7 @@ from .tvdb_client import TvdbEpisode
|
||||
|
||||
# S01E05 - Title or S01E05-E06 - Title
|
||||
PATTERN_SXXEXX = re.compile(
|
||||
r"^(.*?[Ss])(\d+)([Ee])(\d+)(-[Ee](\d+))?(.*)$",
|
||||
r"^(.*?)([Ss])(\d+)([Ee])(\d+)(-[Ee](\d+))?(.*)$",
|
||||
)
|
||||
# Show Name 04x01 Title, 4x01 - Title, etc.
|
||||
PATTERN_NXNN = re.compile(
|
||||
@@ -60,7 +60,7 @@ def parse_episode_stem(stem: str, pattern: str = DEFAULT_EPISODE_PATTERN) -> Opt
|
||||
|
||||
m = PATTERN_SXXEXX.match(stem)
|
||||
if m:
|
||||
prefix_before_s, season_s, _e, ep_s, _dash, ep2_s, rest = m.groups()
|
||||
prefix, s_letter, season_s, e_letter, ep_s, _range_block, ep2_s, rest = m.groups()
|
||||
try:
|
||||
season = int(season_s)
|
||||
old_first = int(ep_s)
|
||||
@@ -78,7 +78,9 @@ def parse_episode_stem(stem: str, pattern: str = DEFAULT_EPISODE_PATTERN) -> Opt
|
||||
title = _clean_title(rest)
|
||||
return {
|
||||
"format": "sxxexx",
|
||||
"prefix_before_s": prefix_before_s,
|
||||
"prefix": prefix,
|
||||
"s_letter": s_letter,
|
||||
"e_letter": e_letter,
|
||||
"season": season,
|
||||
"season_pad": len(season_s),
|
||||
"episode_pad": len(ep_s),
|
||||
@@ -143,11 +145,14 @@ def rewrite_episode_stem(
|
||||
s_pad = max(parsed["season_pad"], len(str(new_season)))
|
||||
e_pad = max(parsed["episode_pad"], pad, len(str(new_ep)))
|
||||
e1 = str(new_ep).zfill(e_pad)
|
||||
prefix = f"{parsed['prefix_before_s']}S{new_season:0{s_pad}d}E"
|
||||
head = (
|
||||
f"{parsed['prefix']}{parsed['s_letter']}{new_season:0{s_pad}d}{parsed['e_letter']}"
|
||||
)
|
||||
if span <= 1:
|
||||
return f"{prefix}{e1}{parsed['suffix']}"
|
||||
return f"{head}{e1}{parsed['suffix']}"
|
||||
e2 = str(new_ep + span - 1).zfill(e_pad)
|
||||
return f"{prefix}{e1}-E{e2}{parsed['suffix']}"
|
||||
range_prefix = f"-{parsed['e_letter']}"
|
||||
return f"{head}{e1}{range_prefix}{e2}{parsed['suffix']}"
|
||||
|
||||
|
||||
def rewrite_episode_number(
|
||||
|
||||
+35
-1
@@ -25,7 +25,7 @@ from PyQt6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QDir, QItemSelectionModel
|
||||
from PyQt6.QtCore import Qt, QDir, QItemSelectionModel, QSettings
|
||||
from PyQt6.QtGui import QFont, QColor
|
||||
|
||||
from engine.pipeline import compute_preview, perform_renames, save_undo_log, load_undo_log, perform_undo
|
||||
@@ -53,7 +53,9 @@ class MainWindow(QMainWindow):
|
||||
self._base_dir = ""
|
||||
self._file_names: list[str] = []
|
||||
self._preview_by_orig: dict[str, str] = {} # original name -> new name for selection preview
|
||||
self._split: QSplitter | None = None
|
||||
self._setup_ui()
|
||||
self._restore_layout()
|
||||
self._refresh_preview()
|
||||
|
||||
def _setup_ui(self):
|
||||
@@ -172,8 +174,40 @@ class MainWindow(QMainWindow):
|
||||
split.addWidget(right)
|
||||
|
||||
split.setSizes([320, 680])
|
||||
self._split = split
|
||||
layout.addWidget(split, 1)
|
||||
|
||||
def _settings(self) -> QSettings:
|
||||
return QSettings()
|
||||
|
||||
def _restore_layout(self) -> None:
|
||||
settings = self._settings()
|
||||
geometry = settings.value("window/geometry")
|
||||
if geometry is not None:
|
||||
self.restoreGeometry(geometry)
|
||||
if self._split is not None:
|
||||
split_state = settings.value("splitter/state")
|
||||
if split_state is not None:
|
||||
self._split.restoreState(split_state)
|
||||
header = self.preview_table.horizontalHeader()
|
||||
header_state = settings.value("preview_table/header")
|
||||
if header_state is not None:
|
||||
header.restoreState(header_state)
|
||||
|
||||
def _save_layout(self) -> None:
|
||||
settings = self._settings()
|
||||
settings.setValue("window/geometry", self.saveGeometry())
|
||||
if self._split is not None:
|
||||
settings.setValue("splitter/state", self._split.saveState())
|
||||
settings.setValue(
|
||||
"preview_table/header",
|
||||
self.preview_table.horizontalHeader().saveState(),
|
||||
)
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
self._save_layout()
|
||||
super().closeEvent(event)
|
||||
|
||||
def _browse(self):
|
||||
path = QFileDialog.getExistingDirectory(self, "Select folder")
|
||||
if path:
|
||||
|
||||
@@ -21,7 +21,11 @@ def main():
|
||||
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
||||
)
|
||||
app = QApplication(sys.argv)
|
||||
app.setOrganizationName("HSRename")
|
||||
app.setOrganizationDomain("hisora.dev")
|
||||
app.setApplicationName("HSRename")
|
||||
version_file = _root / "VERSION"
|
||||
app.setApplicationVersion(version_file.read_text().strip() if version_file.is_file() else "")
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
HSRename 1.0.9
|
||||
- Remember window size, splitter position, and preview column widths between sessions
|
||||
- Fix double S in SxxExx renames (SS02E33) when show name ends before the episode code
|
||||
Reference in New Issue
Block a user