Refactor HVideoTool to support project-based workflow: introduced project management features, updated UI for project handling, and enhanced documentation in README and CLAUDE.md. The tool now organizes images and settings into projects, improving usability and detection caching.

This commit is contained in:
Leonid Pershin
2026-06-07 04:53:27 +03:00
parent 7f0121b7df
commit e27dfdf518
27 changed files with 2998 additions and 387 deletions
+36 -36
View File
@@ -1,7 +1,9 @@
"""Configure the restoration ("расцензурить") engine.
inpaint — no setup. deepmosaics — point at a user-installed DeepMosaics folder and
its clean weights; a CUDA GPU is strongly recommended (set GPU id, -1 = CPU/slow).
inpaint — no setup. deepmosaics — the network code is vendored (built-in); the user
picks a clean model from a dropdown of the bundled ``models/deepmosaics`` weights
(or browses to another ``clean_*.pth``). ``mosaic_position.pth`` must sit beside the
chosen model. A CUDA GPU is strongly recommended (GPU id, -1 = CPU/slow).
"""
from __future__ import annotations
@@ -22,6 +24,7 @@ from PySide6.QtWidgets import (
)
from ..config import AppConfig
from ..core.restore.deepmosaics import discover_models
class RestoreDialog(QDialog):
@@ -29,7 +32,7 @@ class RestoreDialog(QDialog):
super().__init__(parent)
self._cfg = config
self.setWindowTitle("Движок восстановления")
self.setMinimumWidth(520)
self.setMinimumWidth(560)
self.engine = QComboBox()
self.engine.addItem("Инпейнт (быстро, замазывает — без модели)", "inpaint")
@@ -37,24 +40,21 @@ class RestoreDialog(QDialog):
self.engine.setCurrentIndex(1 if config.restorer == "deepmosaics" else 0)
self.engine.currentIndexChanged.connect(self._sync)
self.dm_dir = QLineEdit(config.dm_dir or "")
self.dm_model = QLineEdit(config.dm_model or "")
self.dm_python = QLineEdit(config.dm_python or "")
self.dm_python.setPlaceholderText("по умолчанию — python текущего venv")
# Model dropdown — bundled clean models, plus the configured one if external.
self.model_combo = QComboBox()
self._populate_models(config.dm_model)
self.dm_gpu = QLineEdit(config.dm_gpu or "0")
self.dm_gpu.setPlaceholderText("0 = первая CUDA-карта, -1 = CPU (медленно)")
form = QFormLayout(self)
form.addRow("Движок:", self.engine)
form.addRow("Папка DeepMosaics:", self._with_browse(self.dm_dir, self._browse_dir))
form.addRow("Веса (clean_*.pth):", self._with_browse(self.dm_model, self._browse_model))
form.addRow("Python для DeepMosaics:", self._with_browse(self.dm_python, self._browse_python))
form.addRow("Модель:", self._with_browse(self.model_combo, self._browse_model))
form.addRow("GPU id:", self.dm_gpu)
hint = QLabel(
"DeepMosaics ставится отдельно (git clone + зависимости). Рядом с весами "
"clean_*.pth должен лежать mosaic_position.pth.\n"
"Для покадрового режима берите clean_youknow_resnet_9blocks.pth — "
"видеомодель clean_youknow_video.pth покадрово не работает. См. README."
"Модели берутся из models/deepmosaics. Нужны clean_youknow_resnet_9blocks.pth "
"и mosaic_position.pth (рядом). Видеомодель clean_*_video.pth покадрово не "
"работает и в списке не показывается. На CPU медленно — лучше GPU. См. README."
)
hint.setWordWrap(True)
form.addRow(hint)
@@ -65,41 +65,41 @@ class RestoreDialog(QDialog):
form.addRow(buttons)
self._sync()
def _with_browse(self, line: QLineEdit, slot) -> QWidget:
def _populate_models(self, current: str | None) -> None:
self.model_combo.clear()
for name, path in discover_models():
self.model_combo.addItem(name, path)
# Keep an externally-configured model selectable even if it's outside the folder.
if current and self.model_combo.findData(current) < 0:
self.model_combo.addItem(Path(current).stem + " (внешняя)", current)
if self.model_combo.count() == 0:
self.model_combo.addItem("(модели не найдены — положите в models/deepmosaics)", None)
idx = self.model_combo.findData(current) if current else 0
self.model_combo.setCurrentIndex(max(0, idx))
def _with_browse(self, widget: QWidget, slot) -> QWidget:
w = QWidget()
h = QHBoxLayout(w)
h.setContentsMargins(0, 0, 0, 0)
h.addWidget(line, 1)
btn = QPushButton("")
btn.setMaximumWidth(32)
h.addWidget(widget, 1)
btn = QPushButton("Обзор")
btn.clicked.connect(slot)
h.addWidget(btn)
return w
def _sync(self) -> None:
is_dm = self.engine.currentData() == "deepmosaics"
for w in (self.dm_dir, self.dm_model, self.dm_python, self.dm_gpu):
w.setEnabled(is_dm)
def _browse_dir(self) -> None:
d = QFileDialog.getExistingDirectory(self, "Папка DeepMosaics", self.dm_dir.text())
if d:
self.dm_dir.setText(d)
self.model_combo.setEnabled(is_dm)
self.dm_gpu.setEnabled(is_dm)
def _browse_model(self) -> None:
start = self.dm_model.text() or self.dm_dir.text()
p, _ = QFileDialog.getOpenFileName(self, "Веса DeepMosaics", start, "Веса (*.pth);;Все файлы (*.*)")
p, _ = QFileDialog.getOpenFileName(self, "Веса DeepMosaics", "", "Веса (*.pth);;Все файлы (*.*)")
if p:
self.dm_model.setText(p)
def _browse_python(self) -> None:
p, _ = QFileDialog.getOpenFileName(self, "Python для DeepMosaics", self.dm_python.text(), "python (*.exe);;Все файлы (*.*)")
if p:
self.dm_python.setText(p)
if self.model_combo.findData(p) < 0:
self.model_combo.addItem(Path(p).stem, p)
self.model_combo.setCurrentIndex(self.model_combo.findData(p))
def apply_to_config(self) -> None:
self._cfg.restorer = self.engine.currentData()
self._cfg.dm_dir = self.dm_dir.text().strip() or None
self._cfg.dm_model = self.dm_model.text().strip() or None
self._cfg.dm_python = self.dm_python.text().strip() or None
self._cfg.dm_model = self.model_combo.currentData()
self._cfg.dm_gpu = self.dm_gpu.text().strip() or "0"