Skip to content

Commit 9744617

Browse files
committed
Refactor route imports and enhance lazy loading for optional integrations to avoid CPU spikes on startup
- Updated route imports in `main.py` to use specific modules for better organization and lazy loading. - Implemented lazy imports for `acestream` and `telegram` functionalities to avoid unnecessary loading when not enabled. - Refactored the router initialization in `__init__.py` to support lazy loading of routes, improving startup performance. - Enhanced the `acestream` and `telegram` routes to conditionally include their respective routers based on configuration settings. - Improved the handling of transcode components across various routes to optimize resource usage.
1 parent 02932f2 commit 9744617

7 files changed

Lines changed: 172 additions & 75 deletions

File tree

mediaflow_proxy/main.py

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,16 @@
1212

1313
from mediaflow_proxy.configs import settings
1414
from mediaflow_proxy.middleware import UIAccessControlMiddleware
15-
from mediaflow_proxy.routes import (
16-
proxy_router,
17-
extractor_router,
18-
speedtest_router,
19-
playlist_builder_router,
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
2420
from mediaflow_proxy.schemas import GenerateUrlRequest, GenerateMultiUrlRequest, MultiUrlRequestItem
2521
from mediaflow_proxy.utils.crypto_utils import EncryptionHandler, EncryptionMiddleware
2622
from mediaflow_proxy.utils import redis_utils
2723
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
2824
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
3225

3326
logging.basicConfig(level=settings.log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
3427
logger = logging.getLogger(__name__)
@@ -61,30 +54,22 @@ async def lifespan(app: FastAPI):
6154
# use redis-cli KEYS "mfp:*" | xargs redis-cli DEL
6255
logger.info("Cache clearing note: Redis entries will expire via TTL")
6356

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-
7857
yield
7958

8059
# Shutdown
8160
logger.info("Shutting down...")
8261
# Close acestream sessions
83-
await acestream_manager.close()
84-
logger.info("Acestream manager closed")
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")
8567
# Close telegram session
86-
await telegram_manager.close()
87-
logger.info("Telegram manager closed")
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")
8873
# Close Redis connections
8974
await redis_utils.close_redis()
9075
logger.info("Redis connections closed")
@@ -318,8 +303,14 @@ async def check_base64_url(url: str):
318303

319304

320305
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"], dependencies=[Depends(verify_api_key)])
321-
app.include_router(acestream_router, prefix="/proxy", tags=["acestream"], dependencies=[Depends(verify_api_key)])
322-
app.include_router(telegram_router, prefix="/proxy", tags=["telegram"], dependencies=[Depends(verify_api_key)])
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)])
323314
app.include_router(extractor_router, prefix="/extractor", tags=["extractors"], dependencies=[Depends(verify_api_key)])
324315
app.include_router(speedtest_router, prefix="/speedtest", tags=["speedtest"], dependencies=[Depends(verify_api_key)])
325316
app.include_router(playlist_builder_router, prefix="/playlist", tags=["playlist"])

mediaflow_proxy/remuxer/media_source.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from urllib.parse import urlparse, unquote
1414

1515
from mediaflow_proxy.utils.http_client import create_aiohttp_session
16-
from mediaflow_proxy.utils.telegram import telegram_manager
1716

1817
logger = logging.getLogger(__name__)
1918

@@ -142,6 +141,9 @@ def filename_hint(self) -> str:
142141
return self._filename_hint
143142

144143
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+
145147
effective_limit = limit or self._file_size
146148
if self._use_single_client:
147149
async for chunk in telegram_manager.stream_media_single(

mediaflow_proxy/routes/__init__.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
from .proxy import proxy_router
2-
from .extractor import extractor_router
3-
from .speedtest import speedtest_router
4-
from .playlist_builder import playlist_builder_router
5-
from .xtream import xtream_root_router
6-
from .acestream import acestream_router
7-
from .telegram import telegram_router
8-
91
__all__ = [
102
"proxy_router",
113
"extractor_router",
@@ -15,3 +7,37 @@
157
"acestream_router",
168
"telegram_router",
179
]
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}")

mediaflow_proxy/routes/acestream.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,15 @@
99

1010
import asyncio
1111
import logging
12-
from typing import Annotated
12+
from functools import lru_cache
13+
from typing import Annotated, TYPE_CHECKING
1314
from urllib.parse import urlencode, urljoin, urlparse
1415

1516
import aiohttp
1617
from fastapi import APIRouter, Query, Request, HTTPException, Response, Depends
1718
from starlette.background import BackgroundTask
1819

