mediaflow-proxy 2.4.3__tar.gz → 2.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/PKG-INFO +1 -1
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/configs.py +6 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/handlers.py +5 -3
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/main.py +23 -32
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/mpd_processor.py +87 -23
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/media_source.py +3 -1
- mediaflow_proxy-2.4.4/mediaflow_proxy/routes/__init__.py +43 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/acestream.py +24 -4
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/proxy.py +24 -7
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/telegram.py +40 -16
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/xtream.py +24 -7
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/base_prebuffer.py +4 -1
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/dash_prebuffer.py +24 -4
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/http_utils.py +4 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/mpd_utils.py +118 -18
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/pyproject.toml +1 -1
- mediaflow_proxy-2.4.3/mediaflow_proxy/routes/__init__.py +0 -17
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/.gitignore +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/LICENSE +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/README.md +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/const.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/drm/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/drm/decrypter.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/F16Px.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/base.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/dlhd.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/doodstream.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/factory.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/fastream.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/filelions.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/filemoon.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/gupload.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/livetv.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/lulustream.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/maxstream.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/mixdrop.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/okru.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/sportsonline.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/streamtape.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/streamwish.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/supervideo.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/turbovidplay.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/uqload.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/vavoo.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/vidmoly.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/vidoza.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/vixcloud.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/extractors/voe.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/middleware.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/audio_transcoder.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/codec_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/container_probe.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/ebml_parser.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/hls_manifest.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/mkv_demuxer.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/mp4_muxer.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/mp4_parser.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/pyav_demuxer.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/transcode_handler.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/transcode_pipeline.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/ts_muxer.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/remuxer/video_transcoder.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/extractor.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/playlist_builder.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/routes/speedtest.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/schemas.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/models.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/providers/all_debrid.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/providers/base.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/providers/real_debrid.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/speedtest/service.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/index.html +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/logo.png +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/playlist_builder.html +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/speedtest.html +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/speedtest.js +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/static/url_generator.html +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/__init__.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/acestream.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/aes.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/aesgcm.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/base64_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/cache_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/codec.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/compat.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/constanttime.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/crypto_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/cryptomath.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/deprecations.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/extractor_helpers.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/hls_prebuffer.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/hls_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/http_client.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/m3u8_processor.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/packed.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/python_aes.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/python_aesgcm.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/rate_limit_handlers.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/redis_utils.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/rijndael.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/stream_transformers.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/telegram.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/tlshashlib.py +0 -0
- {mediaflow_proxy-2.4.3 → mediaflow_proxy-2.4.4}/mediaflow_proxy/utils/tlshmac.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mediaflow-proxy
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.4
|
|
4
4
|
Summary: A high-performance proxy server for streaming media, supporting HTTP(S), HLS, and MPEG-DASH with real-time DRM decryption.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mhdzumair/mediaflow-proxy
|
|
6
6
|
Project-URL: Repository, https://github.com/mhdzumair/mediaflow-proxy
|
|
@@ -64,6 +64,12 @@ class Settings(BaseSettings):
|
|
|
64
64
|
dash_prebuffer_emergency_threshold: int = 90 # Emergency threshold percentage to trigger aggressive cache cleanup.
|
|
65
65
|
dash_prebuffer_inactivity_timeout: int = 60 # Seconds of inactivity before cleaning up stream state.
|
|
66
66
|
dash_segment_cache_ttl: int = 60 # TTL (seconds) for cached media segments; longer = better for slow playback.
|
|
67
|
+
dash_player_lock_timeout: float = 2.5 # Max wait (seconds) for player requests when a segment lock is busy.
|
|
68
|
+
dash_prebuffer_lock_timeout: float = 0.25 # Max wait (seconds) for background prebuffer lock acquisition.
|
|
69
|
+
dash_prefetch_max_concurrent: int = 1 # Max concurrent live DASH prefetch downloads to reduce lock contention.
|
|
70
|
+
dash_live_initial_media_prebuffer: bool = (
|
|
71
|
+
False # Whether manifest-time prebuffer should fetch live media segments (init segments are still prewarmed).
|
|
72
|
+
)
|
|
67
73
|
mpd_live_init_cache_ttl: int = 60 # TTL (seconds) for live init segment cache; 0 disables caching.
|
|
68
74
|
mpd_live_playlist_depth: int = 8 # Number of recent segments to expose per live playlist variant.
|
|
69
75
|
remux_to_ts: bool = False # Remux fMP4 segments to MPEG-TS for ExoPlayer/VLC compatibility.
|
|
@@ -877,10 +877,12 @@ async def get_segment(
|
|
|
877
877
|
# - Waiting for existing downloads (via asyncio.Event)
|
|
878
878
|
# - Starting new download if needed
|
|
879
879
|
# - Caching the result
|
|
880
|
-
#
|
|
881
|
-
#
|
|
880
|
+
# Player requests should get priority over background prebuffer activity.
|
|
881
|
+
# Use a configurable lock timeout to balance responsiveness and cache reuse.
|
|
882
882
|
if settings.enable_dash_prebuffer:
|
|
883
|
-
segment_content = await dash_prebuffer.get_or_download(
|
|
883
|
+
segment_content = await dash_prebuffer.get_or_download(
|
|
884
|
+
segment_url, proxy_headers.request, timeout=settings.dash_player_lock_timeout
|
|
885
|
+
)
|
|
884
886
|
else:
|
|
885
887
|
# Prebuffer disabled - check cache then download directly
|
|
886
888
|
segment_content = await get_cached_segment(segment_url)
|
|
@@ -12,23 +12,16 @@ from starlette.staticfiles import StaticFiles
|
|
|
12
12
|
|
|
13
13
|
from mediaflow_proxy.configs import settings
|
|
14
14
|
from mediaflow_proxy.middleware import UIAccessControlMiddleware
|
|
15
|
-
from mediaflow_proxy.routes import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
xtream_root_router,
|
|
21
|
-
acestream_router,
|
|
22
|
-
telegram_router,
|
|
23
|
-
)
|
|
15
|
+
from mediaflow_proxy.routes.proxy import proxy_router
|
|
16
|
+
from mediaflow_proxy.routes.extractor import extractor_router
|
|
17
|
+
from mediaflow_proxy.routes.speedtest import speedtest_router
|
|
18
|
+
from mediaflow_proxy.routes.playlist_builder import playlist_builder_router
|
|
19
|
+
from mediaflow_proxy.routes.xtream import xtream_root_router
|
|
24
20
|
from mediaflow_proxy.schemas import GenerateUrlRequest, GenerateMultiUrlRequest, MultiUrlRequestItem
|
|
25
21
|
from mediaflow_proxy.utils.crypto_utils import EncryptionHandler, EncryptionMiddleware
|
|
26
22
|
from mediaflow_proxy.utils import redis_utils
|
|
27
23
|
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
|
|
28
24
|
from mediaflow_proxy.utils.base64_utils import encode_url_to_base64, decode_base64_url, is_base64_url
|
|
29
|
-
from mediaflow_proxy.utils.acestream import acestream_manager
|
|
30
|
-
from mediaflow_proxy.remuxer.video_transcoder import get_hw_capability, HWAccelType
|
|
31
|
-
from mediaflow_proxy.utils.telegram import telegram_manager
|
|
32
25
|
|
|
33
26
|
logging.basicConfig(level=settings.log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
34
27
|
logger = logging.getLogger(__name__)
|
|
@@ -61,30 +54,22 @@ async def lifespan(app: FastAPI):
|
|
|
61
54
|
# use redis-cli KEYS "mfp:*" | xargs redis-cli DEL
|
|
62
55
|
logger.info("Cache clearing note: Redis entries will expire via TTL")
|
|
63
56
|
|
|
64
|
-
# Log transcoding capability
|
|
65
|
-
hw = get_hw_capability()
|
|
66
|
-
if hw.accel_type != HWAccelType.NONE and settings.transcode_prefer_gpu:
|
|
67
|
-
logger.info(
|
|
68
|
-
"Transcode ready: GPU %s (encoder=%s) | PyAV pipeline",
|
|
69
|
-
hw.accel_type.value,
|
|
70
|
-
hw.h264_encoder,
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
logger.info(
|
|
74
|
-
"Transcode ready: CPU (%s) | PyAV pipeline",
|
|
75
|
-
hw.h264_encoder,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
57
|
yield
|
|
79
58
|
|
|
80
59
|
# Shutdown
|
|
81
60
|
logger.info("Shutting down...")
|
|
82
61
|
# Close acestream sessions
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
if settings.enable_acestream:
|
|
63
|
+
from mediaflow_proxy.utils.acestream import acestream_manager
|
|
64
|
+
|
|
65
|
+
await acestream_manager.close()
|
|
66
|
+
logger.info("Acestream manager closed")
|
|
85
67
|
# Close telegram session
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
if settings.enable_telegram:
|
|
69
|
+
from mediaflow_proxy.utils.telegram import telegram_manager
|
|
70
|
+
|
|
71
|
+
await telegram_manager.close()
|
|
72
|
+
logger.info("Telegram manager closed")
|
|
88
73
|
# Close Redis connections
|
|
89
74
|
await redis_utils.close_redis()
|
|
90
75
|
logger.info("Redis connections closed")
|
|
@@ -318,8 +303,14 @@ async def check_base64_url(url: str):
|
|
|
318
303
|
|
|
319
304
|
|
|
320
305
|
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"], dependencies=[Depends(verify_api_key)])
|
|
321
|
-
|
|
322
|
-
|
|
306
|
+
if settings.enable_acestream:
|
|
307
|
+
from mediaflow_proxy.routes.acestream import acestream_router
|
|
308
|
+
|
|
309
|
+
app.include_router(acestream_router, prefix="/proxy", tags=["acestream"], dependencies=[Depends(verify_api_key)])
|
|
310
|
+
if settings.enable_telegram:
|
|
311
|
+
from mediaflow_proxy.routes.telegram import telegram_router
|
|
312
|
+
|
|
313
|
+
app.include_router(telegram_router, prefix="/proxy", tags=["telegram"], dependencies=[Depends(verify_api_key)])
|
|
323
314
|
app.include_router(extractor_router, prefix="/extractor", tags=["extractors"], dependencies=[Depends(verify_api_key)])
|
|
324
315
|
app.include_router(speedtest_router, prefix="/speedtest", tags=["speedtest"], dependencies=[Depends(verify_api_key)])
|
|
325
316
|
app.include_router(playlist_builder_router, prefix="/playlist", tags=["playlist"])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
import math
|
|
4
|
+
import statistics
|
|
4
5
|
import time
|
|
5
6
|
|
|
6
7
|
from fastapi import Request, Response, HTTPException
|
|
@@ -30,6 +31,83 @@ def _resolve_ts_mode(request: Request) -> bool:
|
|
|
30
31
|
return settings.remux_to_ts
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
def _resolve_nominal_duration_mpd_timescale(profile: dict, segments: list[dict]) -> int | None:
|
|
35
|
+
"""Resolve a stable nominal segment duration (MPD timescale units) for live sequence math."""
|
|
36
|
+
profile_duration = profile.get("nominal_duration_mpd_timescale")
|
|
37
|
+
if isinstance(profile_duration, (int, float)) and profile_duration > 0:
|
|
38
|
+
return int(profile_duration)
|
|
39
|
+
|
|
40
|
+
durations = []
|
|
41
|
+
for seg in segments:
|
|
42
|
+
seg_duration = seg.get("duration_mpd_timescale")
|
|
43
|
+
if isinstance(seg_duration, (int, float)) and seg_duration > 0:
|
|
44
|
+
durations.append(int(seg_duration))
|
|
45
|
+
|
|
46
|
+
if durations:
|
|
47
|
+
# Use median to avoid jumps when first/last live segments are shorter.
|
|
48
|
+
return int(statistics.median_low(durations))
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _compute_live_media_sequence(first_segment: dict, profile: dict, segments: list[dict]) -> int:
|
|
53
|
+
"""
|
|
54
|
+
Compute a stable HLS media sequence for live playlists.
|
|
55
|
+
|
|
56
|
+
Strategy:
|
|
57
|
+
1) If MPD explicitly sets @startNumber, trust segment numbering.
|
|
58
|
+
2) Otherwise derive sequence from timeline time / nominal duration.
|
|
59
|
+
3) Fall back to segment number or template start number.
|
|
60
|
+
"""
|
|
61
|
+
segment_number = first_segment.get("number")
|
|
62
|
+
if profile.get("segment_template_start_number_explicit") and segment_number is not None:
|
|
63
|
+
return max(int(segment_number), 1)
|
|
64
|
+
|
|
65
|
+
timeline_time = first_segment.get("time")
|
|
66
|
+
nominal_duration = _resolve_nominal_duration_mpd_timescale(profile, segments)
|
|
67
|
+
if timeline_time is not None and nominal_duration and nominal_duration > 0:
|
|
68
|
+
return max(math.floor(int(timeline_time) / nominal_duration), 1)
|
|
69
|
+
|
|
70
|
+
if segment_number is not None:
|
|
71
|
+
return max(int(segment_number), 1)
|
|
72
|
+
|
|
73
|
+
template_start = profile.get("segment_template_start_number")
|
|
74
|
+
if isinstance(template_start, int) and template_start > 0:
|
|
75
|
+
return template_start
|
|
76
|
+
|
|
77
|
+
return 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _compute_live_playlist_depth(
|
|
81
|
+
is_ts_mode: bool,
|
|
82
|
+
effective_start_offset: float | None,
|
|
83
|
+
extinf_values: list[float],
|
|
84
|
+
) -> int:
|
|
85
|
+
"""
|
|
86
|
+
Compute a resilient live playlist depth to reduce segment expiry skips.
|
|
87
|
+
|
|
88
|
+
We keep a larger floor for fMP4 live (direct mode), and further expand
|
|
89
|
+
depth based on requested start_offset so players have enough headroom
|
|
90
|
+
during transient stalls.
|
|
91
|
+
"""
|
|
92
|
+
configured_depth = max(settings.mpd_live_playlist_depth, 1)
|
|
93
|
+
depth_floor = 20 if is_ts_mode else 15
|
|
94
|
+
depth = max(configured_depth, depth_floor)
|
|
95
|
+
|
|
96
|
+
if effective_start_offset is not None and effective_start_offset < 0:
|
|
97
|
+
if extinf_values:
|
|
98
|
+
segment_duration = statistics.median(extinf_values)
|
|
99
|
+
if segment_duration <= 0:
|
|
100
|
+
segment_duration = 4.0
|
|
101
|
+
else:
|
|
102
|
+
segment_duration = 4.0
|
|
103
|
+
|
|
104
|
+
segments_behind_live_edge = math.ceil(abs(effective_start_offset) / segment_duration)
|
|
105
|
+
safety_margin = 10 if is_ts_mode else 12
|
|
106
|
+
depth = max(depth, segments_behind_live_edge + safety_margin)
|
|
107
|
+
|
|
108
|
+
return max(depth, 1)
|
|
109
|
+
|
|
110
|
+
|
|
33
111
|
async def process_manifest(
|
|
34
112
|
request: Request,
|
|
35
113
|
mpd_dict: dict,
|
|
@@ -462,8 +540,8 @@ def build_hls_playlist(
|
|
|
462
540
|
continue
|
|
463
541
|
|
|
464
542
|
if is_live:
|
|
465
|
-
|
|
466
|
-
depth =
|
|
543
|
+
extinf_values_for_depth = [s["extinf"] for s in segments if "extinf" in s]
|
|
544
|
+
depth = _compute_live_playlist_depth(is_ts_mode, effective_start_offset, extinf_values_for_depth)
|
|
467
545
|
trimmed_segments = segments[-depth:]
|
|
468
546
|
else:
|
|
469
547
|
trimmed_segments = segments
|
|
@@ -479,31 +557,13 @@ def build_hls_playlist(
|
|
|
479
557
|
else:
|
|
480
558
|
target_duration = math.ceil(max(extinf_values)) if extinf_values else 3
|
|
481
559
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
# For live TS, derive sequence from timeline first for stable continuity
|
|
485
|
-
time_val = first_segment.get("time")
|
|
486
|
-
duration_val = first_segment.get("duration_mpd_timescale")
|
|
487
|
-
if time_val is not None and duration_val and duration_val > 0:
|
|
488
|
-
sequence = math.floor(time_val / duration_val)
|
|
489
|
-
else:
|
|
490
|
-
sequence = first_segment.get("number") or profile.get("segment_template_start_number") or 1
|
|
560
|
+
if is_live:
|
|
561
|
+
sequence = _compute_live_media_sequence(first_segment, profile, trimmed_segments)
|
|
491
562
|
else:
|
|
492
563
|
mpd_start_number = profile.get("segment_template_start_number")
|
|
493
564
|
sequence = first_segment.get("number")
|
|
494
|
-
|
|
495
565
|
if sequence is None:
|
|
496
|
-
|
|
497
|
-
if mpd_start_number is not None:
|
|
498
|
-
sequence = mpd_start_number
|
|
499
|
-
else:
|
|
500
|
-
# As a last resort, derive from timeline information
|
|
501
|
-
time_val = first_segment.get("time")
|
|
502
|
-
duration_val = first_segment.get("duration_mpd_timescale")
|
|
503
|
-
if time_val is not None and duration_val and duration_val > 0:
|
|
504
|
-
sequence = math.floor(time_val / duration_val)
|
|
505
|
-
else:
|
|
506
|
-
sequence = 1
|
|
566
|
+
sequence = mpd_start_number if mpd_start_number is not None else 1
|
|
507
567
|
|
|
508
568
|
hls.extend(
|
|
509
569
|
[
|
|
@@ -543,6 +603,10 @@ def build_hls_playlist(
|
|
|
543
603
|
if query_params.get("api_password"):
|
|
544
604
|
init_query_params["api_password"] = query_params["api_password"]
|
|
545
605
|
|
|
606
|
+
for k, v in query_params.items():
|
|
607
|
+
if k.startswith("rp_") or k.startswith("h_"):
|
|
608
|
+
init_query_params[k] = v
|
|
609
|
+
|
|
546
610
|
init_map_url = encode_mediaflow_proxy_url(
|
|
547
611
|
init_proxy_url,
|
|
548
612
|
query_params=init_query_params,
|
|
@@ -13,7 +13,6 @@ from typing import Protocol, runtime_checkable
|
|
|
13
13
|
from urllib.parse import urlparse, unquote
|
|
14
14
|
|
|
15
15
|
from mediaflow_proxy.utils.http_client import create_aiohttp_session
|
|
16
|
-
from mediaflow_proxy.utils.telegram import telegram_manager
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
19
18
|
|
|
@@ -142,6 +141,9 @@ class TelegramMediaSource:
|
|
|
142
141
|
return self._filename_hint
|
|
143
142
|
|
|
144
143
|
async def stream(self, offset: int = 0, limit: int | None = None) -> AsyncIterator[bytes]:
|
|
144
|
+
# Lazy import to avoid loading Telegram dependencies for non-Telegram routes.
|
|
145
|
+
from mediaflow_proxy.utils.telegram import telegram_manager
|
|
146
|
+
|
|
145
147
|
effective_limit = limit or self._file_size
|
|
146
148
|
if self._use_single_client:
|
|
147
149
|
async for chunk in telegram_manager.stream_media_single(
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"proxy_router",
|
|
3
|
+
"extractor_router",
|
|
4
|
+
"speedtest_router",
|
|
5
|
+
"playlist_builder_router",
|
|
6
|
+
"xtream_root_router",
|
|
7
|
+
"acestream_router",
|
|
8
|
+
"telegram_router",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def __getattr__(name: str):
|
|
13
|
+
# Lazy import routers so importing a single route module does not
|
|
14
|
+
# pull in optional integrations (telegram/acestream/transcode) at startup.
|
|
15
|
+
if name == "proxy_router":
|
|
16
|
+
from .proxy import proxy_router
|
|
17
|
+
|
|
18
|
+
return proxy_router
|
|
19
|
+
if name == "extractor_router":
|
|
20
|
+
from .extractor import extractor_router
|
|
21
|
+
|
|
22
|
+
return extractor_router
|
|
23
|
+
if name == "speedtest_router":
|
|
24
|
+
from .speedtest import speedtest_router
|
|
25
|
+
|
|
26
|
+
return speedtest_router
|
|
27
|
+
if name == "playlist_builder_router":
|
|
28
|
+
from .playlist_builder import playlist_builder_router
|
|
29
|
+
|
|
30
|
+
return playlist_builder_router
|
|
31
|
+
if name == "xtream_root_router":
|
|
32
|
+
from .xtream import xtream_root_router
|
|
33
|
+
|
|
34
|
+
return xtream_root_router
|
|
35
|
+
if name == "acestream_router":
|
|
36
|
+
from .acestream import acestream_router
|
|
37
|
+
|
|
38
|
+
return acestream_router
|
|
39
|
+
if name == "telegram_router":
|
|
40
|
+
from .telegram import telegram_router
|
|
41
|
+
|
|
42
|
+
return telegram_router
|
|
43
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -9,7 +9,8 @@ Provides endpoints for proxying acestream content:
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import logging
|
|
12
|
-
from
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
from typing import Annotated, TYPE_CHECKING
|
|
13
14
|
from urllib.parse import urlencode, urljoin, urlparse
|
|
14
15
|
|
|
15
16
|
import aiohttp
|
|
@@ -17,8 +18,6 @@ from fastapi import APIRouter, Query, Request, HTTPException, Response, Depends
|
|
|
17
18
|
from starlette.background import BackgroundTask
|
|
18
19
|
|
|
19
20
|
from mediaflow_proxy.configs import settings
|
|
20
|
-
from mediaflow_proxy.remuxer.transcode_pipeline import stream_transcode_universal
|
|
21
|
-
from mediaflow_proxy.utils.acestream import acestream_manager, AcestreamSession
|
|
22
21
|
from mediaflow_proxy.utils.http_client import create_aiohttp_session
|
|
23
22
|
from mediaflow_proxy.utils.http_utils import (
|
|
24
23
|
get_original_scheme,
|
|
@@ -34,6 +33,22 @@ from mediaflow_proxy.utils.hls_prebuffer import hls_prebuffer
|
|
|
34
33
|
logger = logging.getLogger(__name__)
|
|
35
34
|
acestream_router = APIRouter()
|
|
36
35
|
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from mediaflow_proxy.utils.acestream import AcestreamSession
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_acestream_manager():
|
|
41
|
+
from mediaflow_proxy.utils.acestream import acestream_manager
|
|
42
|
+
|
|
43
|
+
return acestream_manager
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@lru_cache(maxsize=1)
|
|
47
|
+
def _load_transcode_pipeline():
|
|
48
|
+
from mediaflow_proxy.remuxer.transcode_pipeline import stream_transcode_universal
|
|
49
|
+
|
|
50
|
+
return stream_transcode_universal
|
|
51
|
+
|
|
37
52
|
|
|
38
53
|
class AcestreamM3U8Processor(M3U8Processor):
|
|
39
54
|
"""
|
|
@@ -46,7 +61,7 @@ class AcestreamM3U8Processor(M3U8Processor):
|
|
|
46
61
|
def __init__(
|
|
47
62
|
self,
|
|
48
63
|
request: Request,
|
|
49
|
-
session: AcestreamSession,
|
|
64
|
+
session: "AcestreamSession",
|
|
50
65
|
key_url: str = None,
|
|
51
66
|
force_playlist_proxy: bool = True,
|
|
52
67
|
key_only_proxy: bool = False,
|
|
@@ -140,6 +155,7 @@ async def acestream_hls_manifest(
|
|
|
140
155
|
"""
|
|
141
156
|
if not settings.enable_acestream:
|
|
142
157
|
raise HTTPException(status_code=503, detail="Acestream support is disabled")
|
|
158
|
+
acestream_manager = _get_acestream_manager()
|
|
143
159
|
|
|
144
160
|
if not infohash and not id:
|
|
145
161
|
raise HTTPException(status_code=400, detail="Either 'infohash' or 'id' parameter is required")
|
|
@@ -278,6 +294,7 @@ async def acestream_segment_proxy(
|
|
|
278
294
|
"""
|
|
279
295
|
if not settings.enable_acestream:
|
|
280
296
|
raise HTTPException(status_code=503, detail="Acestream support is disabled")
|
|
297
|
+
acestream_manager = _get_acestream_manager()
|
|
281
298
|
|
|
282
299
|
# Use id or infohash for session lookup
|
|
283
300
|
session_key = id or infohash
|
|
@@ -368,6 +385,7 @@ async def acestream_ts_stream(
|
|
|
368
385
|
"""
|
|
369
386
|
if not settings.enable_acestream:
|
|
370
387
|
raise HTTPException(status_code=503, detail="Acestream support is disabled")
|
|
388
|
+
acestream_manager = _get_acestream_manager()
|
|
371
389
|
|
|
372
390
|
if not infohash and not id:
|
|
373
391
|
raise HTTPException(status_code=400, detail="Either 'infohash' or 'id' parameter is required")
|
|
@@ -438,6 +456,7 @@ async def acestream_ts_stream(
|
|
|
438
456
|
# Use our custom PyAV pipeline with forced video re-encoding
|
|
439
457
|
# (live MPEG-TS sources often have corrupt H.264 bitstreams
|
|
440
458
|
# that browsers reject; re-encoding produces a clean stream).
|
|
459
|
+
stream_transcode_universal = _load_transcode_pipeline()
|
|
441
460
|
content = stream_transcode_universal(
|
|
442
461
|
_acestream_ts_source(),
|
|
443
462
|
force_video_reencode=True,
|
|
@@ -509,6 +528,7 @@ async def acestream_status(
|
|
|
509
528
|
"""
|
|
510
529
|
if not settings.enable_acestream:
|
|
511
530
|
raise HTTPException(status_code=503, detail="Acestream support is disabled")
|
|
531
|
+
acestream_manager = _get_acestream_manager()
|
|
512
532
|
|
|
513
533
|
if infohash:
|
|
514
534
|
session = acestream_manager.get_session(infohash)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
|
+
from functools import lru_cache
|
|
4
5
|
from typing import Annotated
|
|
5
6
|
from urllib.parse import quote, unquote
|
|
6
7
|
|
|
@@ -41,19 +42,31 @@ from mediaflow_proxy.utils.http_utils import (
|
|
|
41
42
|
from mediaflow_proxy.utils.http_client import create_aiohttp_session
|
|
42
43
|
from mediaflow_proxy.utils.m3u8_processor import M3U8Processor
|
|
43
44
|
from mediaflow_proxy.utils.stream_transformers import apply_transformer_to_bytes
|
|
44
|
-
from mediaflow_proxy.remuxer.media_source import HTTPMediaSource
|
|
45
|
-
from mediaflow_proxy.remuxer.transcode_handler import (
|
|
46
|
-
handle_transcode,
|
|
47
|
-
handle_transcode_hls_init,
|
|
48
|
-
handle_transcode_hls_playlist,
|
|
49
|
-
handle_transcode_hls_segment,
|
|
50
|
-
)
|
|
51
45
|
|
|
52
46
|
|
|
53
47
|
logger = logging.getLogger(__name__)
|
|
54
48
|
proxy_router = APIRouter()
|
|
55
49
|
|
|
56
50
|
|
|
51
|
+
@lru_cache(maxsize=1)
|
|
52
|
+
def _load_transcode_components():
|
|
53
|
+
from mediaflow_proxy.remuxer.media_source import HTTPMediaSource
|
|
54
|
+
from mediaflow_proxy.remuxer.transcode_handler import (
|
|
55
|
+
handle_transcode,
|
|
56
|
+
handle_transcode_hls_init,
|
|
57
|
+
handle_transcode_hls_playlist,
|
|
58
|
+
handle_transcode_hls_segment,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
HTTPMediaSource,
|
|
63
|
+
handle_transcode,
|
|
64
|
+
handle_transcode_hls_init,
|
|
65
|
+
handle_transcode_hls_playlist,
|
|
66
|
+
handle_transcode_hls_segment,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
57
70
|
def sanitize_url(url: str) -> str:
|
|
58
71
|
"""
|
|
59
72
|
Sanitize URL to fix common encoding issues and handle base64 encoded URLs.
|
|
@@ -499,6 +512,7 @@ async def transcode_hls_playlist(
|
|
|
499
512
|
"""
|
|
500
513
|
if not settings.enable_transcode:
|
|
501
514
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
515
|
+
HTTPMediaSource, _, _, handle_transcode_hls_playlist, _ = _load_transcode_components()
|
|
502
516
|
destination = sanitize_url(destination)
|
|
503
517
|
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
|
|
504
518
|
await source.resolve_file_size()
|
|
@@ -540,6 +554,7 @@ async def transcode_hls_init(
|
|
|
540
554
|
"""
|
|
541
555
|
if not settings.enable_transcode:
|
|
542
556
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
557
|
+
HTTPMediaSource, _, handle_transcode_hls_init, _, _ = _load_transcode_components()
|
|
543
558
|
destination = sanitize_url(destination)
|
|
544
559
|
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
|
|
545
560
|
await source.resolve_file_size()
|
|
@@ -572,6 +587,7 @@ async def transcode_hls_segment(
|
|
|
572
587
|
"""
|
|
573
588
|
if not settings.enable_transcode:
|
|
574
589
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
590
|
+
HTTPMediaSource, _, _, _, handle_transcode_hls_segment = _load_transcode_components()
|
|
575
591
|
destination = sanitize_url(destination)
|
|
576
592
|
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
|
|
577
593
|
await source.resolve_file_size()
|
|
@@ -693,6 +709,7 @@ async def proxy_stream_endpoint(
|
|
|
693
709
|
if transcode:
|
|
694
710
|
if not settings.enable_transcode:
|
|
695
711
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
712
|
+
HTTPMediaSource, handle_transcode, _, _, _ = _load_transcode_components()
|
|
696
713
|
transcode_headers = dict(proxy_headers.request)
|
|
697
714
|
transcode_headers.pop("range", None)
|
|
698
715
|
transcode_headers.pop("if-range", None)
|
|
@@ -12,37 +12,50 @@ import asyncio
|
|
|
12
12
|
import logging
|
|
13
13
|
import re
|
|
14
14
|
import secrets
|
|
15
|
-
from
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from typing import Annotated, Optional, TYPE_CHECKING
|
|
16
17
|
|
|
17
18
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
|
|
18
19
|
from pydantic import BaseModel
|
|
19
20
|
|
|
20
|
-
from telethon import TelegramClient
|
|
21
|
-
from telethon.sessions import StringSession
|
|
22
|
-
|
|
23
21
|
from mediaflow_proxy.configs import settings
|
|
24
22
|
from mediaflow_proxy.remuxer.media_source import TelegramMediaSource
|
|
25
|
-
from mediaflow_proxy.remuxer.transcode_handler import (
|
|
26
|
-
handle_transcode,
|
|
27
|
-
handle_transcode_hls_init,
|
|
28
|
-
handle_transcode_hls_playlist,
|
|
29
|
-
handle_transcode_hls_segment,
|
|
30
|
-
)
|
|
31
23
|
from mediaflow_proxy.utils.http_utils import (
|
|
32
24
|
EnhancedStreamingResponse,
|
|
33
25
|
ProxyRequestHeaders,
|
|
34
26
|
apply_header_manipulation,
|
|
35
27
|
get_proxy_headers,
|
|
36
28
|
)
|
|
37
|
-
from mediaflow_proxy.utils.telegram import (
|
|
38
|
-
TelegramMediaRef,
|
|
39
|
-
parse_telegram_url,
|
|
40
|
-
telegram_manager,
|
|
41
|
-
)
|
|
42
29
|
|
|
43
30
|
logger = logging.getLogger(__name__)
|
|
44
31
|
telegram_router = APIRouter()
|
|
45
32
|
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from mediaflow_proxy.utils.telegram import TelegramMediaRef
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _telegram_utils():
|
|
38
|
+
from mediaflow_proxy.utils.telegram import TelegramMediaRef, parse_telegram_url, telegram_manager
|
|
39
|
+
|
|
40
|
+
return TelegramMediaRef, parse_telegram_url, telegram_manager
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@lru_cache(maxsize=1)
|
|
44
|
+
def _load_transcode_handlers():
|
|
45
|
+
from mediaflow_proxy.remuxer.transcode_handler import (
|
|
46
|
+
handle_transcode,
|
|
47
|
+
handle_transcode_hls_init,
|
|
48
|
+
handle_transcode_hls_playlist,
|
|
49
|
+
handle_transcode_hls_segment,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
handle_transcode,
|
|
54
|
+
handle_transcode_hls_init,
|
|
55
|
+
handle_transcode_hls_playlist,
|
|
56
|
+
handle_transcode_hls_segment,
|
|
57
|
+
)
|
|
58
|
+
|
|
46
59
|
|
|
47
60
|
def get_content_type(mime_type: str, file_name: Optional[str] = None) -> str:
|
|
48
61
|
"""Determine content type from mime type or filename."""
|
|
@@ -165,6 +178,7 @@ async def telegram_stream(
|
|
|
165
178
|
"""
|
|
166
179
|
if not settings.enable_telegram:
|
|
167
180
|
raise HTTPException(status_code=503, detail="Telegram proxy support is disabled")
|
|
181
|
+
TelegramMediaRef, parse_telegram_url, telegram_manager = _telegram_utils()
|
|
168
182
|
|
|
169
183
|
# Get the URL from either parameter
|
|
170
184
|
telegram_url = d or url
|
|
@@ -359,7 +373,7 @@ async def telegram_stream(
|
|
|
359
373
|
|
|
360
374
|
async def _handle_transcode(
|
|
361
375
|
request: Request,
|
|
362
|
-
ref: TelegramMediaRef,
|
|
376
|
+
ref: "TelegramMediaRef",
|
|
363
377
|
file_size: int,
|
|
364
378
|
start_time: float | None = None,
|
|
365
379
|
file_name: str = "",
|
|
@@ -371,6 +385,7 @@ async def _handle_transcode(
|
|
|
371
385
|
passes it to the source-agnostic transcode handler which handles
|
|
372
386
|
cue probing, seeking, and pipeline selection.
|
|
373
387
|
"""
|
|
388
|
+
handle_transcode, _, _, _ = _load_transcode_handlers()
|
|
374
389
|
source = TelegramMediaSource(ref, file_size, file_name=file_name)
|
|
375
390
|
return await handle_transcode(request, source, start_time=start_time)
|
|
376
391
|
|
|
@@ -406,6 +421,7 @@ async def _resolve_telegram_source(
|
|
|
406
421
|
from fastapi import HTTPException
|
|
407
422
|
|
|
408
423
|
raise HTTPException(status_code=503, detail="Telegram proxy support is disabled")
|
|
424
|
+
TelegramMediaRef, parse_telegram_url, telegram_manager = _telegram_utils()
|
|
409
425
|
|
|
410
426
|
telegram_url = d or url
|
|
411
427
|
|
|
@@ -462,6 +478,7 @@ async def telegram_transcode_hls_playlist(
|
|
|
462
478
|
"""Generate an HLS VOD M3U8 playlist for a Telegram media file."""
|
|
463
479
|
if not settings.enable_transcode:
|
|
464
480
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
481
|
+
_, _, handle_transcode_hls_playlist, _ = _load_transcode_handlers()
|
|
465
482
|
source = await _resolve_telegram_source(
|
|
466
483
|
d,
|
|
467
484
|
url,
|
|
@@ -504,6 +521,7 @@ async def telegram_transcode_hls_init(
|
|
|
504
521
|
"""Serve the fMP4 init segment for a Telegram media file."""
|
|
505
522
|
if not settings.enable_transcode:
|
|
506
523
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
524
|
+
_, handle_transcode_hls_init, _, _ = _load_transcode_handlers()
|
|
507
525
|
source = await _resolve_telegram_source(
|
|
508
526
|
d,
|
|
509
527
|
url,
|
|
@@ -534,6 +552,7 @@ async def telegram_transcode_hls_segment(
|
|
|
534
552
|
"""Serve a single HLS fMP4 media segment for a Telegram media file."""
|
|
535
553
|
if not settings.enable_transcode:
|
|
536
554
|
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
|
|
555
|
+
_, _, _, handle_transcode_hls_segment = _load_transcode_handlers()
|
|
537
556
|
source = await _resolve_telegram_source(
|
|
538
557
|
d,
|
|
539
558
|
url,
|
|
@@ -628,6 +647,7 @@ async def telegram_info(
|
|
|
628
647
|
"""
|
|
629
648
|
if not settings.enable_telegram:
|
|
630
649
|
raise HTTPException(status_code=503, detail="Telegram proxy support is disabled")
|
|
650
|
+
TelegramMediaRef, parse_telegram_url, telegram_manager = _telegram_utils()
|
|
631
651
|
|
|
632
652
|
telegram_url = d or url
|
|
633
653
|
|
|
@@ -718,6 +738,7 @@ async def telegram_status():
|
|
|
718
738
|
}
|
|
719
739
|
|
|
720
740
|
# Check if client is connected
|
|
741
|
+
_, _, telegram_manager = _telegram_utils()
|
|
721
742
|
if telegram_manager.is_initialized:
|
|
722
743
|
return {
|
|
723
744
|
"enabled": True,
|
|
@@ -783,6 +804,9 @@ async def session_start(request: SessionStartRequest):
|
|
|
783
804
|
session_id = secrets.token_urlsafe(16)
|
|
784
805
|
|
|
785
806
|
try:
|
|
807
|
+
from telethon import TelegramClient
|
|
808
|
+
from telethon.sessions import StringSession
|
|
809
|
+
|
|
786
810
|
client = TelegramClient(StringSession(), request.api_id, request.api_hash)
|
|
787
811
|
await client.connect()
|
|
788
812
|
|