625eae7ead8da17597301a7673fcf89d1269ddc9
PLib
Менеджер видеотеки на Avalonia: сканирует папки, вытаскивает превью через ffmpeg и показывает всё сеткой карточек.
Что уже работает
- Сканирование указанных папок, инкрементальное — файл, который не изменился, не переиндексируется.
- Метаданные (длительность, разрешение, кодек) через ffprobe.
- Постеры кадром из видео через ffmpeg, с кэшем на диске.
- Виртуализированная сетка карточек, ленивая загрузка превью, поиск и сортировка.
- Светлая и тёмная темы.
- Клик или Enter по карточке — открыть в системном плеере, правая кнопка — контекстное меню.
Требования
- .NET 10 SDK
ffmpegиffprobeвPATH
Запуск
dotnet run --project src/PLib.Desktop
dotnet test
Архитектура
Четыре слоя, зависимости направлены только внутрь:
| Проект | Отвечает за | Знает о |
|---|---|---|
PLib.Domain |
Сущность VideoItem и её инварианты |
ни о чём |
PLib.Application |
Сценарии (LibraryService) и абстракции портов |
Domain |
PLib.Infrastructure |
EF Core + SQLite, ffmpeg, файловая система | Application |
PLib.Desktop |
Avalonia, ViewModel'и, composition root | Infrastructure |
Ключевые решения:
- MVVM на ReactiveUI. Свойства —
[Reactive]изReactiveUI.SourceGenerators, команды —ReactiveCommand, производные значения (IsScanning,IsEmpty) —ToProperty. Отмена сканирования сделана штатным способом: скан живёт как observable, аCancelScanCommandпросто отписывает его черезTakeUntil, что отменяетCancellationToken. - Сетка — проекция DynamicData, а не пересборка.
SourceCache→AutoRefresh→Filter→SortAndBindотдаёт диффы: добавился один файл — одна вставка в нужную позицию. Скролл, контейнерыItemsRepeaterи уже загруженные превью остаются на месте. Поиск дебаунсится на 200 мс, изменения карточек во время скана коалесцируются в 250 мс. - Сканирование — поток событий.
ILibraryService.ScanAsyncвозвращаетIAsyncEnumerable<LibraryScanEvent>: карточки появляются по мере находок, а не после завершения всего прохода. Тяжёлая часть (ffprobe + ffmpeg) идёт параллельно черезParallel.ForEachAsync, результаты собираются вChannelи применяются к сущностям по одному — трекер изменений EF не потокобезопасен. - Вся работа вне UI-потока. ViewModel оборачивает конвейер в
Task.Runи возвращает каждое событие в UI явно черезDispatcher.UIThread. - Превью живут только пока видны.
AsyncImageзапрашивает битмап при попадании в визуальное дерево и отпускает при выходе;ThumbnailCache— LRU на 256 записей с декодированием в нужную ширину. Память зависит от размера окна, а не от размера библиотеки. - Scope на операцию.
DbContextживёт ровно одну операцию — ViewModel берётIServiceScopeFactoryи создаёт scope на каждый вызов. - Кэш превью самовосстанавливается. Диск — ключ
sha256(путь|размер|mtime), память — LRU на 256 декодированных битмапов. Сканирование проверяет, что запомненный кадр физически на месте (IThumbnailGenerator.IsAvailable), и перерисовывает удалённые; после полного прохода лишние файлы вычищаются (PurgeUnusedAsync). Незавершённые.tmpудаляются только если им больше часа — иначе можно снести рендер второго запущенного экземпляра.
Данные
Всё пользовательское лежит в %LOCALAPPDATA%\PLib:
library.db— SQLite с метаданными;thumbnails/— кэш постеров (ключ = путь + размер + время изменения файла);settings.json— список папок, перечитывается на лету;logs/— Serilog, ротация по дням.
Схема создаётся через EnsureCreated. Когда форма таблицы устоится — заменить на
миграции EF Core (DatabaseInitializer — единственное место, которое надо будет тронуть).
Languages
C#
99.8%
Batchfile
0.2%