Skip to content

Эпизоды через API: создание, meta и поиск

Внешняя система может записать в эпизод результат своей обработки в поле meta. Например, система контроля доступа связывает распознанный автомобиль с заявкой и сохраняет решение об открытии ворот. Пример ниже записывает номер заявки REQ-2026-001, затем находит связанные с ней эпизоды.

Перед началом

Для создания и изменения нужны права записи management API централа Optic. Задайте адрес и учётные данные, затем имя существующего стрима. Роль viewer и токен воспроизведения позволяют только читать. Для примеров нужны Bash, curl и jq.

export CENTRAL_URL='https://central.example.com'
export API_LOGIN='admin'
export API_PASSWORD='your-password'
export STREAM='gate-entrance'

Включите хранение эпизодов в настройках DVR стрима или его темплейта. Для положительного суммарного срока central сохраняет эпизоды; видеозапись при этом не обязательна. Следующий пример создаёт новый эпизод. Для уже существующего возьмите его ID из списка и переходите к записи meta.

Создать эпизод

Внешняя система обращается сразу к POST /central/api-v4/episodes: знать стример, на котором работает стрим, не требуется. Создадим закрытый эпизод типа custom с исходным типом access_control.

В примере opened_at и closed_at равны текущему времени в Unix-миллисекундах: это мгновенное событие. Для реальных событий передавайте их время на шкале видео; оно может отличаться от времени запуска команды. Для продолжающегося эпизода не передавайте closed_at.