1920
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
2221
from mediaflow_proxy.utils.http_client import create_aiohttp_session
2322
from mediaflow_proxy.utils.http_utils import (
2423
get_original_scheme,
@@ -34,6 +33,22 @@
3433
logger = logging.getLogger(__name__)
3534
acestream_router = APIRouter()
3635

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+
3752

3853
class AcestreamM3U8Processor(M3U8Processor):
3954
"""
@@ -46,7 +61,7 @@ class AcestreamM3U8Processor(M3U8Processor):
4661
def __init__(
4762
self,
4863
request: Request,
49-
session: AcestreamSession,
64+
session: "AcestreamSession",
5065
key_url: str = None,
5166
force_playlist_proxy: bool = True,
5267
key_only_proxy: bool = False,
@@ -140,6 +155,7 @@ async def acestream_hls_manifest(
140155
"""
141156
if not settings.enable_acestream:
142157
raise HTTPException(status_code=503, detail="Acestream support is disabled")
158+
acestream_manager = _get_acestream_manager()
143159

144160
if not infohash and not id:
145161
raise HTTPException(status_code=400, detail="Either 'infohash' or 'id' parameter is required")
@@ -278,6 +294,7 @@ async def acestream_segment_proxy(
278294
"""
279295
if not settings.enable_acestream:
280296
raise HTTPException(status_code=503, detail="Acestream support is disabled")
297+
acestream_manager = _get_acestream_manager()
281298

282299
# Use id or infohash for session lookup
283300
session_key = id or infohash
@@ -368,6 +385,7 @@ async def acestream_ts_stream(
368385
"""
369386
if not settings.enable_acestream:
370387
raise HTTPException(status_code=503, detail="Acestream support is disabled")
388+
acestream_manager = _get_acestream_manager()
371389

372390
if not infohash and not id:
373391
raise HTTPException(status_code=400, detail="Either 'infohash' or 'id' parameter is required")
@@ -438,6 +456,7 @@ async def _acestream_ts_source():
438456
# Use our custom PyAV pipeline with forced video re-encoding
439457
# (live MPEG-TS sources often have corrupt H.264 bitstreams
440458
# that browsers reject; re-encoding produces a clean stream).
459+
stream_transcode_universal = _load_transcode_pipeline()
441460
content = stream_transcode_universal(
442461
_acestream_ts_source(),
443462
force_video_reencode=True,
@@ -509,6 +528,7 @@ async def acestream_status(
509528
"""
510529
if not settings.enable_acestream:
511530
raise HTTPException(status_code=503, detail="Acestream support is disabled")
531+
acestream_manager = _get_acestream_manager()
512532

513533
if infohash:
514534
session = acestream_manager.get_session(infohash)

mediaflow_proxy/routes/proxy.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import logging
33
import re
4+
from functools import lru_cache
45
from typing import Annotated
56
from urllib.parse import quote, unquote
67

@@ -41,19 +42,31 @@
4142
from mediaflow_proxy.utils.http_client import create_aiohttp_session
4243
from mediaflow_proxy.utils.m3u8_processor import M3U8Processor
4344
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-
)
5145

5246

5347
logger = logging.getLogger(__name__)
5448
proxy_router = APIRouter()
5549

5650

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+
5770
def sanitize_url(url: str) -> str:
5871
"""
5972
Sanitize URL to fix common encoding issues and handle base64 encoded URLs.
@@ -499,6 +512,7 @@ async def transcode_hls_playlist(
499512
"""
500513
if not settings.enable_transcode:
501514
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
515+
HTTPMediaSource, _, _, handle_transcode_hls_playlist, _ = _load_transcode_components()
502516
destination = sanitize_url(destination)
503517
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
504518
await source.resolve_file_size()
@@ -540,6 +554,7 @@ async def transcode_hls_init(
540554
"""
541555
if not settings.enable_transcode:
542556
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
557+
HTTPMediaSource, _, handle_transcode_hls_init, _, _ = _load_transcode_components()
543558
destination = sanitize_url(destination)
544559
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
545560
await source.resolve_file_size()
@@ -572,6 +587,7 @@ async def transcode_hls_segment(
572587
"""
573588
if not settings.enable_transcode:
574589
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
590+
HTTPMediaSource, _, _, _, handle_transcode_hls_segment = _load_transcode_components()
575591
destination = sanitize_url(destination)
576592
source = HTTPMediaSource(url=destination, headers=dict(proxy_headers.request))
577593
await source.resolve_file_size()
@@ -693,6 +709,7 @@ async def proxy_stream_endpoint(
693709
if transcode:
694710
if not settings.enable_transcode:
695711
raise HTTPException(status_code=503, detail="Transcoding support is disabled")
712+
HTTPMediaSource, handle_transcode, _, _, _ = _load_transcode_components()
696713
transcode_headers = dict(proxy_headers.request)
697714
transcode_headers.pop("range", None)
698715
transcode_headers.pop("if-range", None)

0 commit comments

Comments
 (0)