Hermes Agent commited on
Commit
2bc80a8
·
1 Parent(s): 37a6b13

feat(perf): implement direct-play bypass, HD artwork pipeline, multi-bot pooling, WAL checkpoints and read-ahead buffering

Browse files
apex/apex_stream/stream.py CHANGED
@@ -254,6 +254,14 @@ class StreamDriver:
254
  for _ in range(to_skip):
255
  await run.read_chunk()
256
  while chunk_index <= run_end:
 
 
 
 
 
 
 
 
257
  chunk = await run.read_chunk()
258
  chunk_index += 1
259
  if emitted_window == 0:
@@ -334,6 +342,22 @@ class StreamDriver:
334
 
335
  return _factory
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  # --- convenience ---------------------------------------------------
338
 
339
  def status_snapshot(self) -> dict:
 
254
  for _ in range(to_skip):
255
  await run.read_chunk()
256
  while chunk_index <= run_end:
257
+ # Predictive Read-Ahead: when reaching the end of the current run,
258
+ # asynchronously prefetch the subsequent run into cache
259
+ if chunk_index == run_end and run_end < layout.end_chunk:
260
+ next_run_start = run_end + 1
261
+ next_run_count = min(cfg.run_size, layout.end_chunk - next_run_start + 1)
262
+ asyncio.create_task(
263
+ self._prefetch_next_run(source_key, next_run_start, next_run_count, plan.dc_id)
264
+ )
265
  chunk = await run.read_chunk()
266
  chunk_index += 1
267
  if emitted_window == 0:
 
342
 
343
  return _factory
344
 
345
+ async def _prefetch_next_run(self, source_key: tuple, next_run_start: int, next_run_count: int, dc_id: Optional[int]) -> None:
346
+ """Asynchronously pre-warm the next chunk run in RAM cache ahead of client playback."""
347
+ try:
348
+ client = await self.pool.select(dc_id=dc_id)
349
+ try:
350
+ factory = self._factory_for(client.client, source_key[0], source_key[1])
351
+ source_run = await self.registry.get_run(source_key, next_run_start, next_run_count, factory)
352
+ try:
353
+ await asyncio.wait_for(source_run.read_chunk(), timeout=2.0)
354
+ finally:
355
+ await source_run.close()
356
+ finally:
357
+ await self.pool.release(client.client_id)
358
+ except Exception:
359
+ pass
360
+
361
  # --- convenience ---------------------------------------------------
362
 
363
  def status_snapshot(self) -> dict:
apex/apex_stream/tmdb_meta.py CHANGED
@@ -185,6 +185,14 @@ _MEDIA_EXT_RE = re.compile(
185
  )
186
 
187
 
 
 
 
 
 
 
 
 
188
  def _strip_noise(text: str) -> str:
189
  """Strip group tags ([...], @Handle), the real media extension and collapse
190
  whitespace.
@@ -196,7 +204,9 @@ def _strip_noise(text: str) -> str:
196
  keeping them produces dotted folder names and breaks both Jellyfin
197
  matching and TMDB search queries.
198
  """
199
- t = re.sub(r"\[[^\]]*\]", " ", text)
 
 
200
  t = re.sub(r"@[\w.\-]+", " ", t)
201
  t = _MEDIA_EXT_RE.sub(" ", t)
202
  # "+" and "&" are release/present separators too ("Dd+5.1 H" -> "Dd 5 1 H",
 
185
  )
186
 
187
 
188
+ _URL_NOISE_RE = re.compile(
189
+ r"(?i)\b(?:https?://\S+|www\.\S+|\S+\.(?:com|org|net|vip|in|me|xyz|top|site|cc|to|is|pm|ms|link))\b"
190
+ )
191
+ _WATERMARK_PAREN_RE = re.compile(
192
+ r"(?i)\s*\([^)]*(?:www\.|t\.me|https?://|\.com|\.org|\.net|\.vip|\.in|\.xyz|tamil|bolly|cinema|esub|dual[- ]?audio|clean[- ]?audio|hq[- ]?line|rip)[^)]*\)"
193
+ )
194
+
195
+
196
  def _strip_noise(text: str) -> str:
197
  """Strip group tags ([...], @Handle), the real media extension and collapse
198
  whitespace.
 
204
  keeping them produces dotted folder names and breaks both Jellyfin
205
  matching and TMDB search queries.
206
  """
207
+ t = _URL_NOISE_RE.sub(" ", text)
208
+ t = _WATERMARK_PAREN_RE.sub(" ", t)
209
+ t = re.sub(r"\[[^\]]*\]", " ", t)
210
  t = re.sub(r"@[\w.\-]+", " ", t)
