Add FFmpeg-based video integrity check
This commit is contained in:
+85
-1
@@ -20,7 +20,7 @@ from PySide6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QSplitter, QTreeWidget, QTreeWidgetItem,
|
||||
QTableWidget, QTableWidgetItem, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
QPushButton, QFileDialog, QMessageBox, QInputDialog, QDialog, QDialogButtonBox,
|
||||
QHeaderView, QMenu, QAbstractItemView, QCheckBox, QComboBox,
|
||||
QHeaderView, QMenu, QAbstractItemView, QCheckBox, QComboBox, QProgressDialog,
|
||||
)
|
||||
|
||||
from src.core.file_manager import FileManager
|
||||
@@ -518,6 +518,7 @@ class QtApp(QMainWindow):
|
||||
|
||||
tests_menu = bar.addMenu("&Testy")
|
||||
self._add_action(tests_menu, "Kontrola konzistence dat…", self.check_consistency)
|
||||
self._add_action(tests_menu, "Kontrola integrity videa…", self.check_video_integrity)
|
||||
|
||||
def _add_action(self, menu: QMenu, text: str, slot, shortcut: str | None = None) -> QAction:
|
||||
action = QAction(text, self)
|
||||
@@ -1130,6 +1131,89 @@ class QtApp(QMainWindow):
|
||||
box.setDetailedText("\n".join(detail))
|
||||
box.exec()
|
||||
|
||||
def check_video_integrity(self) -> None:
|
||||
"""Test: scan movies for corrupted / unreadable video data (FFmpeg)."""
|
||||
if not self.filehandler.movies_dir:
|
||||
QMessageBox.information(self, "Testy", "Nejprve nastavte pool.")
|
||||
return
|
||||
|
||||
# Scope: selected movies, or the whole pool when nothing is selected.
|
||||
files = self._selected_movies() or self.filehandler.filelist
|
||||
if not files:
|
||||
QMessageBox.information(self, "Kontrola integrity", "Pool je prázdný.")
|
||||
return
|
||||
scope = "vybrané filmy" if self._selected_movies() else "celý pool"
|
||||
|
||||
# Depth: deep = full decode (thorough, slow), quick = ffprobe (fast).
|
||||
box = QMessageBox(self)
|
||||
box.setIcon(QMessageBox.Question)
|
||||
box.setWindowTitle("Kontrola integrity videa")
|
||||
box.setText(
|
||||
f"Zkontrolovat {scope} ({len(files)}).\n\n"
|
||||
"Důkladná: plné dekódování přes ffmpeg — odhalí i poškozená data "
|
||||
"uvnitř souboru, ale čte celý soubor (pomalé).\n"
|
||||
"Rychlá: ffprobe — odhalí nečitelné/uříznuté soubory (rychlé)."
|
||||
)
|
||||
deep_btn = box.addButton("Důkladná", QMessageBox.AcceptRole)
|
||||
quick_btn = box.addButton("Rychlá", QMessageBox.AcceptRole)
|
||||
box.addButton("Zrušit", QMessageBox.RejectRole)
|
||||
box.exec()
|
||||
clicked = box.clickedButton()
|
||||
if clicked not in (deep_btn, quick_btn):
|
||||
return
|
||||
deep = clicked is deep_btn
|
||||
|
||||
progress = QProgressDialog(
|
||||
"Kontroluji integritu videa…", "Zrušit", 0, len(files), self)
|
||||
progress.setWindowTitle("Kontrola integrity videa")
|
||||
progress.setWindowModality(Qt.WindowModal)
|
||||
progress.setMinimumDuration(0)
|
||||
|
||||
def on_progress(done: int, total: int, f: File) -> None:
|
||||
progress.setValue(done)
|
||||
progress.setLabelText(f"[{done}/{total}] {f.filename}")
|
||||
QApplication.processEvents()
|
||||
|
||||
result = self.filehandler.scan_video_integrity(
|
||||
files, deep=deep, on_progress=on_progress,
|
||||
should_cancel=progress.wasCanceled)
|
||||
progress.setValue(len(files))
|
||||
|
||||
if not result["ok"]:
|
||||
QMessageBox.warning(self, "Kontrola integrity", result["error"])
|
||||
return
|
||||
|
||||
problems = result["problems"]
|
||||
checked = result["checked"]
|
||||
suffix = " (zrušeno)" if result["cancelled"] else ""
|
||||
if not problems:
|
||||
QMessageBox.information(
|
||||
self, "Kontrola integrity videa",
|
||||
f"✅ Bez problémů — zkontrolováno {checked} souborů{suffix}.")
|
||||
return
|
||||
|
||||
bad = [r for r in problems if r.checked]
|
||||
unchecked = [r for r in problems if not r.checked]
|
||||
detail: list[str] = []
|
||||
if bad:
|
||||
detail.append(f"■ Poškozené / vadné ({len(bad)}):")
|
||||
detail += [f" • {r.path.name} — {r.error}" for r in bad]
|
||||
detail.append("")
|
||||
if unchecked:
|
||||
detail.append(f"■ Nešlo zkontrolovat ({len(unchecked)}):")
|
||||
detail += [f" • {r.path.name} — {r.error}" for r in unchecked]
|
||||
|
||||
msg = QMessageBox(self)
|
||||
msg.setIcon(QMessageBox.Warning)
|
||||
msg.setWindowTitle("Kontrola integrity — nalezeny problémy")
|
||||
msg.setText(
|
||||
f"Zkontrolováno: {checked}{suffix}\n"
|
||||
f"Poškozené / vadné: {len(bad)}\n"
|
||||
f"Nešlo zkontrolovat: {len(unchecked)}\n\n"
|
||||
"Podrobnosti zobrazíš přes „Show Details…\".")
|
||||
msg.setDetailedText("\n".join(detail))
|
||||
msg.exec()
|
||||
|
||||
def closeEvent(self, event) -> None: # noqa: N802 — Qt override
|
||||
self.filehandler.global_config["window_geometry"] = f"{self.width()}x{self.height()}"
|
||||
from src.core.config import save_global_config
|
||||
|
||||
Reference in New Issue
Block a user