Эпизоды через 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 сохраняется.