Files

7.2 KiB
Raw Permalink Blame History

media-mirror — API координатора зеркал

Сервис конвертации видео в «зеркала» для AR-очков: одно видео (480p, H.264, без звука) + N аудиодорожек (Vorbis/ogg) отдельными файлами. Воркеры на GPU-машинах молотят очередь из Postgres, готовые файлы лежат в S3 (SeaweedFS).

Базовый URL

Окружение URL
Прод (из интернета) https://mirror.binom.pw
Прод (внутри сети) http://mirror-api-media-mirror-api:8080

Авторизация

Все эндпоинты /api/** требуют заголовок:

X-API-Key: <ключ>

Без ключа или с неверным — 401. /actuator/** открыт (health-пробы).

Модель задачи (Job)

Поле Тип Описание
id uuid ID задачи
itemId string ID элемента из Jellyfin
status string new → processing → done / failed / cancelled
progress int 0..100
videoKey string S3-ключ видео (после done)
audioKeys array список аудиодорожек (после done)

Эндпоинты

1. Создать задачу на конвертацию

POST /api/mirror

Запрос:

{
  "itemId": "d57a20a9909921f0bd133c3bbae6f9db",
  "sourceUrl": "",        // опционально; пусто = воркер сам построит из itemId
  "sourceType": "jellyfin" // по умолчанию jellyfin
}

Ответы:

  • 201 Created — задача создана
  • 200 OK — задача с таким itemId уже существует (не дублируется), возвращается существующая
{ "id": "4a063de2-f5e9-40b7-b395-e9c17dd23301", "status": "new" }
  • 400 — itemId пустой / битый JSON
  • 401 — нет/неверный X-API-Key

2. Статус задачи по ID

GET /api/mirror/{id}
{
  "id": "4a063de2-f5e9-40b7-b395-e9c17dd23301",
  "itemId": "d57a20a9909921f0bd133c3bbae6f9db",
  "status": "processing",
  "progress": 45
}

404 — задачи нет.

3. Статус + файлы зеркала по itemId (главный для клиента!)

GET /api/mirror/by-item/{itemId}

Ответ, если готово:

{
  "itemId": "d57a20a9909921f0bd133c3bbae6f9db",
  "status": "done",
  "files": {
    "video": {
      "key": "mirror/d57a20a9909921f0bd133c3bbae6f9db/video.mkv",
      "url": "https://s3.binom.pw/media/mirror/d57a20a9909921f0bd133c3bbae6f9db/video.mkv"
    },
    "audios": [
      {
        "index": 0,
        "key": "mirror/d57a20a9909921f0bd133c3bbae6f9db/audio-0.ogg",
        "title": "Dub [Reanimedia]",
        "language": "rus",
        "url": "https://s3.binom.pw/media/mirror/d57a20a9909921f0bd133c3bbae6f9db/audio-0.ogg"
      },
      {
        "index": 1,
        "key": "mirror/d57a20a9909921f0bd133c3bbae6f9db/audio-1.ogg",
        "title": "Original",
        "language": "eng",
        "url": "https://s3.binom.pw/media/mirror/d57a20a9909921f0bd133c3bbae6f9db/audio-1.ogg"
      }
    ]
  }
}

Ответ, если ещё не готово:

{ "itemId": "...", "status": "processing", "files": null }

Ответ, если задачи нет вообще: 404 — клиент понимает: «ещё не конвертировано, можно заказать».

4. Список задач (фильтр по статусу)

GET /api/mirror?status=done&limit=50

status — опционально (new/processing/done/failed/cancelled), limit — опционально (дефолт 50).

Ответ:

[
  { "id": "...", "itemId": "...", "status": "done", "progress": 100 }
]

5. Удалить / отменить задачу

DELETE /api/mirror/{id}
  • задача new/processing → cancelled (воркер пропустит)
  • задача done → файлы удаляются из S3 + запись удаляется
  • повторный DELETE → 404

6. Сканировать библиотеку Jellyfin (догонялка)

POST /api/mirror/scan

Обходит Jellyfin (Movies + Episodes), создаёт задачи на только недостающее:

  • есть активная задача (new/processing/done) → skip
  • нет задачи / последняя failed/cancelled → create

Идемпотентен — можно дёргать сколько угодно, дублей не будет. Используется, когда webhook'и Jellyfin потерялись.

Ответ:

{ "found": 1387, "created": 12, "skipped": 1375 }

502 — Jellyfin недоступен.

Формат зеркала (что получит клиент)

Файл Кодек Контейнер Разрешение Описание
video.mkv H.264 (NVENC) mkv 480p (высота ≤480) без звука, без субтитров/метаданных
audio-N.ogg Vorbis ogg 2ch каждая дорожка исходника отдельным файлом

Сколько аудио — столько дорожек было в исходнике (все без исключения). Мета каждой дорожки (title, language) — в ответе by-item, берётся из ffprobe исходника.

Скачивание файлов (S3)

Клиент качает файлы напрямую из S3 (SeaweedFS), не через координатор:

  • S3 endpoint: https://s3.binom.pw
  • bucket: media
  • путь: mirror/{itemId}/video.mkv, mirror/{itemId}/audio-{index}.ogg

Креды клиента (зашиты в приложение):

accessKey: MirrorClient01QwertyKey
secretKey: MirrorClient01SecretKeyValueForGlassesOnly

Права клиентского ключа: только чтение media/mirror/* — писать/удалять/листать нельзя. Подписывать запросы по S3 SigV4 (AWS SDK, region us-east-1). Поддержка Range-запросов есть (видео можно качать/играть по кускам).

Пример полного цикла клиента

1. GET https://mirror.binom.pw/api/mirror/by-item/{itemId}   [X-API-Key]
   → 404 → 2а
   → files → 4
2а. POST /api/mirror  {"itemId": "..."}                      [X-API-Key]
2б. GET by-item, пока status != done (поллинг раз в 5-10с)
3. status == done → берём files.video.url + files.audios[].url
4. Качаем по url с mirror-client кредами (SigV4) → играем
   (видео — на очки, выбранную аудио-дорожку — на телефон)

Health

GET /actuator/health

Открыт без ключа. {"status": "UP"} — сервис жив.