7.2 KiB
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 пустой / битый JSON401— нет/неверный 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"} — сервис жив.