Для чего

Курсы с Udemy и Coursera, записи конференций и воркшопов часто лежат на диске обычными папками. Course Shelf сканирует такую папку и собирает из неё каталог курсов, секций и уроков. Каталог открывается в браузере и в мобильном приложении. Файлы остаются на месте: Course Shelf их не копирует и не перекодирует.

Библиотека

  • Каталог из структуры папок. Папки и имена файлов задают курсы, секции и порядок уроков. Скан привязывает к урокам субтитры и материалы, которые лежат рядом с видео.
  • course.json для точной настройки. Файл в папке курса задаёт названия, порядок уроков и метаданные поверх того, что вывел скан.
  • Метаданные из сети. Скраперы берут обложки, описания, авторов и теги с Udemy, Coursera и YouTube. Для других сайтов подходят декларативные определения скраперов и общий разбор schema.org и OpenGraph.
  • Пересканирование курса. Один курс обновляется без прогона всей библиотеки.

Просмотр

  • Стриминг без перекодирования. Видео идёт по HTTP с byte-range, перемотка срабатывает сразу.
  • Прогресс. Плеер запоминает позицию, продолжает с неё и переходит к следующему уроку. После 90% урок отмечается пройденным.
  • Главная показывает начатые уроки, свежие поступления, завершённые курсы и статистику просмотра за неделю.
  • Каталог с фильтрами по статусу, библиотеке, длительности и автору. Фильтры хранятся в адресной строке, и ссылка открывает ту же выборку.
  • Заметки и закладки. К каждому уроку можно вести заметку в Markdown и ставить закладки на нужную секунду. Заметки видит только их автор.

Транскрипты и поиск

  • Локальная расшифровка. whisper.cpp расшифровывает уроки без субтитров на том же сервере, без облачных API.
  • Поиск находит курсы и отдельные уроки одним запросом и показывает фрагмент с найденным местом. На десктопе он открывается командной палитрой.
  • Транскрипт рядом с плеером. Ссылка с ?t= открывает урок на нужной секунде.
  • Квизы. Вопросы по уроку генерирует LLM: локальный llama.cpp или модель через OpenRouter.

Мобильное приложение

  • Flutter-клиент для iOS и Android с тем же каталогом.
  • Загрузки. Уроки скачиваются с докачкой после обрыва и хранятся на устройстве в зашифрованном виде.
  • Офлайн. Позиция, заметки и закладки сохраняются на устройстве и уходят на сервер, когда появляется связь. Если на сервере прогресс новее, остаётся серверная версия.

Несколько пользователей

  • Доступ по выдаче. Роли USER и ADMIN, доступ выдаётся на библиотеку целиком или на отдельный курс.
  • Администрирование. Сводка по инстансу, управление пользователями и вход от имени пользователя, чтобы увидеть его каталог.
  • Бэкап. Одна кнопка запускает pg_dump и отдаёт ссылку на архив. Медиафайлы в бэкап не входят: Course Shelf ими не владеет.

Под капотом

Монорепо из трёх приложений: бэкенд на NestJS, веб на Nuxt и клиент на Flutter. Контракт API описан один раз, в OpenAPI 3.1 и AsyncAPI 3.0, и клиенты для TypeScript и Dart генерируются из него. Бэкенд отклоняет запросы, которых нет в спецификации, а CI падает, если код разошёлся с контрактом. События скана и фоновых задач приходят в клиенты через Centrifugo. Интерфейс переведён на русский и английский.

Общий каркас Course Shelf, Coriolis и burkmak разобран в посте «Контракт, от которого код не может уйти».

История первого импорта настоящей библиотеки — в посте «26% уроков пропали молча».

Установка

Релизы выходят Docker-образами в GHCR, стек поднимается через Docker Compose. В релизный архив входит агент-установщик для Claude Code: распакуйте архив, запустите claude в каталоге, и агент поднимет стек в диалоге.