211
  t = _MEDIA_EXT_RE.sub(" ", t)
212
  # "+" and "&" are release/present separators too ("Dd+5.1 H" -> "Dd 5 1 H",
apex/combined-entrypoint.sh CHANGED
@@ -42,6 +42,7 @@ restore_jellyfin_local() {
42
  \( -name metadata -o -name metadata.snapshot \) -prune -o \
43
  -exec cp -a -f {} "$LOCAL_ROOT/data/" \; 2>/dev/null || true
44
  echo "[PERSISTENCE] Jellyfin database snapshot found ($(stat -c '%s' /data/jellyfin/data/data/jellyfin.db 2>/dev/null || echo unknown) bytes)."
 
45
  else
46
  echo "[PERSISTENCE] No Jellyfin database snapshot found; Jellyfin will run first-time initialization."
47
  fi
@@ -111,6 +112,7 @@ sync_jellyfin_back() {
111
  while :; do
112
  sleep 20
113
  if [ -f "$LOCAL_ROOT/data/data/jellyfin.db" ]; then
 
114
  mkdir -p /data/jellyfin/data/data
115
  snapshot="/data/jellyfin/data/data/jellyfin.db.tmp"
116
  if sqlite3 "$LOCAL_ROOT/data/data/jellyfin.db" ".backup '$snapshot'" 2>/dev/null; then
@@ -228,6 +230,33 @@ for bcfg in "$LOCAL_ROOT/config/branding.xml" "/data/jellyfin/config/branding.xm
228
  fi
229
  done
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  echo "[HEALTH] Jellyfin starting in background (local datadir)..."
232
  launch_jellyfin
233
 
 
42
  \( -name metadata -o -name metadata.snapshot \) -prune -o \
43
  -exec cp -a -f {} "$LOCAL_ROOT/data/" \; 2>/dev/null || true
44
  echo "[PERSISTENCE] Jellyfin database snapshot found ($(stat -c '%s' /data/jellyfin/data/data/jellyfin.db 2>/dev/null || echo unknown) bytes)."
45
+ sqlite3 "$LOCAL_ROOT/data/data/jellyfin.db" "PRAGMA journal_mode=WAL; PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true
46
  else
47
  echo "[PERSISTENCE] No Jellyfin database snapshot found; Jellyfin will run first-time initialization."
48
  fi
 
112
  while :; do
113
  sleep 20
114
  if [ -f "$LOCAL_ROOT/data/data/jellyfin.db" ]; then
115
+ sqlite3 "$LOCAL_ROOT/data/data/jellyfin.db" "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || true
116
  mkdir -p /data/jellyfin/data/data
117
  snapshot="/data/jellyfin/data/data/jellyfin.db.tmp"
118
  if sqlite3 "$LOCAL_ROOT/data/data/jellyfin.db" ".backup '$snapshot'" 2>/dev/null; then
 
230
  fi
231
  done
232
 
233
+ # Configure encoding.xml for direct play / stream remuxing (prevents CPU-heavy video transcode)
234
+ ENCODING_XML="$LOCAL_ROOT/config/encoding.xml"
235
+ python3 - "$ENCODING_XML" <<'PY'
236
+ import sys
237
+ from pathlib import Path
238
+
239
+ path = Path(sys.argv[1])
240
+ content = """<?xml version="1.0" encoding="utf-8"?>
241
+ <EncodingOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
242
+ <EncodingThreadCount>0</EncodingThreadCount>
243
+ <TranscodingTempPath>/opt/jellyfin-local/cache/transcodes</TranscodingTempPath>
244
+ <DownMixAudioBoost>2</DownMixAudioBoost>
245
+ <EnableThrottling>true</EnableThrottling>
246
+ <ThrottleDelaySeconds>180</ThrottleDelaySeconds>
247
+ <HardwareAccelerationType>none</HardwareAccelerationType>
248
+ <EnableVideoEncoding>false</EnableVideoEncoding>
249
+ <EnableAudioEncoding>true</EnableAudioEncoding>
250
+ <AllowVideoStreamCopy>true</AllowVideoStreamCopy>
251
+ <AllowAudioStreamCopy>true</AllowAudioStreamCopy>
252
+ </EncodingOptions>
253
+ """
254
+ path.parent.mkdir(parents=True, exist_ok=True)
255
+ path.write_text(content.strip(), encoding="utf-8")
256
+ PY
257
+ cp -f "$ENCODING_XML" "/data/jellyfin/config/encoding.xml" 2>/dev/null || true
258
+ echo "[JELLYFIN] Configured zero-CPU video encoding & direct stream remux in $ENCODING_XML"
259
+
260
  echo "[HEALTH] Jellyfin starting in background (local datadir)..."
261
  launch_jellyfin
262
 
apex/gateway/main.py CHANGED
@@ -66,6 +66,13 @@ JELLYFIN_PORT = int(os.environ.get("JELLYFIN_PORT", "8096"))
66
  TG_PORT = int(os.environ.get("TG_LISTEN_PORT", "8082"))
67
  FILETOLINK_PORT = int(os.environ.get("FILETOLINK_PORT", "8081"))
68
 
 
 
 
 
 
 
 
69
  @app.get("/health")
70
  @app.get("/debug/status")
71
  async def health_check():
 
66
  TG_PORT = int(os.environ.get("TG_LISTEN_PORT", "8082"))
67
  FILETOLINK_PORT = int(os.environ.get("FILETOLINK_PORT", "8081"))
68
 
69
+ @app.get("/ping")
70
+ @app.get("/keepalive")
71
+ @app.get("/heartbeat")
72
+ async def keepalive_ping():
73
+ """Fast, zero-overhead heartbeat endpoint for UptimeRobot / cron pings to prevent Space sleep."""
74
+ return JSONResponse({"status": "ok", "service": "Apex-Jellyfin", "alive": True})
75
+
76
  @app.get("/health")
77
  @app.get("/debug/status")
78
  async def health_check():
apex/gateway/utils.py CHANGED
@@ -298,6 +298,20 @@ async def proxy_http_request(
298
  except Exception:
299
  pass
300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  normal_resp = Response(
302
  content=content,
303
  status_code=status_code,
 
298
  except Exception:
299
  pass
300
 
301
+ # Direct Play Optimization: Force DirectPlay & DirectStream in PlaybackInfo responses
302
+ # for .strm items so modern browsers and devices stream natively without HLS CPU transcoding
303
+ if "application/json" in content_type and "playbackinfo" in request.url.path.lower() and content:
304
+ try:
305
+ import json
306
+ pb_data = json.loads(content.decode("utf-8"))
307
+ for m_src in pb_data.get("MediaSources") or []:
308
+ if isinstance(m_src, dict):
309
+ m_src["SupportsDirectPlay"] = True
310
+ m_src["SupportsDirectStream"] = True
311
+ content = json.dumps(pb_data).encode("utf-8")
312
+ except Exception:
313
+ pass
314
+
315
  normal_resp = Response(
316
  content=content,
317
  status_code=status_code,
apex/tests/test_gateway_plugin_routing.py CHANGED
@@ -1,4 +1,5 @@
1
  import pytest
 
2
  from starlette.datastructures import Headers
3
  from unittest.mock import AsyncMock, patch
4
 
@@ -90,3 +91,58 @@ async def test_jellyfin_ui_assets_rewrite_to_web():
90
  target_url = mock_proxy.call_args[0][0]
91
  assert "8096" in target_url
92
  assert "/web/ui/abyss.css" in target_url
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pytest
2
+ import httpx
3
  from starlette.datastructures import Headers
4
  from unittest.mock import AsyncMock, patch
5
 
 
91
  target_url = mock_proxy.call_args[0][0]
92
  assert "8096" in target_url
93
  assert "/web/ui/abyss.css" in target_url
94
+
95
+
96
+ @pytest.mark.asyncio
97
+ async def test_keepalive_endpoints():
98
+ from gateway.main import keepalive_ping
99
+ resp = await keepalive_ping()
100
+ assert resp.status_code == 200
101
+ import json
102
+ data = json.loads(resp.body.decode("utf-8"))
103
+ assert data["status"] == "ok"
104
+ assert data["alive"] is True
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ async def test_playbackinfo_directplay_override():
109
+ import json
110
+ from unittest.mock import MagicMock
111
+ from gateway.utils import proxy_http_request
112
+
113
+ # Mock response with MediaSources having SupportsDirectPlay=False
114
+ mock_resp = MagicMock()
115
+ mock_resp.status_code = 200
116
+ mock_resp.headers = httpx.Headers({"content-type": "application/json"})
117
+ payload = {
118
+ "MediaSources": [
119
+ {
120
+ "Id": "item1",
121
+ "Path": "/data/media/Movies/Spider-Man.strm",
122
+ "SupportsDirectPlay": False,
123
+ "SupportsDirectStream": False,
124
+ }
125
+ ]
126
+ }
127
+ mock_resp.aread = AsyncMock(return_value=json.dumps(payload).encode("utf-8"))
128
+ mock_resp.aclose = AsyncMock()
129
+
130
+ mock_client = MagicMock()
131
+ mock_client.build_request.return_value = MagicMock()
132
+ mock_client.send = AsyncMock(return_value=mock_resp)
133
+
134
+ with patch("gateway.utils.get_http_client", return_value=mock_client):
135
+ from starlette.datastructures import QueryParams
136
+ req = AsyncMock()
137
+ req.url.path = "/jellyfin/Items/123/PlaybackInfo"
138
+ req.headers = Headers({"accept": "application/json"})
139
+ req.method = "POST"
140
+ req.body = AsyncMock(return_value=b"{}")
141
+ req.query_params = QueryParams([])
142
+
143
+ result = await proxy_http_request("http://127.0.0.1:8096/Items/123/PlaybackInfo", req)
144
+ assert result.status_code == 200
145
+ out_data = json.loads(result.body.decode("utf-8"))
146
+ src = out_data["MediaSources"][0]
147
+ assert src["SupportsDirectPlay"] is True
148
+ assert src["SupportsDirectStream"] is True
apex/tg_streamer.py CHANGED
@@ -759,10 +759,25 @@ apex_driver = StreamDriver(
759
  # they never consume updates so the webhook/primary bot owns ingestion.
760
  extra_clients = []
761
  if not APEX_CFG.extra_tokens:
762
- APEX_CFG.extra_tokens = [
763
- value for index in range(1, 100)
764
- if (value := os.environ.get(f"JELLYFIN_MULTI_TOKEN{index}", "").strip())
765
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  for _ec_idx, _ec_token in enumerate(APEX_CFG.extra_tokens, start=2):
767
  if API_ID and API_HASH and _ec_token:
768
  try:
@@ -1444,10 +1459,24 @@ async def _enrich_media_metadata(entry):
1444
  f.write(build_tvshow_nfo(detail))
1445
  poster_url = _best_image("poster_path")
1446
  if poster_url and not os.path.exists(poster_path):
1447
- await _download_image(session, _resize_tmdb_image(poster_url), poster_path)
 
 
 
 
 
 
 
1448
  backdrop_url = _best_image("backdrop_path")
1449
  if backdrop_url and not os.path.exists(backdrop_path):
1450
- await _download_image(session, _resize_tmdb_image(backdrop_url, "w1280"), backdrop_path)
 
 
 
 
 
 
 
1451
 
1452
  season_detail = None
1453
  season_number = int(entry.get("season") or 1)
@@ -1476,12 +1505,35 @@ async def _enrich_media_metadata(entry):
1476
  )
1477
  with open(nfo_path, "w", encoding="utf-8") as f:
1478
  f.write(build_movie_nfo(detail))
 
 
 
 
 
 
 
1479
  poster_url = _best_image("poster_path")
1480
- if poster_url and not os.path.exists(os.path.join(target_dir, "poster.jpg")):
1481
- await _download_image(session, _resize_tmdb_image(poster_url), os.path.join(target_dir, "poster.jpg"))
 
 
 
 
 
 
 
 
1482
  backdrop_url = _best_image("backdrop_path")
1483
- if backdrop_url and not os.path.exists(os.path.join(target_dir, "backdrop.jpg")):
1484
- await _download_image(session, _resize_tmdb_image(backdrop_url, "w1280"), os.path.join(target_dir, "backdrop.jpg"))
 
 
 
 
 
 
 
 
1485
 
1486
  prev = FILE_ID_CACHE.get(key)
1487
  if isinstance(prev, dict):
@@ -1542,9 +1594,11 @@ def _tmdb_year(detail):
1542
  return None
1543
 
1544
 
1545
- def _resize_tmdb_image(url, size="w500"):
1546
- """Rewrite a TMDB original image URL to a smaller requested size."""
1547
- if "image.tmdb.org/" not in url:
 
 
1548
  return url
1549
  return url.replace("/original", f"/{size}", 1)
1550
 
 
759
  # they never consume updates so the webhook/primary bot owns ingestion.
760
  extra_clients = []
761
  if not APEX_CFG.extra_tokens:
762
+ discovered_tokens = []
763
+ for index in range(1, 100):
764
+ if val := os.environ.get(f"JELLYFIN_MULTI_TOKEN{index}", "").strip():
765
+ if val not in discovered_tokens:
766
+ discovered_tokens.append(val)
767
+ for prefix in ("BOT_TOKEN_", "TG_BOT_TOKEN_", "MULTI_TOKEN_"):
768
+ for index in range(1, 25):
769
+ if val := os.environ.get(f"{prefix}{index}", "").strip():
770
+ if val not in discovered_tokens and val != BOT_TOKEN:
771
+ discovered_tokens.append(val)
772
+ for env_name in ("EXTRA_BOT_TOKENS", "MULTI_BOT_TOKENS", "APEX_EXTRA_TOKENS", "APEX_TELEGRAM_EXTRA_TOKENS"):
773
+ if val := os.environ.get(env_name, "").strip():
774
+ for tok in val.split(","):
775
+ tok_clean = tok.strip()
776
+ if tok_clean and tok_clean not in discovered_tokens and tok_clean != BOT_TOKEN:
777
+ discovered_tokens.append(tok_clean)
778
+ APEX_CFG.extra_tokens = discovered_tokens
779
+ if APEX_CFG.extra_tokens:
780
+ logger.info(f"[POOL] Discovered {len(APEX_CFG.extra_tokens)} extra streaming bot token(s).")
781
  for _ec_idx, _ec_token in enumerate(APEX_CFG.extra_tokens, start=2):
782
  if API_ID and API_HASH and _ec_token:
783
  try:
 
1459
  f.write(build_tvshow_nfo(detail))
1460
  poster_url = _best_image("poster_path")
1461
  if poster_url and not os.path.exists(poster_path):
1462
+ if await _download_image(session, _resize_tmdb_image(poster_url, "w780"), poster_path):
1463
+ folder_path = os.path.join(show_root, "folder.jpg")
1464
+ if not os.path.exists(folder_path):
1465
+ try:
1466
+ with open(poster_path, "rb") as src, open(folder_path, "wb") as dst:
1467
+ dst.write(src.read())
1468
+ except OSError:
1469
+ pass
1470
  backdrop_url = _best_image("backdrop_path")
1471
  if backdrop_url and not os.path.exists(backdrop_path):
1472
+ if await _download_image(session, _resize_tmdb_image(backdrop_url, "original"), backdrop_path):
1473
+ fanart_path = os.path.join(show_root, "fanart.jpg")
1474
+ if not os.path.exists(fanart_path):
1475
+ try:
1476
+ with open(backdrop_path, "rb") as src, open(fanart_path, "wb") as dst:
1477
+ dst.write(src.read())
1478
+ except OSError:
1479
+ pass
1480
 
1481
  season_detail = None
1482
  season_number = int(entry.get("season") or 1)
 
1505
  )
1506
  with open(nfo_path, "w", encoding="utf-8") as f:
1507
  f.write(build_movie_nfo(detail))
1508
+ movie_nfo = os.path.join(target_dir, "movie.nfo")
1509
+ if not os.path.exists(movie_nfo):
1510
+ try:
1511
+ with open(nfo_path, "rb") as src, open(movie_nfo, "wb") as dst:
1512
+ dst.write(src.read())
1513
+ except OSError:
1514
+ pass
1515
  poster_url = _best_image("poster_path")
1516
+ movie_poster = os.path.join(target_dir, "poster.jpg")
1517
+ if poster_url and not os.path.exists(movie_poster):
1518
+ if await _download_image(session, _resize_tmdb_image(poster_url, "w780"), movie_poster):
1519
+ folder_path = os.path.join(target_dir, "folder.jpg")
1520
+ if not os.path.exists(folder_path):
1521
+ try:
1522
+ with open(movie_poster, "rb") as src, open(folder_path, "wb") as dst:
1523
+ dst.write(src.read())
1524
+ except OSError:
1525
+ pass
1526
  backdrop_url = _best_image("backdrop_path")
1527
+ movie_backdrop = os.path.join(target_dir, "backdrop.jpg")
1528
+ if backdrop_url and not os.path.exists(movie_backdrop):
1529
+ if await _download_image(session, _resize_tmdb_image(backdrop_url, "original"), movie_backdrop):
1530
+ fanart_path = os.path.join(target_dir, "fanart.jpg")
1531
+ if not os.path.exists(fanart_path):
1532
+ try:
1533
+ with open(movie_backdrop, "rb") as src, open(fanart_path, "wb") as dst:
1534
+ dst.write(src.read())
1535
+ except OSError:
1536
+ pass
1537
 
1538
  prev = FILE_ID_CACHE.get(key)
1539
  if isinstance(prev, dict):
 
1594
  return None
1595
 
1596
 
1597
+ def _resize_tmdb_image(url, size="w780"):
1598
+ """Rewrite a TMDB image URL to requested size (or original)."""
1599
+ if not url or "image.tmdb.org/" not in url:
1600
+ return url
1601
+ if size == "original":
1602
  return url
1603
  return url.replace("/original", f"/{size}", 1)
1604