OPENED_AT=$(( $(date +%s) * 1000 ))
episode=$(jq -n --arg media "$STREAM" --argjson opened_at "$OPENED_AT" '
  {
    media: $media,
    opened_at: $opened_at,
    closed_at: $opened_at,
    details: {type: "custom", original_type: "access_control"}
  }
')
created=$(curl --fail-with-body --silent --show-error \
  --user "$API_LOGIN:$API_PASSWORD" \
  --header 'Content-Type: application/json' \
  --data "$episode" \
  "$CENTRAL_URL/central/api-v4/episodes")
EPISODE_ID=$(printf '%s\n' "$created" | jq -er '.episode_id')
export EPISODE_ID
printf '%s\n' "$created" | jq

Первый запрос возвращает 201 и {"episode_id":"…"}. Команды сохраняют полученный ID в EPISODE_ID; он используется далее в PATCH и GET. Эпизод сразу доступен в истории и на вкладке Эпизоды стрима в Optic.

Повтор с теми же media, opened_at и details.type возвращает 200 и прежний ID без изменения эпизода. Для custom в ключ входит буквальный тип custom, а не original_type. Повторяйте тот же JSON при повторной отправке: вычисление нового OPENED_AT означает новое событие. POST не редактирует существующий эпизод; дополнительные данные меняются через meta.

Ошибки создания:

  • 404 — неизвестный стрим.
  • 409 — хранение эпизодов отключено.
  • 422 — некорректный документ; ответ содержит пути полей.
  • 413 — полное тело POST превышает 32 МиБ.

В payload можно передать исходные данные события. Для последующих дополнений внешних систем используйте отдельное поле meta.

Сохранить данные

Отправьте JSON Merge Patch в PATCH /central/api-v4/episodes/{episode_id}/meta. В примере все данные интеграции находятся под ключом access_control; другие сервисы могут использовать собственные ключи.

curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
  --request PATCH \
  --header 'Content-Type: application/merge-patch+json' \
  --data '{"access_control":{"request_id":"REQ-2026-001","decision":"allow"}}' \
  "$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID/meta"

Ответ 200 содержит итоговое значение meta. Оно допускает произвольный JSON. Объекты сливаются рекурсивно; массивы и скаляры заменяются целиком. Повторная запись только decision сохраняет request_id. При параллельных изменениях разные ключи сохраняются вместе; для одного ключа побеждает последняя зафиксированная запись.

Прочитать данные

Обычный GET эпизода возвращает meta; команда ниже выводит только это поле:

curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
  "$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID" | jq '.meta'

Поле также приходит в списке эпизодов и в playback API централа при чтении с токеном стрима. Пока meta не задано, ключ отсутствует в JSON эпизода. Вкладка Эпизоды в Optic показывает события и стандартные фильтры; произвольные поля meta читаются через API.

Изменить или удалить данные

Для изменения отправьте PATCH с новыми значениями. Для удаления одного ключа задайте ему null. Этот запрос удаляет решение, сохраняя номер заявки:

curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
  --request PATCH \
  --header 'Content-Type: application/merge-patch+json' \
  --data '{"access_control":{"decision":null}}' \
  "$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID/meta"

Чтобы снять всё meta, отправьте в том же запросе --data 'null'. Ответ будет JSON null, а в следующих чтениях эпизода ключ meta снова будет отсутствовать. Пустой объект {} считается заданным значением.

Найти эпизоды по своим данным

История централа поддерживает серверный фильтр meta: передайте JSON-структуру в query string запроса GET /central/api-v4/episodes. Она должна содержаться в сохранённом meta; лишние поля допустимы. Все заданные ключи должны совпасть с сохранением вложенности. Фильтрация выполняется до ограничения страницы и сочетается со штатными фильтрами через AND. Этот же фильтр доступен в центральном playback API. В текущем фиде стримера и в форме фильтров консоли Optic поиска по meta нет.

Для примера выберите стрим и период события, а также номер сохранённой заявки. from и to задаются в Unix-миллисекундах, правая граница не включается. Замените примерные значения своим диапазоном.

export FROM_MS="$((OPENED_AT - 1000))"
export TO_MS="$((OPENED_AT + 60000))"
export REQUEST_ID='REQ-2026-001'

Запрос первой страницы:

curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" --get \
  --data-urlencode "media=$STREAM" \
  --data-urlencode "from=$FROM_MS" \
  --data-urlencode "to=$TO_MS" \
  --data-urlencode 'meta={"access_control":{"request_id":"REQ-2026-001"}}' \
  "$CENTRAL_URL/central/api-v4/episodes" | jq

Сохраните следующий код в find-episodes.sh и выполните bash find-episodes.sh. Он передаёт фильтр серверу, проходит страницы результатов и выводит по одному JSON-объекту на каждый найденный эпизод. Опция curl --get помещает --data-urlencode в query string; тела у GET нет.

set -euo pipefail
filter=$(jq -cn --arg request_id "$REQUEST_ID" \
  '{"access_control":{"request_id":$request_id}}')
cursor=''
while :; do
  query=(--data-urlencode "media=$STREAM"
         --data-urlencode "from=$FROM_MS"
         --data-urlencode "to=$TO_MS"
         --data-urlencode "meta=$filter"
         --data-urlencode 'limit=1000')
  if [ -n "$cursor" ]; then
    query+=(--data-urlencode "cursor=$cursor")
  fi
  page=$(curl --fail-with-body --silent --show-error \
    --user "$API_LOGIN:$API_PASSWORD" --get "${query[@]}" \
    "$CENTRAL_URL/central/api-v4/episodes")
  printf '%s\n' "$page" | jq -c \
    '.episodes[] | {episode_id, media, opened_at, meta}'
  cursor=$(printf '%s\n' "$page" | jq -r '.next_cursor // empty')
  [ -n "$cursor" ] || break
done

Для поиска решения об открытии ворот используйте meta={"access_control":{"decision":"allow"}}. Для одновременной проверки заявки и решения задайте оба ключа: meta={"access_control":{"request_id":"REQ-2026-001","decision":"allow"}}.

Тип JSON учитывается: строка "123" отличается от числа 123. Для массивов проверяется наличие требуемых элементов: фильтр {"tags":["staff","delivery"]} подходит к {"tags":["delivery","staff","urgent"]}. Порядок и повторы элементов значения не имеют; скаляр также может совпасть с элементом массива. Это поиск по содержимому, а не сравнение массивов целиком.

Эпизоды без meta не подходят ни одному meta-фильтру. Пустой объект {} ищет объектное meta, в том числе непустое; фильтр null не означает отсутствие поля. Некорректный JSON или U+0000 в query возвращает 400. Для диапазонов значений и OR отдельного синтаксиса нет.

Продолжайте обход по next_cursor, сохраняя исходный JSON-фильтр и остальные параметры. Пустая страница с cursor не завершает поиск: она может означать пропуск слишком большой записи. offset не поддерживается. История меняется во время обхода и не является зафиксированным снимком. PATCH meta не меняет updated_at: сортировка по этому времени не позволяет отслеживать изменения дополнительных данных.

Срок хранения и ошибки

Харвестинг со стримера и повторный POST создания сохраняют meta. PATCH не меняет исходный payload, время события или срок его хранения. Дополнительные данные удаляются вместе с эпизодом; продлить его жизнь записью meta нельзя.

  • 404 — эпизод не найден или уже удалён.
  • 413 — тело запроса либо итоговое сериализованное meta превышает 32 МиБ.
  • 422 — неверный JSON или символ U+0000; ответ содержит структурированную ошибку.

При отказе прежнее значение meta сохраняется.