# Правки ролика

> Как править ролик через Slicer API — текст ролика с id слов, каталог операций, элементы по времени, ревизии, субтитры из речи, ИИ-помощник и перевод.

Источник: https://docs.slicer.media/api/editing/

Готовый ролик или черновик [проекта без ИИ-режиссёра](https://docs.slicer.media/api/montage/#manual) можно править так же, как в редакторе приложения: вырезать слова и паузы, разделить, подрезать, переставить и дублировать фрагменты, поставить стоп-кадр, исправить субтитры, добавить хук, плашки, титры, эмодзи, наезды и маркеры, поменять скорость (и кривую скорости), звук, переходы, эффекты и цвет, кадр, маску, хромакей и ключевые кадры, поставить музыку и голос, свои фото и видео поверх ролика — во весь кадр или окном, поменять обложку и формат кадра. Правки бесплатны и сразу видны в редакторе; файл ролика обновляет [экспорт](https://docs.slicer.media/api/export/). Платные — только ИИ-функции Slicer: субтитры из речи, ИИ-помощник и перевод субтитров, все по себестоимости. Смонтировать ролик целиком ваш агент может и сам, бесплатно: он пишет план, а Slicer применяет к нему правила своего ИИ-режиссёра — Свой режиссёр.

## Как устроены правки

### 1. Получите текст ролика

`GET /v1/projects/{id}/transcript` отдаёт ролик как текст: фрагменты по порядку, в них слова с `id`, отметки вырезанных и выделенных слов, и элементы — с привязкой к словам или ко времени ролика.

Ответ (сокращённо):

```json
{
  "rev": 7,
  "durationSec": 31.4,
  "text": "…",
  "blocks": [
    {
      "id": "b1",
      "position": 0,
      "sourceIndex": 0,
      "kind": "words",
      "words": [
        { "id": 12, "text": "Сегодня", "cut": false, "accent": false },
        { "id": 13, "text": "покажу", "cut": false, "accent": false },
        { "id": 14, "text": "эээ", "cut": true, "accent": false },
        { "id": 15, "text": "монтаж", "cut": false, "accent": true }
      ]
    },
    { "id": "b2", "position": 1, "sourceIndex": 2, "kind": "range", "words": [] }
  ],
  "elements": {
    "hook": "Монтаж за 3 минуты",
    "plaques": [],
    "cards": [],
    "emoji": [{ "id": "e1", "emoji": "🚀", "wordId": 15, "start": 1.2, "end": 2.4 }],
    "zooms": [{ "id": "z1", "fromWordId": 12, "toWordId": 15, "start": 0, "end": 2.4 }],
    "titles": [
      {
        "id": "t1", "text": "Шаг 1", "font": "punch", "size": 96, "color": "#FFFFFF", "background": "pill",
        "align": "center", "x": 540, "y": 420, "fromWordId": null, "toWordId": null,
        "start": 8, "end": 11, "anchor": { "blockId": "b2", "at": 3.5, "duration": 3 }
      }
    ],
    "markers": [{ "id": "m1", "time": 12.5, "label": "Сюда музыку", "color": "yellow" }]
  }
}
```

-   `kind: "words"` — фрагмент речи со словами; `kind: "range"` — фрагмент по времени исходника: видеоряд, фото или ролик без распознанной речи. Слов в нём может не быть.
-   `start` и `end` у элементов — где элемент стоит в ролике, в секундах. Нет `start` — элемент сейчас не виден (его слова вырезаны).
-   Элемент, поставленный по времени, а не на слова, приходит с `fromWordId: null` и привязкой `anchor` — фрагмент, секунда его исходника и длительность (Элементы по времени).
-   `markers` — пометки автора на таймлайне, в ролик они не попадают.

Поле `text` — тот же ролик одной строкой в форме, удобной для языковой модели: его можно передать своему ИИ-агенту, чтобы он предложил правки. Фрагменты без речи показаны в нём со временем в ролике.

### 2. Отправьте операции

`POST /v1/projects/{id}/edits` с ревизией `rev` из шага 1 и списком операций (до 80 за раз):

Тело запроса:

```json
{
  "rev": 7,
  "operations": [
    { "op": "cut_fillers" },
    { "op": "set_hook", "text": "Монтаж за 3 минуты" },
    { "op": "set_accent", "wordId": 18 },
    { "op": "set_emoji", "wordId": 18, "emoji": "🔥" },
    { "op": "add_plaque", "fromWordId": 20, "toWordId": 26, "kind": "list", "text": "Три шага", "caption": "Загрузить\nВыбрать стиль\nЭкспорт" },
    { "op": "add_title", "start": 8, "duration": 3, "text": "Шаг 1", "titleBackground": "pill", "titleIn": "pop" },
    { "op": "tune_block", "id": "b3", "transition": { "kind": "dissolve", "duration": 0.4 }, "animIn": { "kind": "zoomIn", "duration": 0.5 } }
  ]
}
```

Ответ:

```json
{ "ok": true, "data": { "rev": 8, "applied": 7, "skipped": [], "durationSec": 29.8 } }
```

### 3. Экспортируйте, когда правки закончены

Правки сохраняются сразу. Чтобы получить обновлённый файл, запустите [экспорт](https://docs.slicer.media/api/export/) — он бесплатный.

## Операции

Каталог операций один на весь Slicer: те же операции применяют ручные правки в редакторе, ИИ-помощник, режиссёр в «Чате», API и MCP-инструмент `edit_project`. Поэтому результат совпадает с тем, что вы получили бы в редакторе.

Слова адресуются `id` из текста ролика, фрагменты и элементы — своими `id`. Диапазон слов — `fromWordId`…`toWordId` включительно, позиция фрагмента — с 0.

### Речь и фрагменты

| Операция | Поля | Что делает |
| --- | --- | --- |
| `cut_words` | `fromWordId`, `toWordId` | вырезать слова из ролика |
| `restore_words` | `fromWordId`, `toWordId` | вернуть вырезанные или неиспользованные слова исходника |
| `cut_fillers` | —   | вырезать слова-паразиты |
| `cut_silences` | `minPause`, `blockIds` | убрать паузы по тишине, без распознавания речи и бесплатно: режет фрагменты по времени (`kind: "range"`) по паузам длиннее `minPause` — `0.3`, `0.5` (по умолчанию) или `1` секунда; без `blockIds` — во всех таких фрагментах |
| `add_source` | `sourceIndex`, `position`, `srcStart`, `srcEnd` | поставить исходник проекта в ролик новым фрагментом: без `srcStart`/`srcEnd` — его речь по словам или весь исходник; без `position` — в конец |
| `remove_block` | `id` | удалить фрагмент вместе с элементами, поставленными на него по времени |
| `move_block` | `id`, `position` | переставить фрагмент |
| `slip_block` | `id`, `delta` | показать в фрагменте видеоряда другой кусок того же исходника: `delta` в секундах, `+` — более поздние кадры; длина и место фрагмента не меняются |
| `split_block` | `id`, `at` | разделить фрагмент на два в момент ролика `at` (с) внутри него; обе части сохраняют его настройки |
| `trim_block` | `id`, `edge`, `delta` | подрезать или удлинить фрагмент: край `start` или `end`, `delta` — сдвиг края в секундах ролика (`+` — позже, `−` — раньше), −60…60. `start` `+1` срезает первую секунду, `end` `+1` добавляет секунду исходника |
| `duplicate_block` | `id` | копия фрагмента сразу после него |
| `freeze_frame` | `id`, `at`, `duration` | стоп-кадр: новый фрагмент без звука с остановленным кадром. `at` — момент ролика внутри фрагмента (по умолчанию — его последний кадр), `duration` — 0,3–10 с (по умолчанию 2) |
| `set_hold` | `id`, `duration` | длина стоп-кадра, 0,3–10 с |
| `replace_source` | `id`, `sourceIndex`, `srcStart` | показать в фрагменте другой исходник с секунды `srcStart` (по умолчанию 0): у видеоряда картинка меняется на месте, длина та же; у фрагмента с речью речь остаётся, а новая картинка ложится поверх вставкой |

### Настройки фрагментов и всего ролика

| Операция | Поля | Что делает |
| --- | --- | --- |
| `tune_block` | `id`, `speed`, `volume`, `fadeIn`, `fadeOut`, `effect`, `transition`, `filter`, `adjust`, `lut`, `animIn`, `animOut`, `background` | скорость, громкость, нарастание и затухание звука, эффект, переход в этот фрагмент, цвет — фильтр, «Настроить» и LUT, анимации появления и исчезновения, фон за вписанным кадром или маской — `blur` (размытый кадр) или цвет `#RRGGBB`; `null` сбрасывает поле |
| `set_process` | `id`, `process` | бесплатная обработка фрагмента — список из `reverse` (обратная перемотка), `stabilize` (стабилизация), `denoise` (шумоподавление), `enhance` (улучшение звука); заменяет эти четыре, пустой список — без обработки. Результат сервер готовит в фоне, обычно за минуту |
| `apply_keyframe_preset` | `id`, `preset` | плавное движение камеры на весь фрагмент ключевыми кадрами: `zoomIn` — наезд, `zoomOut` — отъезд, `panLeft`, `panRight`, `panUp`, `panDown` — панорама; заменяет ключевые кадры фрагмента |
| `apply_look` | `id`, `blockIds`, `parts` | перенести оформление фрагмента `id` на другие: без `blockIds` — на все остальные фрагменты; `parts` — что переносить: `speed`, `audio`, `color`, `effect`, `transition`, `animations`, `mask`, `background`, `framing` (по умолчанию всё, кроме `framing`) |
| `set_all_transitions` | `transition` | один переход на всех стыках (`null` — убрать все) |
| `set_all_effects` | `effect` | один эффект на всех фрагментах (`null` — убрать все) |
| `set_all_color` | `filter`, `adjust`, `lut` | один цвет на всех фрагментах: фильтр, «Настроить» и LUT; чего нет в запросе — снимается (`filter: null` без остального — убрать весь цвет) |
| `set_format` | `format` | формат кадра: `9:16`, `4:5`, `1:1`, `16:9` |
| `set_cover` | `start`, `coverTitle`, `coverStyle`, `coverIntro` | обложка: кадр (момент ролика, с), заголовок на ней до 60 знаков (`null` — без заголовка), вид заголовка `hook` или `plaque`, показывать ли обложку первым кадром |

### Кадр, маска, скорость и звук по времени

С версии API 1.5.0.

| Операция | Поля | Что делает |
| --- | --- | --- |
| `set_framing` | `id`, `mode`, `cx`, `cy`, `zoom` | кадр фрагмента: `auto` — автоматический, `crop` — заполнить кадр (центр окна `cx`, `cy` — доли исходника 0–1), `fit` — вписать целиком с фоном, `track` — кадр следит за лицом; `zoom` — приближение 1–3. `track` работает, только если лица в этом куске уже найдены — ИИ-монтажом или «Следить за лицом» в редакторе; иначе операция пропускается |
| `set_keyframe` | `id`, `t`, `x`, `y`, `scale`, `rotate`, `opacity`, `ease` | ключевой кадр через `t` секунд от начала фрагмента: сдвиг `x`, `y` (доли кадра, −1…1), масштаб 0,2–4, поворот по часовой стрелке −180…180°, непрозрачность 0–1 (меньше 1 — уход в чёрное), сглаживание к следующему кадру `smooth` (по умолчанию), `linear`, `in` (разгон), `out` (торможение). Ключевой кадр в этот момент меняется; чего нет в запросе — берётся из вида кадра в момент `t`. До 24 на фрагмент; для движения нужны два кадра |
| `remove_keyframe` | `id`, `t` | убрать ключевой кадр в момент `t` |
| `set_mask` | `id`, `shape`, `size`, `feather`, `cx`, `cy`, `rotate`, `invert`, `maskKeys` | маска: форма `circle`, `rounded`, `heart`, `stripe` (`none` — убрать), размер 0,2–1, мягкость 0–1, сдвиг центра `cx`, `cy` — −0,5…0,5 доли кадра от центра, поворот в градусах, `invert` — видео снаружи формы, фон внутри; `maskKeys` — анимация `[{ "t", "cx", "cy", "size", "rotate" }]`, до 24 точек (`null` — без анимации). Чего нет в запросе — остаётся как было |
| `set_chromakey` | `id`, `keyColor`, `similarity`, `blend`, `spill`, `keyBackground`, `keyImage` | хромакей, бесплатно: цвет фона съёмки `#rrggbb` (`null` — выключить), сходство 0,05–0,5 (по умолчанию 0,15), мягкость края 0–0,3 (0,1), `spill` — убрать цветную кайму (по умолчанию `true`); позади — цвет `keyBackground` или фото проекта `keyImage` (id загрузки картинки, `null` — цвет) |
| `set_speed_curve` | `id`, `preset` или `speedPoints` | кривая скорости фрагмента видеоряда без речи: готовая — `montage`, `hero`, `bullet`, `flash`, `rampUp`, `rampDown` — или своя `[{ "t", "speed" }]`: 2–8 точек, `t` — доля фрагмента 0–1, скорость 0,1–8, между точками — линейно. Звук фрагмента на кривой выключается; `preset: null` — обычная скорость |
| `set_smooth` | `id`, `on` | сверхзамедление фрагмента видеоряда медленнее 1× (кусок исходника до 10 с): сервер дорисовывает промежуточные кадры в фоне, бесплатно |
| `set_volume_keys` | `id`, `volumeKeys` | громкость фрагмента по точкам `[{ "t", "v" }]`: `t` — секунды от начала фрагмента, `v` — 0–1, между точками — линейно, до 24 точек; заменяет постоянную громкость, `null` — снова постоянная |

Спидрамп со сверхзамедлением и плавный поворот кадра:

```json
[
  { "op": "set_speed_curve", "id": "b4", "preset": "bullet" },
  { "op": "set_smooth", "id": "b4", "on": true },
  { "op": "set_keyframe", "id": "b5", "t": 0, "rotate": 0 },
  { "op": "set_keyframe", "id": "b5", "t": 2.5, "rotate": 12, "scale": 1.2 }
]
```

### Субтитры

| Операция | Поля | Что делает |
| --- | --- | --- |
| `set_text` | `wordId`, `text` | исправить слово в субтитрах (до 200 знаков) |
| `set_accent`, `clear_accent` | `wordId` | выделить слово акцентным цветом или снять выделение |
| `set_caption_position` | `placement` | положение субтитров: `top`, `center`, `bottom` — как ручной сдвиг в редакторе: все фразы встают на эту высоту, и умное «Авто» их не переставляет (стиль) |
| `set_phrase_text` | `fromWordId`, `toWordId`, `text` | переписать текст фразы в субтитрах целиком — опечатки, имена, числа; речь не меняется |
| `reset_phrase_text` | `fromWordId`, `toWordId` | текст этих слов в субтитрах — снова как в распознанной речи |

### Элементы

Элементы ставятся на слова (`fromWordId`, `toWordId` или `wordId`) или — где речи нет — по времени ролика (`start`, `duration`).

| Операция | Поля | Что делает |
| --- | --- | --- |
| `set_hook`, `remove_hook` | `text` (до 120 знаков) | цепляющий заголовок в первые секунды |
| `add_plaque`, `remove_plaque` | слова или время, `text`, `caption`, `kind` / `id` | плашка: заголовок до 60 знаков и строки через `\n`; вид — `title`, `number`, `list`, `quote`, `name` |
| `add_title`, `update_title`, `remove_title` | слова или время, `text` и оформление / `id` | титр — свободный текстовый слой, до 3 строк через `\n` (до 200 знаков); оформление — ниже |
| `set_emoji`, `remove_emoji` | `wordId` или `start` + `duration`, `emoji` / `id` или `wordId` | эмодзи поверх ролика; только из набора Slicer (`GET /v1/styles` → `emoji`) |
| `add_zoom`, `remove_zoom` | слова или время, `scale`, `motion` / `id` | наезд камеры: масштаб 1,05–1,6 (по умолчанию 1,3), движение `push` — плавный наезд (по умолчанию), `pull` — отъезд, `punch` — резкий скачок |
| `add_card`, `remove_card` | слова или время, `text`, `caption` / `id` | медиа-карточка: `text` — описание картинки на английском, `caption` — подпись |
| `add_marker`, `update_marker`, `remove_marker` | `wordId` или `start`, `label`, `color` / `id` | маркер — пометка автору на таймлайне до 40 знаков, в ролик не попадает; цвет — `cyan`, `yellow`, `red`, `green`, `violet` |
| `add_sound`, `update_sound`, `remove_sound` | `wordId` или `start`, `kind` / `id`, `kind` / `id` | звук в момент ролика: `whoosh`, `pop`, `ding`, `click`, `swipe`, `rise`, `boom`, `chime`; переезжает вместе с фрагментом, звучит и при выключенных звуковых эффектах; в ролике — до 60 звуков. Перенести звук — `move_element` с `element: "sound"` |
| `update_plaque` | `id`, `text`, `caption`, `kind` | изменить заголовок, строки или вид плашки на месте |
| `update_card` | `id`, `caption`, `uploadId` | медиа-карточка: подпись (`""` — без неё) и своё фото из медиа проекта вместо картинки по описанию, бесплатно (`null` — снова по описанию) |
| `update_zoom` | `id`, `scale`, `motion` | изменить масштаб и движение наезда на месте |
| `move_element` | `element`, `id`, `start`, `duration` | перенести элемент на другой момент ролика: `element` — `plaque`, `card`, `zoom`, `emoji`, `insert`, `title`, `sound` или `marker`; над речью он встанет на слова в этом месте |
| `set_element_position` | `element`, `id`, `x`, `y` | сдвинуть эмодзи, карточку или плашку (`element` — `emoji`, `card`, `plaque`) по кадру: центр в кадре 1080×1920; `null` — на место по умолчанию |

### Музыка и голос

С версии API 1.7.0. Трек и запись голоса — исходник проекта (`sourceIndex`) или файл из медиа проекта (`uploadId` — свои файлы, добавленные к проекту в редакторе). `id` записей голоса — в документе монтажа, список `voices`.

| Операция | Поля | Что делает |
| --- | --- | --- |
| `set_music` | `sourceIndex` или `uploadId`, `volume`, `sourceVolume`, `ducking`, `offset`, `fadeIn`, `fadeOut` | фоновая музыка: аудио-исходник или аудио из медиа проекта; без `sourceIndex` и `uploadId` — правка текущей музыки. Громкость музыки 0–1 (обычно 0,2–0,35), `sourceVolume` — громкость звука фрагментов под музыкой 0–1, `ducking` — музыка тише под речью, `offset` — с какой секунды трека начать, нарастание и затухание — 0–5 с |
| `remove_music` | —   | убрать фоновую музыку |
| `fit_music` | `on` | `true` — подогнать трек под длину ролика: он закончится вместе с роликом на границе такта; `false` — играть как есть |
| `fit_to_beats` | `every` | склейки фрагментов видеоряда — под биты музыки: каждая доля (`1`, по умолчанию), каждая 2-я или 4-я |
| `add_voice` | `sourceIndex` или `uploadId`, `start`, `volume` | запись голоса или аудио-исходник на дорожку голоса с секунды ролика `start`; громкость 0–2 (по умолчанию 1). Звук фрагментов и музыка под голосом приглушаются |
| `move_voice` | `id`, `start` | перенести запись на другой момент ролика |
| `trim_voice` | `id`, `edge`, `at` | подрезать запись: край `start` или `end` и его новое время в ролике `at` (с) |
| `set_voice_volume` | `id`, `volume` | громкость записи, 0–2 |
| `remove_voice` | `id` | убрать запись с дорожки |

Трек из исходников потише, под длину ролика, склейки — на каждую вторую долю:

```json
[
  { "op": "set_music", "sourceIndex": 3, "volume": 0.25, "ducking": true, "fadeOut": 1.5 },
  { "op": "fit_music", "on": true },
  { "op": "fit_to_beats", "every": 2 }
]
```

### Свои фото и видео поверх ролика

С версии API 1.7.0. Вставка закрывает картинку ролика фото или видео, пока идёт речь, — например, чтобы показать то, о чём говорит автор. Ставится на слова или по времени, как элементы; `id` вставок — в документе монтажа, список `inserts`.

| Операция | Поля | Что делает |
| --- | --- | --- |
| `add_insert` | `sourceIndex` или `uploadId`, `fromWordId` + `toWordId` или `start` + `duration`, `fit`, `srcStart`, `mode` | фото или видео из исходников или медиа проекта поверх ролика: `fit` — `cover` (на весь кадр) или `contain` (картинка целиком), `srcStart` — у видео с этой секунды, `mode` — `full` (во весь кадр, по умолчанию) или `pip` (окном, вид окна — `set_pip`) |
| `update_insert` | `id`, `fit`, `srcStart` | вписывание вставки и начало её видео |
| `remove_insert` | `id` | убрать вставку |

Фото результата окном поверх фразы:

```json
{ "op": "add_insert", "sourceIndex": 2, "fromWordId": 40, "toWordId": 52, "mode": "pip" }
```

### Картинка в картинке

С версии API 1.6.0. Вставка — своё фото или видео поверх ролика (её `id` — в документе монтажа, список `inserts`) — может закрывать весь кадр или стоять окном.

| Операция | Поля | Что делает |
| --- | --- | --- |
| `set_pip` | `id`, `mode`, `x`, `y`, `scale`, `rotate`, `opacity`, `shape`, `radius`, `borderColor`, `borderWidth`, `volume` | `mode: "pip"` (по умолчанию) — вставка окном поверх ролика, `"full"` — снова во весь кадр. Центр окна `x`, `y` — в кадре 1080×1920 (можно частично за краем), ширина `scale` — доля ширины кадра 0,1–1, поворот по часовой стрелке −180…180°, непрозрачность 0–1, форма `rect` или `circle`, скругление углов `radius` — доля меньшей стороны 0–0,5, рамка — цвет `#rrggbb` (`null` — без рамки) и толщина 0–0,1, `volume` — звук видео в окне 0–1 (по умолчанию 0). Чего нет в запросе — остаётся как было; у окна с ключевыми кадрами положение правится в первом кадре. В ролике — до 10 окон: 11-е пропускается |

Вставка круглым окном с белой рамкой справа внизу:

```json
{ "op": "set_pip", "id": "i4k2m9q", "x": 800, "y": 1500, "scale": 0.4, "shape": "circle", "borderColor": "#ffffff", "borderWidth": 0.03 }
```

Ключевые кадры окна задаются в редакторе; в документе монтажа они — `inserts[].pip.keyframes`.

### Значения

-   `speed` — от 0,25 до 4; `volume` — от 0 до 1; `fadeIn`, `fadeOut` — от 0 до 3 секунд;
-   `effect` — `shake`, `pulse`, `flash`, `cinema`, `vignette`, `grain`, `blur`, `glitch`, `vhs`;
-   `transition` — `{ "kind", "duration" }`, длительность — от 0,2 до 1,2 секунды; вид — `fade`, `flash`, `zoom`, `dissolve`, `blur`, сдвиги `slide`, `slideRight`, `slideUp`, `slideDown`, шторки `wipe`, `wipeRight`, `wipeUp`, `wipeDown`, круг `circleOpen` и `circleClose`, `zoomIn`, `pixelize`, `radial`, `scatter`;
-   `filter` — `{ "id", "strength" }`, фильтр — `bw`, `warm`, `cool`, `vintage`, `sepia`, `vivid`, `pastel`, `noir`, `cinema`, сила — от 0 до 1;
-   `adjust` — «Настроить»: `exposure`, `brightness`, `contrast`, `saturation`, `temperature`, `tint`, каждое от −1 до 1 (0,15 — заметный шаг); заменяет все ползунки фрагмента сразу;
-   `lut` — `{ "preset", "strength" }` или `{ "uploadId", "strength" }`: встроенный LUT — `teal-orange`, `film`, `matte`, `bleach`, `golden`, `moody`, `nordic`, `cross`, `mono-film`, `vibrant`; `uploadId` — свой `.cube` из «Мои LUT» (его id виден в документе монтажа у фрагмента с этим LUT); сила — от 0 до 1;
-   `animIn`, `animOut` — `{ "kind", "duration" }`, вид — `fade`, `zoomIn`, `zoomOut`, `slideLeft`, `slideRight`, `slideUp`, `slideDown`, длительность — от 0,2 до 2 секунд.

Оформление титра — поля с префиксом `title`; у `update_title` `null` сбрасывает поле:

| Поле | Значение |
| --- | --- |
| `titleFont` | шрифт из `GET /v1/styles` → `fonts` |
| `titleSize` | кегль, 24–200 px кадра 1080×1920 |
| `titleColor`, `titleBackgroundColor` | цвет текста и подложки, `#RRGGBB` |
| `titleBackground` | подложка: `none`, `box` (по строкам), `pill` (скруглённая) |
| `titleOutline`, `titleShadow` | обводка букв и тень, `true` / `false` |
| `titleAlign` | выравнивание: `left`, `center`, `right` |
| `titleX`, `titleY` | место на кадре 1080×1920: точка по горизонтали (край или центр — по выравниванию) и середина по вертикали |
| `titleIn`, `titleOut` | появление `fade`, `slide`, `pop`, `typewriter` (печать по буквам) и исчезновение `fade`, `slide`, `pop` |

Полная схема операций:

`EditOperation`

| Поле | Тип | Описание |
| --- | --- | --- |

## Элементы по времени

В ролике без речи — видеоряд, фото, черновик без распознанной речи — слов нет, поэтому плашки, титры, эмодзи, наезды, карточки и маркеры ставятся по времени: `start` — секунда ролика, `duration` — длительность в секундах ролика (0,3–60).

Титр и наезд по времени:

```json
{ "op": "add_title", "start": 4, "duration": 2.5, "text": "Утро в кофейне", "titleIn": "typewriter" }
```

```json
{ "op": "add_zoom", "start": 9, "duration": 2, "scale": 1.2, "motion": "pull" }
```

Элемент держится за фрагмент, над которым стоит, — как присоединённый клип: переставите фрагмент — элемент переедет с ним, удалите фрагмент — элемент уйдёт вместе с ним. Над речью лучше ставить элементы на слова: такая привязка переживает правку текста. Если у операции есть и слова, и время, берутся слова.

## Пропущенные операции

Операции применяются по порядку. Если операция ничего не меняет — слово не найдено, фрагмента нет, эмодзи не из набора, — она пропускается, а остальные применяются. Пропущенные перечислены в `skipped`:

Ответ с пропусками:

```json
{ "rev": 9, "applied": 2, "skipped": [{ "index": 1, "op": "remove_plaque", "reason": "element_not_found" }], "durationSec": 29.8 }
```

| `reason` | Причина |
| --- | --- |
| `word_not_found_or_nothing_to_change` | нет такого слова или правка уже применена |
| `words_or_start_required` | у элемента нет ни слов, ни `start`, таких слов нет или в этот момент ролика нет фрагмента |
| `block_not_found` | нет фрагмента с таким `id` |
| `source_not_found` | нет исходника с таким номером |
| `element_not_found` | нет элемента или фрагмента с таким `id` |
| `emoji_not_allowed_or_word_not_found` | эмодзи не из набора Slicer или нет такого слова |
| `bad_arguments` | поля операции не прошли проверку |
| `unknown_operation` | такой операции нет |
| `block_not_footage` | кривая скорости и сверхзамедление — только у фрагмента видеоряда по времени, без речи и не стоп-кадра |
| `keyframe_not_found` | в этот момент фрагмента нет ключевого кадра |
| `block_not_found_or_no_face_data` | нет фрагмента с таким `id` или для `track` в этом куске ещё не найдены лица |
| `block_or_source_not_found` | нет фрагмента с таким `id` или подходящего исходника |
| `audio_not_found` | нет такого аудио в исходниках или медиа проекта |
| `no_music_or_beats` | в ролике нет музыки или доли трека ещё не посчитаны |
| `recording_not_found` | нет такой записи голоса или аудио |
| `media_or_place_not_found` | нет такого фото или видео, или для вставки не нашлось места: таких слов нет или в этот момент ролика нет фрагмента |
| `word_not_found` | нет такого слова |
| `nothing_to_change` | операция ничего не меняет |

## Ревизии

У ролика есть номер ревизии `rev`; каждая сохранённая правка увеличивает его. Правка с устаревшей ревизией отклоняется с `409 stale_document` — значит, ролик успели изменить в редакторе или другим запросом. Перечитайте текст ролика и повторите правку с новой `rev`. Так правки из API и из приложения не затирают друг друга.

## Субтитры из речи

В [проекте без ИИ-режиссёра](https://docs.slicer.media/api/montage/#manual) речь не распознана, поэтому субтитров нет, а фрагменты в тексте ролика — по времени. `POST /v1/projects/{id}/transcribe` распознаёт речь исходников — как **Субтитры из речи** в редакторе ([Субтитры из речи](https://docs.slicer.media/guide/editor/speech/#speech)). Так же можно распознать исходники, которые добавили в проект после монтажа.

Терминал:

```bash
curl https://api.slicer.media/v1/projects/318/transcribe \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"language":"auto"}'
```

| Поле | Что задаёт |
| --- | --- |
| `sources` | номера исходников; по умолчанию — все со звуком, где речь ещё не распознана |
| `language` | язык речи: двухбуквенный код или `auto` — язык из настроек проекта, а если он не задан, Slicer определит его сам |
| `confirmCost` | цена, на которую вы согласны; без неё ответ — `Quote`, ничего не запускается |

Распознавание работает **по себестоимости** за минуту звука выбранных исходников: без `confirmCost` придёт `Quote` с `estimate` (ожидаемая цена) и `cost` (предельная), `atCost: true` — как у ИИ-помощника. Повторите запрос с `"confirmCost"`, равным `cost`: токены резервируются, спишется фактическая себестоимость, не больше `cost`. Нужна область `montage:run`.

Распознавание идёт в фоне. Ответ на запуск — задача `{ id, status, sources, createdAt }`; её ход — `GET /v1/projects/{id}/transcribe`, список задач проекта:

| `status` | Что значит |
| --- | --- |
| `processing` | речь распознаётся |
| `ready` | готово: слова — в тексте ролика, субтитры идут за речью |
| `failed` | не получилось, в `error` — понятный текст; зарезервированные токены вернулись |

Когда задача готова, перечитайте `GET /v1/projects/{id}/transcript`: у фрагментов появятся слова с `id`, на них можно ставить элементы и править субтитры (`set_text`, `set_accent`, `set_phrase_text`). Сам документ монтажа при этом не меняется. Цену заранее называет `POST /v1/estimate` с `kind: "transcribe"` и `projectId`.

| Ответ | Что значит |
| --- | --- |
| `400 nothing_to_transcribe` | в выбранных исходниках нет звука или речь уже распознана |
| `409 busy` | речь в проекте уже распознаётся — дождитесь окончания |
| `409 not_ready` | проект ещё не готов к правкам |
| `409 sources_expired` | исходники удалены по сроку хранения |
| `503 ai_unavailable` | распознавание речи временно недоступно |

## ИИ-помощник

Команда своими словами — помощник сам подберёт операции из того же каталога:

Терминал:

```bash
curl https://api.slicer.media/v1/projects/318/assistant \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command":"Сделай ролик динамичнее и добавь эмодзи на ключевых словах","rev":9}'
```

Помощник работает **по себестоимости**: команда стоит ровно столько, сколько стоили вызовы ИИ, без наценки. Без `confirmCost` придёт цена:

Ответ без confirmCost:

```json
{ "ok": true, "data": { "status": "quote", "cost": 2, "free": false, "estimate": 0.4, "atCost": true, "available": 240, "enough": true, "spendLimitLeft": 176 } }
```

-   `atCost: true` — операция по себестоимости;
-   `estimate` — ожидаемая цена в токенах, дробно;
-   `cost` — предельная цена: повторите запрос с `"confirmCost"`, равным ей. Спишется фактическая себестоимость, но не больше `cost`.

С `confirmCost` ответ — `{ rev, summary, applied, charged, costTokens, durationSec }`: `summary` — что сделано, `costTokens` — фактическая себестоимость команды (дробно), `charged` — сколько целых токенов списано с баланса сейчас. Дробный остаток копится у проекта и спишется, когда наберётся целый токен. Токены списываются, только если помощник что-то изменил. Нужна область `montage:run`.

## Перевод субтитров

`POST /v1/projects/{id}/translate` с `language` — `en`, `es`, `pt`, `de`, `fr`, `it`, `pl`, `tr`, `id`, `uk`, `ru` — и `rev`. Перевод раскладывается по словам, поэтому акценты и эмодзи остаются на месте. Перевод тоже работает по себестоимости: цена (`Quote` с `estimate` и `cost`) и подтверждение — как у помощника. В ответе — `{ rev, phrases, charged, costTokens }`; списывается, только если перевод получился.

## Автобаланс цвета

`POST /v1/projects/{id}/auto-color` выравнивает цвет без ИИ и бесплатно — по 10 кадрам фрагмента, как кнопки **Авто** и **Как у этого — ко всем** в редакторе. В теле — `rev`, `blockId` и `mode`:

-   `auto` — поправить фрагмент `blockId`: экспозиция, контраст, насыщенность и баланс белого;
-   `match` — сделать остальные фрагменты похожими на `blockId`: им ставятся его фильтр и LUT, а «Настроить» подбирается каждому своё.

Тело POST /v1/projects/318/auto-color:

```json
{ "rev": 7, "blockId": "b2", "mode": "match" }
```

Результат сразу сохраняется. В ответе — новая ревизия, значения «Настроить» по id фрагментов (`adjust`), сколько фрагментов изменено (`applied`) и длительность ролика.

## Свой режиссёр

Ваш ИИ-агент может смонтировать ролик сам — на своей стороне, для Slicer бесплатно: он смотрит кадры и пишет план, а Slicer применяет к плану правила своего ИИ-режиссёра на сервере и отвечает исправлениями и замечаниями. Сами правила агенту не передаются. Как это устроено по шагам — [Свой режиссёр](https://docs.slicer.media/mcp/own-director/) в разделе MCP: инструменты `get_director_brief`, `get_frames` и `submit_director_plan` построены на этих маршрутах.

| Маршрут | Что делает | Область |
| --- | --- | --- |
| `POST /v1/projects/{id}/director/brief` | материал — речь по id слов, паузы, дубли, пожелания (`material`), исходники (`sources`), общие советы (`guide`), схема плана (`planSchema`), схема наблюдений по кадрам (`scenesSchema`) и `rev` | `projects:write` |
| `GET /v1/projects/{id}/sources/{index}/frames` | кадры исходника: JPEG в base64, 512 px по большей стороне, до 16 за запрос | `projects:read` |
| `POST /v1/projects/{id}/director/plan` | план (`plan`) и наблюдения по кадрам (`scenes`) → ролик, новая ревизия; в ответе — исправления (`fixes`) и замечания (`issues`) | `projects:write` |

Все три бесплатны. Если в ролике есть речь, сначала распознайте её — Субтитры из речи (по себестоимости): без слов режиссёру нечего резать.

### Бриф

Тело необязательно: настройки режиссуры — `mode` (`auto`, `talking`, `story`, `product`, `aesthetic`), `pace` (`calm`, `balanced`, `dynamic`), `effects` (`off`, `light`, `bold`), `targetDurationSec` (10–180, `null` — решает режиссёр, до 90 секунд), `hook`, `plaques`, `emoji`, `cameraMotion`. Переданные поля сохраняются в проекте: по ним Slicer применяет правила к плану.

Терминал:

```bash
curl https://api.slicer.media/v1/projects/318/director/brief \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pace":"dynamic","targetDurationSec":30}'
```

| Поле ответа | Что в нём |
| --- | --- |
| `projectId`, `rev` | проект и ревизия ролика — `rev` нужна для плана |
| `settings` | настройки режиссуры проекта |
| `sources` | исходники: номер (`index`), вид (`kind`), длительность, имя файла и сколько в нём распознанных слов |
| `speechRecognized` | распознана ли речь |
| `material` | материал текстом: исходники, речь по id слов `w<ID>`, паузы, дубли `[RETAKE …]`, пожелания автора |
| `guide` | несколько общих советов — правил ИИ-режиссёра Slicer в брифе нет |
| `planSchema` | JSON-схема плана |
| `scenesSchema` | JSON-схема наблюдений по кадрам |
| `next` | что делать дальше |

### Кадры

`times` — секунды исходника через запятую (до 16), или равномерно: `from`, `to`, `count` (по умолчанию 8 кадров на весь исходник). Секунды за пределами исходника подтягиваются к его краю. Фото отдаёт одну картинку; у аудио кадров нет (`400 no_picture`), нет такого исходника — `404 source_not_found`.

Терминал:

```bash
curl "https://api.slicer.media/v1/projects/318/sources/0/frames?times=2.5,7,12.4" \
  -H "Authorization: Bearer $SLICER_TOKEN"
```

Ответ:

```json
{ "ok": true, "data": { "sourceIndex": 0, "durationSec": 48.2, "frames": [{ "time": 2.5, "mimeType": "image/jpeg", "data": "/9j/4AAQ…" }] } }
```

### План

В теле — `rev` (из брифа или из прошлого ответа), `plan` по `planSchema` и, по желанию, `scenes` — до 400 наблюдений по `scenesSchema`: `sourceIndex`, `start` и `end` в секундах исходника, `kind` (`talking_head`, `demo`, `people`, `screen`, `broll`, `unusable`), `interest` от 1 до 5 и `description`. Что значат виды и как Slicer ставит по ним видеоряд — [Наблюдения по кадрам](https://docs.slicer.media/mcp/own-director/#scenes).

Тело POST /v1/projects/318/director/plan (сокращённо):

```json
{
  "rev": 3,
  "plan": {
    "concept": "Три шага от молока до рисунка",
    "segments": [
      { "kind": "speech", "role": "hook", "fromWordId": 41, "toWordId": 47, "why": "Сильное начало" },
      { "kind": "footage", "role": "body", "sourceIndex": 0, "fromSec": 14.5, "toSec": 19, "why": "Показ рисунка" }
    ],
    "hook": "Латте-арт за минуту"
  },
  "scenes": [
    { "sourceIndex": 0, "start": 0, "end": 14.5, "kind": "talking_head", "interest": 3, "description": "Бариста рассказывает у стойки" },
    { "sourceIndex": 0, "start": 14.5, "end": 21, "kind": "demo", "interest": 5, "description": "Рисует сердце на латте" }
  ]
}
```

На сервере к плану применяются правила режиссёра Slicer: речь — по словам, видеоряд — по наблюдениям, сдержанный монтаж, длина. План сразу становится роликом — новой ревизией, даже если к нему есть замечания. Медиа-карточки по описанию не принимаются — их картинка платная; своё фото ставит операция `update_card`. Кадр фрагментов — по центру, правится операцией `set_framing`.

| Ответ | Что значит |
| --- | --- |
| `200` | `{ rev, durationSec, planSegments, fragments, fixes, issues, video }` — ролик сохранён: `fixes` — что Slicer поправил сам, `issues` — замечания, `video` — ролик текстом для сверки |
| `400 bad_plan` | план не по схеме, в `message` — первые ошибки |
| `400 empty_plan` | в плане нет фрагментов |
| `409 stale_document` | ролик изменился после брифа или прошлого плана — перечитайте и пришлите план с новой `rev` |
| `409 busy` / `409 not_ready` / `409 sources_expired` | проект сейчас рендерится, ещё не готов к правкам или его исходники удалены |

### Замечания к плану

Замечание — `{ code, segment, message }`: код, номер фрагмента плана с нуля (только если замечание о фрагменте) и что поправить — на английском, его читает модель. Поправьте план и пришлите его целиком снова с новой `rev`; когда `issues` пуст, план в порядке.

| Код | Что не так |
| --- | --- |
| `cuts_thought` | фрагмент обрывается посреди фразы |
| `starts_mid_thought` | фрагмент начинается посреди фразы |
| `single_word` | фрагмент из одного слова — сделайте его законченной мыслью или уберите |
| `two_takes` | в ролике два дубля одной мысли — оставьте лучший |
| `weak_ending` | ролик кончается на полуфразе, а не законченным предложением |
| `too_long` | ролик заметно длиннее желаемой длины, а если она не задана, — длиннее 90 секунд |
| `too_short` | ролик заметно короче желаемой длины |
| `unused_source` | исходник без речи — видео или фото — не использован в плане: покажите его там, где он к месту в истории |
| `footage_is_talking_head` | в видеоряде — говорящая голова без речи вместо действия, товара или результата (по наблюдениям) |
| `unusable_footage` | видеоряд взят из участка, отмеченного в наблюдениях как `unusable` |
| `nothing_shown` | в наблюдениях есть показ действия или видеоряд, а в плане нет ни видеоряда, ни перебивок |
| `hook_long` | хук длиннее 8 секунд |
| `hook_title_long` | заголовок хука длиннее 6 слов |

## Стиль

`PATCH /v1/projects/{id}/style` меняет шаблон, шрифт, акцентный цвет, вид субтитров, звуковые эффекты и наезд в начале — бесплатно. Правка частичная: меняются только переданные поля, остальное — в том числе другие поля `subtitles` и `params` — остаётся как было.

Тело PATCH /v1/projects/318/style:

```json
{ "templateId": "clean", "fontId": "editorial", "colorId": "mint", "subtitles": { "design": "karaoke", "size": "large" } }
```

Новый стиль виден в редакторе сразу, в файле — после экспорта. Чтобы ИИ заново собрал ролик с другими параметрами монтажа, нужна [пересборка](https://docs.slicer.media/api/montage/).

### Вид субтитров

Поля `subtitles` — те же, что в шторке **Стиль субтитров** редактора ([Субтитры](https://docs.slicer.media/guide/editor/captions/#style)). Выделение слова, появление, слов на экране, регистр и цвет по говорящим — с версии API 1.8.0.

| Поле | Значения |
| --- | --- |
| `enabled` | показывать субтитры: `true` или `false` |
| `design` | оформление: `dynamic`, `karaoke`, `classic`, `minimal`, `pop` — «Поп», `box` — «Плашка», `word` — «Одно слово» |
| `size` | размер: `small`, `medium`, `large` |
| `position` | положение: `auto` — каждая фраза там, где в кадре больше свободного места: не на лице, хуке, плашках, титрах и окнах «картинка в картинке»; `top`, `center`, `bottom` — все фразы в одном месте |
| `speakerColors` | слова каждого говорящего — своим цветом: `true` или `false` |
| `highlight` | выделение слова, которое звучит: `color` — акцентным цветом, `pop` — увеличение, соседние слова расступаются, `box` — плашка акцентного цвета, `underline` — подчёркивание, `none` — без выделения |
| `animation` | появление фразы: `fade` — плавно, `pop` — выскакивает, `slide` — снизу, `words` — слова по мере речи, `none` — сразу |
| `words` | слов на экране: `one` — по одному, `few` — 2–3, `line` — строка до шести слов, `phrase` — фраза до двух строк |
| `letterCase` | регистр: `upper` — заглавные, `normal` — как в речи |

У `highlight`, `animation`, `words` и `letterCase` есть и `auto` — значение по умолчанию: как задумано оформлением (у `letterCase` — шрифтом). Шаблоны — `bold`, `clean` и `pop`.

Слово, которое звучит, выпрыгивает — остальной стиль не меняется:

```json
{ "subtitles": { "highlight": "pop" } }
```

Как ведёт себя правка `subtitles`:

-   меняются только переданные поля, остальные остаются как были. До версии 1.8.0 частичная правка `subtitles` сбрасывала остальные поля к значениям по умолчанию;
-   новое `design` без `highlight`, `animation` и `words` возвращает их к `auto` — как выбор оформления в редакторе: оформление сразу выглядит так, как задумано. Размер, положение, регистр и цвет по говорящим остаются. Чтобы сменить оформление и сразу задать выделение, передайте оба поля:

«Караоке», в котором слово, которое звучит, увеличивается:

```json
{ "subtitles": { "design": "karaoke", "highlight": "pop" } }
```

Названия и описания значений на двух языках — в каталоге `GET /v1/styles`: оформления — `subtitleDesigns`, положения — `subtitlePositions`, выделение слова, появление, слов на экране и регистр — `subtitleOptions`:

GET /v1/styles → subtitleOptions (сокращённо):

```json
[
  {
    "id": "highlight",
    "title": { "ru": "Выделение слова", "en": "Word highlight" },
    "help": { "ru": "Как выделяется слово, которое сейчас звучит.", "en": "How the word being spoken is highlighted." },
    "values": [
      { "id": "auto", "title": { "ru": "Как в оформлении", "en": "As in the style" }, "description": { "ru": "Так, как задумано выбранным оформлением.", "en": "As the chosen style intends." } },
      { "id": "pop", "title": { "ru": "Увеличение", "en": "Pop" }, "description": { "ru": "Слово, которое звучит, увеличивается и подсвечивается, соседние слова плавно расступаются.", "en": "The spoken word grows and lights up, the others smoothly make room." } }
    ]
  }
]
```

Те же поля принимают `settings.subtitles` при создании проекта ([Настройки ролика](https://docs.slicer.media/api/montage/)) и MCP-инструмент `set_style`.

## Документ монтажа (для продвинутых)

`GET /v1/projects/{id}/document` отдаёт весь документ монтажа — полное описание ролика, из которого строятся превью и рендер, — с ревизией. `PUT` того же адреса с `{ rev, document }` заменяет его целиком. Документ проходит те же проверки, что правки из редактора; неизвестные поля отбрасываются.

> **Осторожно.** Документ — внутренняя модель ролика. Внутри v1 в нём только добавляются поля, но если вам достаточно операций, используйте их: они проще и не зависят от устройства документа.

## Пример: убрать паразиты и поставить хук

**JavaScript**

polish.mjs:

```js
// node polish.mjs 318
const API = 'https://api.slicer.media/v1';
const headers = { Authorization: `Bearer ${process.env.SLICER_TOKEN}`, 'Content-Type': 'application/json' };
const id = process.argv[2];

async function call(method, path, body) {
  const response = await fetch(`${API}${path}`, { method, headers, body: body && JSON.stringify(body) });
  const json = await response.json();
  if (!json.ok) throw Object.assign(new Error(json.error.message), { code: json.error.code });
  return json.data;
}

for (let attempt = 0; attempt < 3; attempt += 1) {
  const { rev, blocks } = await call('GET', `/projects/${id}/transcript`);
  const words = blocks.flatMap((block) => block.words).filter((word) => !word.cut);
  try {
    const result = await call('POST', `/projects/${id}/edits`, {
      rev,
      operations: [
        { op: 'cut_fillers' },
        { op: 'set_hook', text: 'Монтаж за 3 минуты' },
        { op: 'set_accent', wordId: words[0].id },
      ],
    });
    console.log(`Применено: ${result.applied}, длительность: ${result.durationSec} с`);
    break;
  } catch (error) {
    if (error.code !== 'stale_document') throw error; // ролик изменили — перечитать и повторить
  }
}
```

**Python**

polish.py:

```python
# python polish.py 318
import os
import sys

import requests

API = "https://api.slicer.media/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SLICER_TOKEN']}"
project_id = sys.argv[1]

class ApiError(Exception):
    def __init__(self, error):
        super().__init__(error["message"])
        self.code = error["code"]

def call(method, path, json=None):
    body = session.request(method, API + path, json=json, timeout=60).json()
    if not body["ok"]:
        raise ApiError(body["error"])
    return body["data"]

for attempt in range(3):
    transcript = call("GET", f"/projects/{project_id}/transcript")
    words = [w for block in transcript["blocks"] for w in block["words"] if not w["cut"]]
    try:
        result = call("POST", f"/projects/{project_id}/edits", {
            "rev": transcript["rev"],
            "operations": [
                {"op": "cut_fillers"},
                {"op": "set_hook", "text": "Монтаж за 3 минуты"},
                {"op": "set_accent", "wordId": words[0]["id"]},
            ],
        })
        print(f"Применено: {result['applied']}, длительность: {result['durationSec']} с")
        break
    except ApiError as error:
        if error.code != "stale_document":  # ролик изменили — перечитать и повторить
            raise
```

## Что дальше

[Экспорт и публикацияПолучить файл с правками и опубликовать его.](https://docs.slicer.media/api/export/)[Справочник: правкиЭндпоинты текста ролика, правок, помощника и стиля.](https://docs.slicer.media/api/reference/editing/)[ИИ-помощникКакие команды понимает помощник.](https://docs.slicer.media/guide/ai/assistant/)
