# Формат сценария

В веб-приложении этот текст можно открыть в браузере: **`/docs/scenario-format`** (с префиксом приложения, например `https://ваш-домен/ai_video/docs/scenario-format`). Сырой Markdown: `?raw=1`, скачать файл: `?download=1`.

## Где хранятся сценарий и медиа

В **рабочем режиме** приложение хранит данные в **MySQL**:

- таблица **`scenarios`** — поля сценария (`id`, `title`, `description`, `loop`, `default_theme`, `slides` как JSON и т.д.);
- таблица **`media_resources`** — файлы (картинки, аудио), привязанные к `scenario_id` и относительному **имени файла** (например `assets/screen.png`).

Папка **`videos/<video-id>/` на диске** для работы плеера **не обязательна**: медиа отдаются по HTTP-пути **`/videos/<id>/<путь-из-json>`** (в HTML это относительный **`videos/<id>/...`** относительно `<base href="...">`, чтобы за прокси `/ai_video/` не было 404 на корневой `/videos/`).

В самом **`scenario.json`** (и при загрузке ZIP) пути к картинкам и аудио указываются **относительно id сценария** — обычно через префикс `assets/…`, например `assets/music.mp3`. Внешние URL (`http://` / `https://`) для ресурсов не преобразуются.

## Обязательные поля (валидация)

На уровне сценария:

- **`id`**, **`title`** — непустые строки;
- **`slides`** — массив, **минимум один слайд**.

Каждый слайд:

- **`id`** — строка;
- **`durationSec`** — число **> 0**;
- **`background`** — объект с полем **`type`**: одно из `solid` | `gradient` | `image`;
  - для `solid` — **`value`** как цвет (hex `rgb`/`hsl` по правилам валидатора);
  - для `gradient` — **`value`** непустая строка (CSS-градиент);
  - для `image` — **`src`** непустая строка (путь к файлу или URL).

Опционально: **`description`**, **`loop`** (boolean; по умолчанию при отсутствии в рантайме часто подставляется `true` — см. `withScenarioDefaults`), **`defaultTheme`**, **`texts`**, **`images`**, **`audio`**, **`layout`**, **`transition`**.

Для блоков **`texts[]`**: **`value`** обязателен; **`x`**, **`y`**, **`width`** если заданы — числа **0…100** (проценты).

Для **`images[]`**: **`src`** обязателен; **`x`**, **`y`**, **`width`**, **`height`** если заданы — **0…100**.

## Минимальный пример `scenario.json`

```json
{
  "id": "my-video",
  "title": "Название видео",
  "description": "Короткое описание",
  "loop": false,
  "defaultTheme": {
    "fontFamily": "Verdana, sans-serif",
    "textColor": "#FFFFFF",
    "accentColor": "#7CC6FF"
  },
  "slides": [
    {
      "id": "slide-1",
      "durationSec": 6,
      "layout": "free",
      "transition": "fade",
      "background": { "type": "solid", "value": "#0E1325" },
      "texts": [
        {
          "value": "Текст слайда",
          "x": 8,
          "y": 15,
          "width": 60,
          "fontFamily": "Verdana, sans-serif",
          "fontSize": 42,
          "weight": 700,
          "color": "#FFFFFF",
          "align": "left"
        }
      ],
      "images": [
        {
          "src": "assets/screen.svg",
          "alt": "Описание скриншота",
          "x": 45,
          "y": 12,
          "width": 50,
          "height": 70,
          "fit": "cover"
        }
      ],
      "audio": {
        "music": "assets/music.mp3",
        "voiceover": "assets/voice-1.mp3",
        "caption": "Подпись о текущем аудио"
      }
    }
  ]
}
```

## Поля (кратко)

- **`background.type`**: `solid` | `gradient` | `image`
- **`background.value`**: цвет или строка градиента для `solid` / `gradient`
- **`background.src`**: путь или URL для фона типа `image`
- Координаты **`x` / `y` / `width` / `height`**: в процентах (**0…100**)
- **`layout`**: логическое поле для рендера (`free`, `split`, `screencast`, …)
- **`audio`**: `music`, `voiceover`, `caption` — пути как в `assets/…` или абсолютные URL
- **`loop`**: повтор сценария после последнего слайда (`true` | `false`)

## Как добавить или изменить видеоинструкцию

1. **Веб-консоль** (нужны регистрация и вход по API-ключу): откройте **`/register`**, затем **`/login`**. Создание: главная → «Создать новое видео» или переход на **`/edit/<uuid>`** (uuid можно сгенерировать новый). Редактировать можно только **свои** сценарии (после первого сохранения сценарий привязывается к вашему ключу).
2. **ZIP-архив** в консоли редактирования: в архиве должен быть **`scenario.json`** (в корне или в одной вложенной папке) и файлы по путям из JSON. При загрузке **`id` в JSON заменяется** на id страницы редактирования. Служебные файлы **macOS** (`__MACOSX`, `.DS_Store`, `._*`, `.AppleDouble`) **не импортируются**.
3. **API** (с cookie-сессией после входа в браузере или с заголовком **`X-API-Key`** / **`Authorization: Bearer <ключ>`**):
   - `POST /api/scenarios` — multipart: поле `scenario` (JSON-строка) и опционально файлы `media`;
   - `POST /api/scenarios/<id>/zip` — поле `archive` (файл `.zip`).

Подробнее про API и ключи — в **`README.md`**.

## Проверка структуры (`npm run check`)

Команда **`npm run check`** использует тот же валидатор, что и сервер, и загружает сценарии **из MySQL** (нужен настроенный **`.env`** с `DB_*`). Это не проверка локальной папки `videos/` на диске.

## Прямая ссылка на одну инструкцию

Формат пути (с префиксом приложения, если оно смонтировано как **`/ai_video`**):

- **`/watch/<id>`**

Пример для `id: my-video` на сервере:

- `https://ваш-домен/ai_video/watch/my-video`

Локально без префикса:

- `http://127.0.0.1:3030/watch/my-video`

## Встраивание в iframe на другой сайт

Сервер отдаёт страницу с **`Content-Security-Policy: frame-ancestors *`** для режима встраивания.

Рекомендуемый URL:

- **`/embed/<id>`**

Альтернатива (тот же плеер, подходящий для iframe):

- **`/watch/<id>?embed=1`** (или **`?iframe=1`**)

Пример:

```html
<iframe
  src="https://ваш-домен/ai_video/embed/my-video"
  width="100%"
  height="640"
  title="Инструкция"
  allowfullscreen
  loading="lazy"
></iframe>
```

## Экспорт в MP4

Для выгрузки ролика на стриминговые платформы используется CLI (нужен **`ffmpeg`** в `PATH`):

```bash
npm run export -- --id my-video --out dist/my-video.mp4
```

Детали — в **`README.md`**, раздел «Экспорт в MP4».
