feat(video): add video upload with remux/transcode playback

Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
This commit is contained in:
iu
2026-10-11 20:37:53 +03:00
parent d059d042f0
commit d38ede0201
52 changed files with 2717 additions and 82 deletions
@@ -0,0 +1,36 @@
# Spec Delta
## MODIFIED Requirements
### Requirement: Создание альбомов и фотографий
Скрипт SHALL создавать заданное параметрами число альбомов и фотографий в каждом от имени
аутентифицированного владельца через `POST /api/albums` и
`POST /api/albums/:id/photos`. Названия альбомов SHALL содержать метку прогона. При
включённом флаге видео скрипт SHALL дополнительно загружать видео-фикстуры.
#### Scenario: Наполнение альбомами и фото
- **WHEN** скрипт запущен с числом альбомов и фотографий на альбом
- **THEN** создаются указанные альбомы и фотографии, а имена альбомов помечены меткой прогона
#### Scenario: Воспроизводимость
- **WHEN** задан числовой seed
- **THEN** структура и параметры генерируемых данных детерминированы для этого seed
#### Scenario: Флаг видео
- **WHEN** скрипт запущен с включённым флагом видео
- **THEN** помимо изображений часть альбомов получает видео-фикстуры
## ADDED Requirements
### Requirement: Наполнение видео
При включённом флаге видео скрипт SHALL загружать видео-фикстуру через общий эндпоинт
загрузки; если генерация видео недоступна, скрипт SHALL пропускать видео и продолжать без
ошибки.
#### Scenario: Видео включено
- **WHEN** включён флаг видео
- **THEN** в часть альбомов загружаются видео-фикстуры, различимые по метке прогона
#### Scenario: Генерация недоступна
- **WHEN** видео-инструменты недоступны, а флаг видео включён
- **THEN** скрипт пропускает видео и продолжает наполнение
@@ -0,0 +1,51 @@
# Spec Delta
## MODIFIED Requirements
### Requirement: Журнал объектов
Каждый физический объект SHALL иметь ровно одну запись в слое метаданных с ключом,
MIME-типом, размером, габаритами и состоянием; фото SHALL ссылаться на объекты по
ролям original, thumb, medium, preview и playback.
#### Scenario: Привязка ролей
- **WHEN** фото загружено и обработано
- **THEN** фото ссылается на объект-оригинал и, когда они готовы, на объекты thumb, medium и preview
#### Scenario: Оригинал без вариантов
- **WHEN** варианты ещё не сгенерированы
- **THEN** фото доступно с оригиналом, а thumb, medium и preview отсутствуют
#### Scenario: Привязка роли воспроизведения
- **WHEN** видео обработано
- **THEN** медиа ссылается на объект playback наряду с оригиналом и постером
### Requirement: Генерация вариантов
Генерация вариантов SHALL выполняться через надёжную очередь заданий с
однократным выполнением и повтором при сбое; готовый результат SHALL
транслироваться подписчикам альбома. Очередь SHALL поддерживать настраиваемую
пропускную способность, SHALL обрабатывать более свежие фото раньше остальных и
SHALL различать короткие задания изображений и длительные задания видео.
#### Scenario: Повтор после рестарта
- **WHEN** процесс перезапускается с незавершёнными заданиями
- **THEN** генерация вариантов продолжается
#### Scenario: Событие после генерации
- **WHEN** варианты фото готовы
- **THEN** подписчикам альбома публикуется событие обновления этого фото
#### Scenario: Приоритет свежих фото
- **WHEN** в очереди есть готовые к обработке задания разного возраста
- **THEN** система обрабатывает более свежие фото раньше
#### Scenario: Настраиваемая пропускная способность
- **WHEN** задан уровень параллелизма генерации
- **THEN** система обрабатывает варианты с этим уровнем параллелизма
#### Scenario: Длительное задание видео
- **WHEN** выполняется длительная подготовка видео
- **THEN** задание удерживается на весь срок выполнения и не вытесняет генерацию вариантов изображений
@@ -0,0 +1,58 @@
# Spec Delta
## MODIFIED Requirements
### Requirement: Загрузка фото
Пользователь с правом загрузки SHALL иметь возможность загружать медиа (изображения и
видео) по одному и массово; система SHALL принимать ввод `image/*` и `video/*`.
#### Scenario: Успешная загрузка
- **WHEN** участник с правом загрузки отправляет файл изображения
- **THEN** система сохраняет оригинал и создаёт запись фото с метаданными
#### Scenario: Успешная загрузка видео
- **WHEN** участник с правом загрузки отправляет видеофайл
- **THEN** система сохраняет оригинал и создаёт медиа-запись с признаком видео
#### Scenario: Загрузка без права
- **WHEN** наблюдатель пытается загрузить фото
- **THEN** система отклоняет запрос
#### Scenario: Неподдерживаемый файл
- **WHEN** загружается файл, не являющийся изображением или видео
- **THEN** система отклоняет загрузку
### Requirement: Ограничение размера загрузки
Система SHALL ограничивать размер загружаемых файлов отдельно для изображений и видео и
SHALL отвечать понятной ошибкой при превышении лимита, а не ошибкой разбора формы.
#### Scenario: Превышение лимита
- **WHEN** загружаемый файл превышает максимальный размер
- **THEN** система отклоняет загрузку с явной ошибкой о превышении размера
#### Scenario: Превышение лимита видео
- **WHEN** загружаемое видео превышает лимит видео
- **THEN** система отклоняет загрузку с явной ошибкой о превышении размера
#### Scenario: Файл в пределах лимита
- **WHEN** файл не превышает лимит
- **THEN** система принимает multipart-загрузку и обрабатывает фото
## ADDED Requirements
### Requirement: Медиа-запись фото
Данные фото SHALL включать вид медиа (`image` | `video`), длительность для видео и адрес
воспроизведения, когда видео-вариант готов; для изображений адрес воспроизведения
отсутствует.
#### Scenario: Запись изображения
- **WHEN** клиент получает данные изображения
- **THEN** вид медиа — изображение, длительность и адрес воспроизведения отсутствуют
#### Scenario: Запись видео без готового варианта
- **WHEN** клиент получает данные видео, а вариант ещё не готов
- **THEN** вид медиа — видео, длительность присутствует, адрес воспроизведения отсутствует
#### Scenario: Запись видео с готовым вариантом
- **WHEN** видео-вариант готов
- **THEN** данные медиа содержат адрес воспроизведения
@@ -0,0 +1,60 @@
# Spec Delta
## MODIFIED Requirements
### Requirement: Загрузка фотографий из рабочего стола
Загрузка SHALL запускаться действием в тулбаре; перетаскивание файлов над альбомом SHALL
показывать оверлей и загружать их; пустой альбом SHALL показывать подсказку-зону загрузки.
Загрузка SHALL быть доступна только ролям с правом загрузки; принимать SHALL изображения и
видео.
#### Scenario: Перетаскивание
- **WHEN** пользователь с правом загрузки перетаскивает файлы над альбомом
- **THEN** показывается оверлей загрузки, и файлы загружаются
#### Scenario: Пустой альбом
- **WHEN** пользователь открывает альбом без фото и имеет право загрузки
- **THEN** в центре показана зона-подсказка загрузки
#### Scenario: Загрузка видео
- **WHEN** пользователь с правом загрузки выбирает или перетаскивает видеофайл
- **THEN** видео загружается в альбом наравне с изображениями
## ADDED Requirements
### Requirement: Воспроизведение видео в просмотре
Видео в режиме просмотра SHALL воспроизводиться встроенным плеером с нативными контролами и
постером до готовности варианта; зум, панорамирование и режим панорамы SHALL к видео не
применяться, а действия скачивания, переноса, копирования и удаления SHALL оставаться доступными.
#### Scenario: Плеер
- **WHEN** пользователь открывает видео в режиме просмотра
- **THEN** показывается встроенный плеер, а до готовности варианта — постер
#### Scenario: Перемотка
- **WHEN** пользователь перематывает воспроизведение
- **THEN** плеер показывает запрошенный момент видео
#### Scenario: Готовность на лету
- **WHEN** вариант видео становится готов, пока просмотр открыт
- **THEN** плеер становится доступен без перезагрузки страницы
#### Scenario: Действия над видео
- **WHEN** открыто видео в режиме просмотра
- **THEN** доступны скачивание оригинала, перенос, копирование и удаление по правам
### Requirement: Плитка видео в сетке
Плитка видео в сетке SHALL показывать постер, признак видео и длительность; открытие плитки
SHALL открывать режим просмотра.
#### Scenario: Постер и признак
- **WHEN** видео показано в сетке
- **THEN** плитка показывает постер, значок воспроизведения и длительность
#### Scenario: Постер не готов
- **WHEN** постер ещё не готов
- **THEN** плитка показывает плейсхолдер и сетка отображается корректно
#### Scenario: Открытие из сетки
- **WHEN** пользователь открывает плитку видео
- **THEN** открывается режим просмотра видео
@@ -0,0 +1,111 @@
# Spec Delta
## Purpose
Поддержка видео в альбомах: приём файла, фоновая подготовка web-совместимого варианта,
постер-кадр и воспроизведение в браузере.
## ADDED Requirements
### Requirement: Приём видео
Участник с правом загрузки SHALL загружать видео в альбом; система SHALL принимать ввод
`video/*` и SHALL отклонять файлы сверх лимита видео понятной ошибкой о превышении размера.
#### Scenario: Успешная загрузка видео
- **WHEN** участник с правом загрузки отправляет видеофайл в пределах лимита
- **THEN** система сохраняет оригинал и создаёт медиа-запись с признаком видео
#### Scenario: Превышение лимита видео
- **WHEN** загружается файл больше лимита видео
- **THEN** система отклоняет загрузку с явной ошибкой о превышении размера
#### Scenario: Не медиафайл
- **WHEN** загружается файл, не являющийся изображением или видео
- **THEN** система отклоняет загрузку
### Requirement: Метаданные видео
При обработке система SHALL извлекать длительность, размеры кадра и поворот и SHALL
возвращать длительность в данных медиа.
#### Scenario: Длительность доступна
- **WHEN** видео успешно обработано
- **THEN** данные медиа содержат длительность видео
#### Scenario: Метаданные недоступны
- **WHEN** из видео не удаётся извлечь метаданные
- **THEN** запись сохраняется, а длительность пуста
### Requirement: Постер видео
Система SHALL извлекать кадр-постер и SHALL генерировать из него варианты thumb, medium и
preview тем же способом, что для изображений, чтобы сетка и обложка отображали видео постером.
#### Scenario: Генерация постера
- **WHEN** видео обработано
- **THEN** система создаёт варианты thumb, medium и preview из кадра-постера
#### Scenario: Обложка из видео
- **WHEN** видео становится обложкой альбома
- **THEN** альбом отображает постер видео
### Requirement: Гарантированное воспроизведение
Система SHALL готовить воспроизводимый вариант видео: SHALL выполнять remux без
перекодирования, когда источник web-совместим, и SHALL транскодировать в h264/yuv420p/aac
с faststart иначе; система SHALL NOT увеличивать разрешение и SHALL ограничивать длинную
сторону 1080p.
#### Scenario: Совместимый источник
- **WHEN** источник — h264/yuv420p с совместимым профилем, звуком aac или без звука, в подходящем контейнере и в пределах разрешения
- **THEN** система подготавливает вариант remux без перекодирования
#### Scenario: Несовместимый источник
- **WHEN** источник не является web-совместимым (например, hevc, 10-bit или yuv444)
- **THEN** система транскодирует видео в h264/yuv420p/aac
#### Scenario: Разрешение выше предела
- **WHEN** длинная сторона источника превышает 1080p
- **THEN** система масштабирует видео до 1080p, не увеличивая меньший источник
### Requirement: Готовность и воспроизведение видео
Данные медиа SHALL содержать адрес воспроизводимого варианта, когда он готов; клиент SHALL
воспроизводить его с перемоткой; до готовности система SHALL отдавать постер и признак
обработки, а обновление о готовности SHALL транслироваться подписчикам.
#### Scenario: Вариант не готов
- **WHEN** видео ещё обрабатывается
- **THEN** данные медиа содержат постер и не содержат адрес воспроизведения
#### Scenario: Вариант готов
- **WHEN** обработка завершена
- **THEN** данные медиа содержат адрес воспроизведения, а подписчики получают обновление
#### Scenario: Перемотка
- **WHEN** клиент перематывает воспроизведение
- **THEN** система отдаёт запрошенный диапазон байт
### Requirement: Фоновая подготовка видео
Задание подготовки видео SHALL выполняться в фоне с длительным лизингом, таймаутом и
повторами при сбое и SHALL NOT вытеснять генерацию вариантов изображений.
#### Scenario: Повтор при сбое
- **WHEN** подготовка видео завершается ошибкой
- **THEN** задание повторяется с задержкой до достижения предела попыток
#### Scenario: Изоляция от картинок
- **WHEN** длительное видео обрабатывается
- **THEN** генерация вариантов изображений продолжает выполняться
### Requirement: Оригинал видео
Система SHALL сохранять исходный видеофайл и SHALL использовать его для скачивания
независимо от подготовленного варианта.
#### Scenario: Скачивание оригинала
- **WHEN** участник скачивает видео
- **THEN** система возвращает адрес исходного файла
### Requirement: Отсутствие видео-инструментов
При отсутствии `ffmpeg`/`ffprobe` система SHALL отключать приём видео и SHALL отражать
недоступность видео-обработки в состоянии здоровья.
#### Scenario: Инструменты отсутствуют
- **WHEN** видео-инструменты недоступны
- **THEN** приём видео отклоняется, а health сообщает о недоступности видео