Готовые сценарии
Рабочие рецепты на curl и Python — добавить поток, завести сотню потоков из списка, снять статистику, выгрузить зрителей, включить архив, проверить здоровье.
Готовые к запуску сценарии. Каждый работает как есть — подставьте адрес сервера и пароль. Отдельные операции разобраны на страницах Потоки и Конфигурация и система, общие правила — во Введении.
Подготовка#
Все примеры на bash рассчитывают на две переменные:
BASE=https://media.example.com TOKEN=$(curl -sS -X POST "$BASE/api/v1/auth/login" \ -H 'Content-Type: application/json' \ -d '{"password":"ваш-пароль-администратора"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
Сессионный токен живёт 12 часов, и скрипт, запускаемый по расписанию, будет входить заново каждый
раз. Задайте auth { api_write_token "…"; } (или api_read_token для сценариев, которые только
читают) и подставляйте его в TOKEN напрямую — вход не потребуется:
BASE=https://media.example.com TOKEN='ваш-статический-токен-не-короче-16-символов'
Примеры на Python используют только стандартную библиотеку. Общая заготовка — сохраните её как
sirin.py рядом со скриптами:
"""Минимальный клиент /api/v1 — только стандартная библиотека.""" import json import urllib.error import urllib.parse import urllib.request BASE = "https://media.example.com" def api(path, token=None, method="GET", body=None, timeout=30): """Один запрос к /api/v1. Возвращает разобранный JSON.""" req = urllib.request.Request(BASE + "/api/v1" + path, method=method) if token: req.add_header("Authorization", "Bearer " + token) data = None if body is not None: data = json.dumps(body).encode() req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, data, timeout=timeout) as resp: return json.load(resp) except urllib.error.HTTPError as e: detail = e.read().decode("utf-8", "replace") raise RuntimeError("%s %s → HTTP %d: %s" % (method, path, e.code, detail)) def login(password): """Вход по паролю администратора. Возвращает сессионный токен на 12 часов.""" return api("/auth/login", method="POST", body={"password": password})["token"] def sid(stream_id): """Идентификатор потока для URL: live/cam1 → live%2Fcam1.""" return urllib.parse.quote(stream_id, safe="")
Добавить поток#
curl -sS -X POST "$BASE/api/v1/streams" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{"path":"cam1", "directives":"input rtsp://admin:пароль@10.0.0.10:554/Streaming/Channels/101;"}'
{"epoch":12,"hash":"4a186afe976f6ef3","path":"cam1","changed":true}
sleep 3 curl -sS "$BASE/api/v1/streams/cam1?fields=id,alive,health,fps,bitrate" \ -H "Authorization: Bearer $TOKEN"
{"id":"cam1","alive":true,"health":"ok","fps":25.0,"bitrate":2920000.0}
Если пришло 404 — поток создан, но ещё не запустился либо источник недоступен; посмотрите
GET /api/v1/logs?stream=cam1. Если health держится failing — проверьте адрес и учётные данные
камеры.
Зрителю поток доступен сразу по всем протоколам:
http://media.example.com:8080/cam1/index.m3u8 HLS http://media.example.com:8080/cam1/embed.html тестовый плеер rtsp://media.example.com:8554/cam1 RTSP
Завести потоки из списка#
Типовая задача при вводе объекта: есть таблица камер — нужно сто потоков. Подготовьте cameras.csv:
path,url lobby/cam1,rtsp://admin:пароль@10.0.0.10:554/Streaming/Channels/101 lobby/cam2,rtsp://admin:пароль@10.0.0.11:554/Streaming/Channels/101 gate/cam1,rtsp://admin:пароль@10.0.0.12:554/Streaming/Channels/101
#!/usr/bin/env python3 """Массовое создание потоков из CSV. Повторный запуск безопасен.""" import csv import sys import time from sirin import api, login, sid PASSWORD = "ваш-пароль-администратора" RECORD = True # писать архив STORAGE = "local" # имя хранилища из конфигурации RETENTION = "7d 500G" # глубина: по времени и по объёму token = login(PASSWORD) # Что уже есть — чтобы повторный запуск не падал на 409. existing = {row["path"] for row in api("/config/streams", token)} created = skipped = failed = 0 with open("cameras.csv", newline="", encoding="utf-8") as f: for row in csv.DictReader(f): path, url = row["path"].strip(), row["url"].strip() if not path or not url: continue if path in existing: print("= %s уже настроен" % path) skipped += 1 continue directives = "input %s;" % url if RECORD: directives += "\n dvr %s %s;" % (STORAGE, RETENTION) try: res = api("/streams", token, method="POST", body={"path": path, "directives": directives}) print("+ %s создан (версия %d)" % (path, res["epoch"])) created += 1 except RuntimeError as e: print("! %s: %s" % (path, e), file=sys.stderr) failed += 1 # Каждое создание переписывает и применяет конфигурацию — не гоните очередь. time.sleep(0.2) print("\nсоздано %d, пропущено %d, с ошибкой %d" % (created, skipped, failed))
+ lobby/cam1 создан (версия 12) + lobby/cam2 создан (версия 13) = gate/cam1 уже настроен создано 2, пропущено 1, с ошибкой 0
POST /streams переписывает файл и применяет его целиком. Для сотни камер это сто применений: пауза
между запросами обязательна, а параллельно запускать такой скрипт нельзя — вторая копия получит 409.
Если камер тысячи, соберите весь текст конфигурации сами и примените один раз через
PUT /config.
Если у всех камер совпадают параметры записи и упаковки, опишите их один раз шаблоном
(Configuration → Templates) и ссылайтесь на него из блока потока — тогда в directives останется
только input.
Снять статистику#
Сводка по узлу одной командой:
curl -sS "$BASE/api/v1/totals" -H "Authorization: Bearer $TOKEN" \ | python3 -c ' import sys, json t = json.load(sys.stdin) print("потоков: %d" % t["streams"]) print("зрителей: %d (живых %d, архив %d)" % (t["sessions"], t["live_sessions"], t["dvr_sessions"])) print("принято: %.1f ГиБ" % (t["bytes_in"] / 2**30)) print("отдано: %.1f ГиБ (архив %.1f ГиБ)" % (t["bytes_out"] / 2**30, t["dvr_bytes_out"] / 2**30)) '
потоков: 10 зрителей: 1 (живых 1, архив 0) принято: 6.1 ГиБ отдано: 0.1 ГиБ (архив 0.0 ГиБ)
Таблица по потокам, отсортированная по числу зрителей:
curl -sS "$BASE/api/v1/streams?limit=50000&sort=viewers:desc&fields=id,health,viewers,bitrate,fps,reconnects,input_errors" \ -H "Authorization: Bearer $TOKEN" \ | python3 -c ' import sys, json d = json.load(sys.stdin) head = "%-28s %-10s %6s %8s %6s %11s %7s" row = "%-28s %-10s %6d %8.2f %6.1f %11d %7d" print(head % ("поток", "здоровье", "зрит.", "Мбит/с", "fps", "переподкл.", "ошибок")) for s in d["items"]: print(row % (s["id"], s["health"], s["viewers"], s["bitrate"] / 1e6, s["fps"], s["reconnects"], s["input_errors"])) print("\nвсего потоков: %d" % d["total"]) '
поток здоровье зрит. Мбит/с fps переподкл. ошибок cam1 ok 2 3.73 25.0 0 0 cam2 ok 1 1.37 25.1 0 0 gate/cam1 failing 0 0.00 0.0 13 13 всего потоков: 3
Ресурсы хоста:
curl -sS "$BASE/api/v1/system" -H "Authorization: Bearer $TOKEN" \ | python3 -c ' import sys, json s = json.load(sys.stdin) print("CPU процесса: %.2f ядра из %d рабочих" % (s["proc_cpu_cores"], s["worker_cores"])) print("CPU хоста: %.1f%%" % s["host_cpu_busy_pct"]) print("память: %d МиБ RSS из %d МиБ" % (s["rss_mb"], s["mem_total_mb"])) print("дескрипторов: %d" % s["open_fds"]) print("аптайм: %.1f ч" % (s["uptime_s"] / 3600)) '
Выгрузить список зрителей#
curl -sS "$BASE/api/v1/sessions?limit=50000&fields=stream,protocol,client_ip,user_agent,bytes_out,connected_ms,rr_fraction_lost" \ -H "Authorization: Bearer $TOKEN" \ | python3 -c ' import sys, json, csv, datetime d = json.load(sys.stdin) w = csv.writer(sys.stdout) w.writerow(["поток", "протокол", "адрес", "отдано_байт", "потери_%", "подключён", "клиент"]) for s in d["items"]: when = datetime.datetime.fromtimestamp(s["connected_ms"] / 1000) w.writerow([s["stream"], s["protocol"], s.get("client_ip") or "", s["bytes_out"], round(s["rr_fraction_lost"] * 100 / 256, 1), when.isoformat(timespec="seconds"), s.get("user_agent") or ""]) ' > viewers.csv
поток,протокол,адрес,отдано_байт,потери_%,подключён,клиент cam1,hls-fmp4,10.0.0.55,109546034,0.0,2026-08-19T17:56:05,Mozilla/5.0 … cam1,dvr,10.0.0.61,54428222,0.0,2026-08-19T17:58:47,Mozilla/5.0 …
GET /sessions показывает активные сессии. Ушедший зритель исчезает из списка бесследно. Для
учёта всех просмотров подпишитесь на тему sessions-audit потока событий и складывайте события в
своё хранилище — см. Мониторинг и метрики.
Только проблемные зрители — те, у кого есть потери или задержки доставки:
curl -sS "$BASE/api/v1/sessions?min_loss=1&fields=stream,protocol,client_ip,rr_fraction_lost,lag_events" \ -H "Authorization: Bearer $TOKEN"
Включить архив у существующего потока#
Правка потока заменяет тело блока целиком, поэтому нельзя просто дописать строку — нужно прочитать текущие директивы, добавить свою и отправить всё обратно.
#!/usr/bin/env python3 """Включить запись архива у потока, не потеряв остальные его директивы.""" from sirin import api, login, sid PASSWORD = "ваш-пароль-администратора" STREAM = "cam1" STORAGE = "local" RETENTION = "7d 500G" token = login(PASSWORD) # 1. Текущее тело блока — работает и для остановленного потока. cur = api("/streams/%s/config" % sid(STREAM), token) directives = cur["directives"].rstrip() print("было:\n%s\n" % directives) if "dvr " in directives: raise SystemExit("у потока %s архив уже настроен — правьте вручную" % STREAM) # 2. Дописываем одну директиву к существующему телу. directives += "\n dvr %s %s;" % (STORAGE, RETENTION) # 3. Отправляем тело целиком. res = api("/streams/%s" % sid(STREAM), token, method="PATCH", body={"directives": directives}) print("стало:\n%s\n" % directives) print("применено, версия конфигурации %d" % res["epoch"])
было: input rtsp://admin:пароль@10.0.0.10:554/Streaming/Channels/101; стало: input rtsp://admin:пароль@10.0.0.10:554/Streaming/Channels/101; dvr local 7d 500G; применено, версия конфигурации 14
curl -sS "$BASE/api/v1/storages" -H "Authorization: Bearer $TOKEN" \ | python3 -c ' import sys, json for st in json.load(sys.stdin): print("%s: занято %.1f ГиБ" % (st["name"], st["used_bytes"] / 2**30)) for path, files, used in st["streams"]: print(" %-28s %6d сегм. %.2f ГиБ" % (path, files, used / 2**30)) '
Поток должен появиться в списке хранилища в течение минуты. Если его нет — убедитесь, что хранилище
с таким именем существует, и загляните в GET /api/v1/logs?stream=cam1.
Директива dvr ссылается на блок storage по имени. Если такого блока нет, правка будет отклонена
кодом 422 и на диск не попадёт — это защита, а не сбой. Как завести хранилище —
Хранилища и S3.
Проверка здоровья для системы мониторинга#
Скрипт возвращает коды выхода, принятые в системах мониторинга: 0 — норма, 1 — предупреждение,
2 — авария.
#!/bin/sh # health.sh — проверка узла SirinVideo. # Переменные окружения: BASE, TOKEN (достаточно api_read_token). set -eu counts=$(curl -sS --max-time 10 "$BASE/api/v1/streams/counts" \ -H "Authorization: Bearer $TOKEN") \ || { echo "CRITICAL: сервер недоступен"; exit 2; } field() { printf '%s' "$counts" | python3 -c "import sys,json;print(json.load(sys.stdin)$1)"; } total=$(field '["total"]') failing=$(field '["health"]["failing"]') degraded=$(field '["health"]["degraded"]') if [ "$failing" -gt 0 ]; then echo "CRITICAL: потоков в отказе $failing из $total"; exit 2 fi if [ "$degraded" -gt 0 ]; then echo "WARNING: деградировавших потоков $degraded из $total"; exit 1 fi echo "OK: все $total потоков здоровы"; exit 0
BASE=https://media.example.com TOKEN='ваш-токен-чтения' ./health.sh; echo "код выхода $?"
OK: все 10 потоков здоровы код выхода 0
Более подробная проверка — с учётом лицензии, места на дисках и молчащих источников:
#!/usr/bin/env python3 """Развёрнутая проверка узла. Код выхода: 0 норма, 1 предупреждение, 2 авария.""" import sys from sirin import api TOKEN = "ваш-токен-чтения" # достаточно api_read_token NO_DATA_LIMIT = 30 # секунд молчания источника до тревоги DISK_LIMIT = 95 # процент заполнения диска до тревоги warn, crit = [], [] try: lic = api("/license", TOKEN) if lic["state"] == "grace": warn.append("лицензия не обновляется: %s" % lic.get("reason", "причина не указана")) elif lic["state"] != "licensed": crit.append("лицензия недействительна (%s) — медиа не отдаётся" % lic["state"]) counts = api("/streams/counts", TOKEN) if counts["health"]["failing"]: crit.append("потоков в отказе: %d" % counts["health"]["failing"]) if counts["health"]["degraded"]: warn.append("деградировавших потоков: %d" % counts["health"]["degraded"]) quiet = [s["id"] for s in api("/streams?fields=id,no_data_secs&limit=50000", TOKEN)["items"] if s.get("no_data_secs", 0) > NO_DATA_LIMIT] if quiet: crit.append("нет данных от источников: %s" % ", ".join(sorted(quiet)[:5])) for st in api("/storages", TOKEN): if not st["total_bytes"]: continue pct = 100 * st["used_bytes"] / st["total_bytes"] if pct >= DISK_LIMIT: warn.append("хранилище %s заполнено на %.0f%%" % (st["name"], pct)) if not st["identity_healthy"] or not st["maintenance_healthy"]: crit.append("хранилище %s не обслуживается" % st["name"]) if not st["s3_healthy"]: warn.append("ярус S3 хранилища %s недоступен" % st["name"]) except Exception as e: # сеть, 401, 5xx print("CRITICAL: %s" % e) sys.exit(2) if crit: print("CRITICAL: " + "; ".join(crit + warn)) sys.exit(2) if warn: print("WARNING: " + "; ".join(warn)) sys.exit(1) print("OK: %d потоков, лицензия в порядке, хранилища в норме" % counts["total"]) sys.exit(0)
OK: 10 потоков, лицензия в порядке, хранилища в норме
Скрипт-проверка отвечает на вопрос «всё ли хорошо прямо сейчас». Для графиков и истории поднимите
сбор с эндпоинта GET /metrics — там те же данные в формате Prometheus, включая ряды по каждому
потоку. См. Мониторинг и метрики.
Куда дальше#
- Потоки — все операции над потоками с параметрами и кодами ошибок.
- Конфигурация и система — запись конфигурации, откат, хранилища, журналы.
- Мониторинг и метрики — Prometheus, поток событий, что отслеживать.
openapi.yaml— машиночитаемая спецификация всех 71 операции.