Enhance PLib video library manager with new tabbed navigation for entities and metadata management. Introduce sections for videos, tags, actors, studios, collections, and metadata, each with dedicated search and sorting capabilities. Update UI components to reflect these changes, ensuring a cohesive user experience. Revise repository interfaces to support loading video items with labels and summaries for efficient browsing. Update README.md to document new features and usage instructions.

This commit is contained in:
Leonid Pershin
2026-08-09 08:58:07 +03:00
parent 61d01d1970
commit 5acb42d11d
26 changed files with 1434 additions and 18 deletions
+27
View File
@@ -12,6 +12,9 @@
- Анимированное превью: наведите курсор на карточку — вместо постера прокручиваются
кадры, снятые по всей длительности.
- Виртуализированная сетка карточек, ленивая загрузка превью, поиск и сортировка.
- Вкладки: видео, теги, актёры, студии, коллекции. В каждой — свой поиск и сортировка
(по названию или по частоте); клик по сущности показывает её видео в сетке.
- Поиск в сетке идёт и по меткам, так что имя актёра можно набрать прямо в строке поиска.
- Настройки — боковой панелью в том же окне (сетка сдвигается, а не перекрывается): папки
библиотеки с удалением, параметры превью и сканирования, тема. Всё пишется
в `settings.json` и подхватывается без перезапуска.
@@ -20,6 +23,8 @@
- Источники метаданных: список GraphQL-эндпойнтов (название, адрес, API-ключ) со схемой
stash-box. Поиск по отпечатку запускается кнопкой на странице видео; найденное показывается
списком, и применяется тем, что выбрали — название, описание, теги, актёры, студия.
- Вкладка «Метаданные» — тот же поиск сразу по всей библиотеке, с прогрессом, остановкой
и списком найденного. По желанию однозначные совпадения применяются на месте.
- Светлая, тёмная и системная темы; выбор запоминается.
- Встроенный плеер: клик по карточке открывает страницу медиа прямо в окне — видео,
перемотка, громкость, кнопка «назад». Полноэкранный режим по F11 или кнопке, выход —
@@ -138,10 +143,32 @@ dotnet test
Появление актёров и студий это подтвердило: два новых значения перечисления, ноль новых
таблиц. Уникальность — по нормализованному имени в паре с видом, так что «Комедия» и
«комедия» не разойдутся, а тег и студия с одним именем сосуществуют.
- **Вкладки — колонки одного макета, а не `TabControl`.** Страницы, между которыми они
переключают, соседствуют с панелью настроек и страницей плеера в одной сетке, а `TabControl`
захотел бы владеть этой компоновкой целиком. Четыре вкладки сущностей делят одну панель:
различается только вид метки, и четыре почти одинаковых разметки разошлись бы при первой же
правке.
- **Метки лежат на карточке, а не запрашиваются.** Отбор по тегу, актёру или студии — это
предикат, который DynamicData прогоняет по каждой карточке в фоновом потоке; ходить оттуда
в базу значило бы запрос на карточку. Поэтому `GetLibraryAsync` грузит видео вместе с
метками (`AsSplitQuery` — иначе каждая строка видео вернулась бы по разу на метку), а
`ApplyLabels` намеренно отделён от `Apply`: сканирование грузит видео без меток, и пустой
список там означает «не загружены», а не «их нет».
Списки сущностей пересобираются целиком, без второй цепочки DynamicData: меток сотни там,
где видео тысячи, и машинерия обошлась бы дороже, чем экономит. Количества считает база
(`LabelSummary`), а не загрузка связей ради `Count`.
- **Метаданные — только по кнопке.** Никакой фоновой синхронизации: обращение к чужому
серверу по поводу файлов пользователя происходит тогда, когда он нажал «Найти метаданные»,
и больше никогда. Уходит один отпечаток — 16 шестнадцатеричных цифр; ни имён файлов, ни
самих файлов.
Прогон по всей библиотеке — отдельная страница, а не фоновая задача: он обращается к чужим
серверам сотни раз подряд, и это должно быть там, где пользователь на это смотрит и может
остановить. Между запросами есть пауза (`RequestDelayMilliseconds`, по умолчанию 250 мс) —
тысяча запросов залпом получает от публичного инстанса не ответы, а лимит. Источник,
который упал, выбывает из прогона после первой же ошибки: отвергнутый ключ падает на каждом
видео, и тысяча одинаковых строк была бы всей страницей.
«Применять однозначные сразу» по умолчанию выключено, а два кандидата не применяются никогда
— расхождение источников это ровно тот случай, ради которого страницу и смотрят.
Источники опрашиваются по очереди и независимо: упавший попадает в список «не ответили»,
но не прячет то, что нашли остальные. GraphQL отвечает двухсотым и массивом `errors`,
поэтому он разбирается явно — иначе неверный ключ читался бы как «источник ничего не знает».