Каталогизатор документов: разбор папки, тезисы, поиск, GUI

Программа принимает папку с документами, извлекает из них текст и
метаданные и строит каталог: краткое описание, тезисы и ключевые слова
для каждого файла плюс поиск по всему собранному.

Ядро:
- extract: PDF, DJVU, DOC(X), RTF, ODT, CHM, EPUB, FB2, PPT(X), XLS(X), TXT;
  PDF передаётся MuPDF потоком в память (работают длинные пути) и ограничен
  по времени — повреждённый файл иначе чинится минутами;
- summarize: экстрактивное реферирование по TF-IDF, посчитанному на самой
  библиотеке, с лёгким стеммером для русского и английского;
- db: SQLite с полнотекстовым индексом FTS5, морфологический поиск с BM25;
- scanner: инкрементальный многопоточный обход, счётчик попыток защищает
  от файла, обрывающего разбор;
- report: автономный HTML с поиском, Markdown, JSON, CSV.

Интерфейс:
- окно PySide6 в тёмной теме: вкладки разбора и поиска, фоновый поток,
  карточка документа, правка своих ключевых слов;
- командная строка: build, scan, analyze, report, search, show, tag, stats;
- у каждой папки свой каталог, последняя обработанная запоминается.

Сборка: scripts/build_exe.py даёт Catalogizer.exe в Windows и
Catalogizer.app в macOS, иконки .ico и .icns рисуются кодом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 19:20:29 +03:00
commit 49f91247c7
30 changed files with 3934 additions and 0 deletions

58
catalogizer/app.py Normal file
View File

@@ -0,0 +1,58 @@
"""@file app.py
@brief Точка входа графического приложения каталогизатора.
Собирает QApplication и главное окно. С ключом ``--check`` окно строится,
но не показывается: так проверяется собранный exe, где traceback показать
некому.
"""
from __future__ import annotations
import sys
from pathlib import Path
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication
from .ui.main_window import MainWindow
def _set_windows_app_id() -> None:
"""@brief Закрепляет собственную группу приложения на панели задач Windows."""
if sys.platform != "win32":
return
try:
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
"SET.Catalogizer")
except (AttributeError, OSError):
pass
def main(argv: list[str] | None = None) -> int:
"""@brief Запускает окно каталогизатора.
@param argv Аргументы без имени программы: путь к папке и/или ``--check``.
@return Код выхода приложения.
"""
argv = list(sys.argv[1:] if argv is None else argv)
check = "--check" in argv
folders = [a for a in argv if not a.startswith("-")]
_set_windows_app_id()
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
application = QApplication(sys.argv[:1])
application.setApplicationName("Каталогизатор")
application.setOrganizationName("SET")
window = MainWindow(Path(folders[0]) if folders else None)
if check:
# Самопроверка сборки: окно строится целиком и приложение выходит.
application.processEvents()
titles = [window.tabs.tabText(i) for i in range(window.tabs.count())]
print("Каталогизатор: вкладок", len(titles), "-", ", ".join(titles))
return 0
window.show()
return application.exec()