diff --git a/openspec/changes/add-video-support/.openspec.yaml b/openspec/changes/archive/2026-10-11-add-video-support/.openspec.yaml similarity index 100% rename from openspec/changes/add-video-support/.openspec.yaml rename to openspec/changes/archive/2026-10-11-add-video-support/.openspec.yaml diff --git a/openspec/changes/add-video-support/design.md b/openspec/changes/archive/2026-10-11-add-video-support/design.md similarity index 100% rename from openspec/changes/add-video-support/design.md rename to openspec/changes/archive/2026-10-11-add-video-support/design.md diff --git a/openspec/changes/add-video-support/proposal.md b/openspec/changes/archive/2026-10-11-add-video-support/proposal.md similarity index 100% rename from openspec/changes/add-video-support/proposal.md rename to openspec/changes/archive/2026-10-11-add-video-support/proposal.md diff --git a/openspec/changes/add-video-support/specs/dev-seed-data/spec.md b/openspec/changes/archive/2026-10-11-add-video-support/specs/dev-seed-data/spec.md similarity index 100% rename from openspec/changes/add-video-support/specs/dev-seed-data/spec.md rename to openspec/changes/archive/2026-10-11-add-video-support/specs/dev-seed-data/spec.md diff --git a/openspec/changes/add-video-support/specs/media-objects/spec.md b/openspec/changes/archive/2026-10-11-add-video-support/specs/media-objects/spec.md similarity index 100% rename from openspec/changes/add-video-support/specs/media-objects/spec.md rename to openspec/changes/archive/2026-10-11-add-video-support/specs/media-objects/spec.md diff --git a/openspec/changes/add-video-support/specs/photos/spec.md b/openspec/changes/archive/2026-10-11-add-video-support/specs/photos/spec.md similarity index 100% rename from openspec/changes/add-video-support/specs/photos/spec.md rename to openspec/changes/archive/2026-10-11-add-video-support/specs/photos/spec.md diff --git a/openspec/changes/add-video-support/specs/ui-workspace/spec.md b/openspec/changes/archive/2026-10-11-add-video-support/specs/ui-workspace/spec.md similarity index 100% rename from openspec/changes/add-video-support/specs/ui-workspace/spec.md rename to openspec/changes/archive/2026-10-11-add-video-support/specs/ui-workspace/spec.md diff --git a/openspec/changes/add-video-support/specs/videos/spec.md b/openspec/changes/archive/2026-10-11-add-video-support/specs/videos/spec.md similarity index 100% rename from openspec/changes/add-video-support/specs/videos/spec.md rename to openspec/changes/archive/2026-10-11-add-video-support/specs/videos/spec.md diff --git a/openspec/changes/add-video-support/tasks.md b/openspec/changes/archive/2026-10-11-add-video-support/tasks.md similarity index 100% rename from openspec/changes/add-video-support/tasks.md rename to openspec/changes/archive/2026-10-11-add-video-support/tasks.md diff --git a/openspec/specs/dev-seed-data/spec.md b/openspec/specs/dev-seed-data/spec.md index 28e6476..b077d10 100644 --- a/openspec/specs/dev-seed-data/spec.md +++ b/openspec/specs/dev-seed-data/spec.md @@ -29,7 +29,8 @@ SHALL не выводиться в поток вывода. ### Requirement: Создание альбомов и фотографий Скрипт SHALL создавать заданное параметрами число альбомов и фотографий в каждом от имени аутентифицированного владельца через `POST /api/albums` и -`POST /api/albums/:id/photos`. Названия альбомов SHALL содержать метку прогона. +`POST /api/albums/:id/photos`. Названия альбомов SHALL содержать метку прогона. При +включённом флаге видео скрипт SHALL дополнительно загружать видео-фикстуры. #### Scenario: Наполнение альбомами и фото - **WHEN** скрипт запущен с числом альбомов и фотографий на альбом @@ -39,6 +40,10 @@ SHALL не выводиться в поток вывода. - **WHEN** задан числовой seed - **THEN** структура и параметры генерируемых данных детерминированы для этого seed +#### Scenario: Флаг видео +- **WHEN** скрипт запущен с включённым флагом видео +- **THEN** помимо изображений часть альбомов получает видео-фикстуры + ### Requirement: Генерация изображений с EXIF Скрипт SHALL генерировать загружаемые изображения на лету. При доступной библиотеке Pillow он SHALL использовать JPEG с EXIF-метаданными (время съёмки, при необходимости GPS); при @@ -109,3 +114,16 @@ HTTP-клиент; Pillow SHALL быть необязательной завис #### Scenario: Значения по умолчанию - **WHEN** цель запущена без параметров - **THEN** используются документированные значения по умолчанию + +### Requirement: Наполнение видео +При включённом флаге видео скрипт SHALL загружать видео-фикстуру через общий эндпоинт +загрузки; если генерация видео недоступна, скрипт SHALL пропускать видео и продолжать без +ошибки. + +#### Scenario: Видео включено +- **WHEN** включён флаг видео +- **THEN** в часть альбомов загружаются видео-фикстуры, различимые по метке прогона + +#### Scenario: Генерация недоступна +- **WHEN** видео-инструменты недоступны, а флаг видео включён +- **THEN** скрипт пропускает видео и продолжает наполнение diff --git a/openspec/specs/media-objects/spec.md b/openspec/specs/media-objects/spec.md index 7f3d598..8cd66cd 100644 --- a/openspec/specs/media-objects/spec.md +++ b/openspec/specs/media-objects/spec.md @@ -25,7 +25,7 @@ Каждый физический объект SHALL иметь ровно одну запись в слое метаданных с ключом, MIME-типом, размером, габаритами и состоянием; фото SHALL ссылаться на объекты по -ролям original, thumb, medium и preview. +ролям original, thumb, medium, preview и playback. #### Scenario: Привязка ролей @@ -37,6 +37,11 @@ MIME-типом, размером, габаритами и состоянием; - **WHEN** варианты ещё не сгенерированы - **THEN** фото доступно с оригиналом, а thumb, medium и preview отсутствуют +#### Scenario: Привязка роли воспроизведения + +- **WHEN** видео обработано +- **THEN** медиа ссылается на объект playback наряду с оригиналом и постером + ### Requirement: Совместные объекты Несколько фото SHALL иметь возможность ссылаться на один и тот же физический объект; перенос и копирование SHALL изменять только ссылки и SHALL не изменять @@ -80,10 +85,11 @@ MIME-типом, размером, габаритами и состоянием; - **THEN** попытка повторяется до успешного удаления ### Requirement: Генерация вариантов -Генерация thumb и preview SHALL выполняться через надёжную очередь заданий с +Генерация вариантов SHALL выполняться через надёжную очередь заданий с однократным выполнением и повтором при сбое; готовый результат SHALL транслироваться подписчикам альбома. Очередь SHALL поддерживать настраиваемую -пропускную способность и SHALL обрабатывать более свежие фото раньше остальных. +пропускную способность, SHALL обрабатывать более свежие фото раньше остальных и +SHALL различать короткие задания изображений и длительные задания видео. #### Scenario: Повтор после рестарта - **WHEN** процесс перезапускается с незавершёнными заданиями @@ -100,3 +106,7 @@ MIME-типом, размером, габаритами и состоянием; #### Scenario: Настраиваемая пропускная способность - **WHEN** задан уровень параллелизма генерации - **THEN** система обрабатывает варианты с этим уровнем параллелизма + +#### Scenario: Длительное задание видео +- **WHEN** выполняется длительная подготовка видео +- **THEN** задание удерживается на весь срок выполнения и не вытесняет генерацию вариантов изображений diff --git a/openspec/specs/photos/spec.md b/openspec/specs/photos/spec.md index 0e0924f..44d6c9d 100644 --- a/openspec/specs/photos/spec.md +++ b/openspec/specs/photos/spec.md @@ -7,16 +7,25 @@ ## Requirements ### Requirement: Загрузка фото -Пользователь с правом загрузки SHALL иметь возможность загружать фото по одному и массово. +Пользователь с правом загрузки SHALL иметь возможность загружать медиа (изображения и +видео) по одному и массово; система SHALL принимать ввод `image/*` и `video/*`. #### Scenario: Успешная загрузка - **WHEN** участник с правом загрузки отправляет файл изображения - **THEN** система сохраняет оригинал и создаёт запись фото с метаданными +#### Scenario: Успешная загрузка видео +- **WHEN** участник с правом загрузки отправляет видеофайл +- **THEN** система сохраняет оригинал и создаёт медиа-запись с признаком видео + #### Scenario: Загрузка без права - **WHEN** наблюдатель пытается загрузить фото - **THEN** система отклоняет запрос +#### Scenario: Неподдерживаемый файл +- **WHEN** загружается файл, не являющийся изображением или видео +- **THEN** система отклоняет загрузку + ### Requirement: Варианты изображений Система SHALL генерировать четыре варианта: thumb (200×200, квадратный кроп), medium @@ -110,13 +119,17 @@ EXIF (дата, координаты, размеры). - **THEN** система отклоняет запрос ### Requirement: Ограничение размера загрузки -Система SHALL ограничивать размер загружаемых файлов и SHALL отвечать понятной ошибкой при -превышении лимита, а не ошибкой разбора формы. +Система SHALL ограничивать размер загружаемых файлов отдельно для изображений и видео и +SHALL отвечать понятной ошибкой при превышении лимита, а не ошибкой разбора формы. #### Scenario: Превышение лимита - **WHEN** загружаемый файл превышает максимальный размер - **THEN** система отклоняет загрузку с явной ошибкой о превышении размера +#### Scenario: Превышение лимита видео +- **WHEN** загружаемое видео превышает лимит видео +- **THEN** система отклоняет загрузку с явной ошибкой о превышении размера + #### Scenario: Файл в пределах лимита - **WHEN** файл не превышает лимит - **THEN** система принимает multipart-загрузку и обрабатывает фото @@ -208,3 +221,20 @@ EXIF (дата, координаты, размеры). #### Scenario: Повторный показ без перезагрузки - **WHEN** сетка превью перерисовывается, а URL объектов не изменились - **THEN** браузер отдаёт объекты из кэша без повторного запроса к бэкенду + +### Requirement: Медиа-запись фото +Данные фото SHALL включать вид медиа (`image` | `video`), длительность для видео и адрес +воспроизведения, когда видео-вариант готов; для изображений адрес воспроизведения +отсутствует. + +#### Scenario: Запись изображения +- **WHEN** клиент получает данные изображения +- **THEN** вид медиа — изображение, длительность и адрес воспроизведения отсутствуют + +#### Scenario: Запись видео без готового варианта +- **WHEN** клиент получает данные видео, а вариант ещё не готов +- **THEN** вид медиа — видео, длительность присутствует, адрес воспроизведения отсутствует + +#### Scenario: Запись видео с готовым вариантом +- **WHEN** видео-вариант готов +- **THEN** данные медиа содержат адрес воспроизведения diff --git a/openspec/specs/ui-workspace/spec.md b/openspec/specs/ui-workspace/spec.md index f8b100f..0d7d1e2 100644 --- a/openspec/specs/ui-workspace/spec.md +++ b/openspec/specs/ui-workspace/spec.md @@ -106,7 +106,8 @@ SHALL отображаться вне каркаса, но в общей тем ### Requirement: Загрузка фотографий из рабочего стола Загрузка SHALL запускаться действием в тулбаре; перетаскивание файлов над альбомом SHALL показывать оверлей и загружать их; пустой альбом SHALL показывать подсказку-зону загрузки. -Загрузка SHALL быть доступна только ролям с правом загрузки. +Загрузка SHALL быть доступна только ролям с правом загрузки; принимать SHALL изображения и +видео. #### Scenario: Перетаскивание - **WHEN** пользователь с правом загрузки перетаскивает файлы над альбомом @@ -116,6 +117,10 @@ SHALL отображаться вне каркаса, но в общей тем - **WHEN** пользователь открывает альбом без фото и имеет право загрузки - **THEN** в центре показана зона-подсказка загрузки +#### Scenario: Загрузка видео +- **WHEN** пользователь с правом загрузки выбирает или перетаскивает видеофайл +- **THEN** видео загружается в альбом наравне с изображениями + ### Requirement: Темы и графитовый хром Тёмная тема SHALL использовать графитовый хром Lightroom и SHALL применяться по умолчанию; нейтральная тема SHALL быть тёплым графитом; остальные темы SHALL оставаться доступными, а @@ -590,3 +595,40 @@ Ctrl/Cmd-клик — добавить или убрать, Shift-клик — - **WHEN** для фото доступен только оригинал или только миниатюра - **THEN** показывается доступный вариант, и сетка отображается корректно + +### 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** открывается режим просмотра видео diff --git a/openspec/specs/videos/spec.md b/openspec/specs/videos/spec.md new file mode 100644 index 0000000..0c6c960 --- /dev/null +++ b/openspec/specs/videos/spec.md @@ -0,0 +1,110 @@ +# videos Specification + +## Purpose +Поддержка видео в альбомах: приём файла, фоновая подготовка web-совместимого варианта, +постер-кадр и воспроизведение в браузере. + +## 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 сообщает о недоступности видео