mirror of
https://github.com/Viren070/mediaflow-proxy.git
synced 2025-12-01 23:22:12 +01:00
Implement LiveTVExtractor and improve error handling
Added LiveTVExtractor for extracting M3U8 and MPD streams. Enhanced error handling by introducing ExtractorError and updated existing extractors to use this new error class. Removed support for unused request headers and integrated caching for extractor results.
This commit is contained in:
@@ -52,6 +52,7 @@ class TransportConfig(BaseSettings):
|
||||
|
||||
class Settings(BaseSettings):
|
||||
api_password: str # The password for accessing the API endpoints.
|
||||
log_level: str = "INFO" # The logging level to use.
|
||||
transport_config: TransportConfig = Field(default_factory=TransportConfig) # Configuration for httpx transport.
|
||||
enable_streaming_progress: bool = False # Whether to enable streaming progress tracking.
|
||||
|
||||
|
||||
@@ -12,13 +12,6 @@ SUPPORTED_RESPONSE_HEADERS = [
|
||||
]
|
||||
|
||||
SUPPORTED_REQUEST_HEADERS = [
|
||||
"accept",
|
||||
"accept-encoding",
|
||||
"accept-language",
|
||||
"connection",
|
||||
"range",
|
||||
"if-range",
|
||||
"user-agent",
|
||||
"referer",
|
||||
"origin",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Tuple, Optional
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
import httpx
|
||||
|
||||
@@ -7,23 +7,32 @@ from mediaflow_proxy.configs import settings
|
||||
from mediaflow_proxy.utils.http_utils import create_httpx_client
|
||||
|
||||
|
||||
class ExtractorError(Exception):
|
||||
"""Base exception for all extractors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BaseExtractor(ABC):
|
||||
"""Base class for all URL extractors."""
|
||||
|
||||
def __init__(self, request_headers: dict):
|
||||
self.base_headers = {
|
||||
"user-agent": settings.user_agent,
|
||||
"accept-language": "en-US,en;q=0.5",
|
||||
}
|
||||
self.mediaflow_endpoint = "proxy_stream_endpoint"
|
||||
self.base_headers.update(request_headers)
|
||||
|
||||
async def _make_request(self, url: str, headers: Optional[Dict] = None, **kwargs) -> httpx.Response:
|
||||
async def _make_request(
|
||||
self, url: str, method: str = "GET", headers: Optional[Dict] = None, **kwargs
|
||||
) -> httpx.Response:
|
||||
"""Make HTTP request with error handling."""
|
||||
try:
|
||||
async with create_httpx_client() as client:
|
||||
request_headers = self.base_headers
|
||||
request_headers.update(headers or {})
|
||||
response = await client.get(
|
||||
response = await client.request(
|
||||
method,
|
||||
url,
|
||||
headers=request_headers,
|
||||
**kwargs,
|
||||
@@ -31,11 +40,11 @@ class BaseExtractor(ABC):
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except httpx.HTTPError as e:
|
||||
raise ValueError(f"HTTP request failed: {str(e)}")
|
||||
raise ExtractorError(f"HTTP request failed: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Request failed: {str(e)}")
|
||||
raise ExtractorError(f"Request failed: {str(e)}")
|
||||
|
||||
@abstractmethod
|
||||
async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
|
||||
async def extract(self, url: str, **kwargs) -> Dict[str, Any]:
|
||||
"""Extract final URL and required headers."""
|
||||
pass
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import re
|
||||
import time
|
||||
from typing import Tuple, Dict
|
||||
from typing import Dict
|
||||
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
|
||||
|
||||
|
||||
class DoodStreamExtractor(BaseExtractor):
|
||||
"""DoodStream URL extractor."""
|
||||
|
||||
def __init__(self, proxy_enabled: bool, request_headers: dict):
|
||||
super().__init__(proxy_enabled, request_headers)
|
||||
def __init__(self, request_headers: dict):
|
||||
super().__init__(request_headers)
|
||||
self.base_url = "https://d000d.com"
|
||||
|
||||
async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
|
||||
async def extract(self, url: str, **kwargs) -> Dict[str, str]:
|
||||
"""Extract DoodStream URL."""
|
||||
response = await self._make_request(url)
|
||||
|
||||
@@ -20,15 +20,20 @@ class DoodStreamExtractor(BaseExtractor):
|
||||
pattern = r"(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)"
|
||||
match = re.search(pattern, response.text, re.DOTALL)
|
||||
if not match:
|
||||
raise ValueError("Failed to extract URL pattern")
|
||||
raise ExtractorError("Failed to extract URL pattern")
|
||||
|
||||
# Build final URL
|
||||
pass_url = f"{self.base_url}{match[1]}"
|
||||
referer = f"{self.base_url}/"
|
||||
headers = {"range": "bytes=0-", "referer": referer}
|
||||
|
||||
rebobo_response = await self._make_request(pass_url, headers=headers)
|
||||
response = await self._make_request(pass_url, headers=headers)
|
||||
timestamp = str(int(time.time()))
|
||||
final_url = f"{rebobo_response.text}123456789{match[2]}{timestamp}"
|
||||
final_url = f"{response.text}123456789{match[2]}{timestamp}"
|
||||
|
||||
return final_url, {"Referer": referer}
|
||||
self.base_headers["referer"] = referer
|
||||
return {
|
||||
"destination_url": final_url,
|
||||
"request_headers": self.base_headers,
|
||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from typing import Dict, Type
|
||||
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
|
||||
from mediaflow_proxy.extractors.doodstream import DoodStreamExtractor
|
||||
from mediaflow_proxy.extractors.livetv import LiveTVExtractor
|
||||
from mediaflow_proxy.extractors.mixdrop import MixdropExtractor
|
||||
from mediaflow_proxy.extractors.uqload import UqloadExtractor
|
||||
|
||||
@@ -13,6 +14,7 @@ class ExtractorFactory:
|
||||
"Doodstream": DoodStreamExtractor,
|
||||
"Uqload": UqloadExtractor,
|
||||
"Mixdrop": MixdropExtractor,
|
||||
"LiveTV": LiveTVExtractor,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -20,5 +22,5 @@ class ExtractorFactory:
|
||||
"""Get appropriate extractor instance for the given host."""
|
||||
extractor_class = cls._extractors.get(host)
|
||||
if not extractor_class:
|
||||
raise ValueError(f"Unsupported host: {host}")
|
||||
raise ExtractorError(f"Unsupported host: {host}")
|
||||
return extractor_class(request_headers)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
import re
|
||||
from typing import Dict, Tuple
|
||||
from urllib.parse import urljoin, urlparse, unquote
|
||||
|
||||
from httpx import Response
|
||||
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
|
||||
|
||||
|
||||
class LiveTVExtractor(BaseExtractor):
|
||||
"""LiveTV URL extractor for both M3U8 and MPD streams."""
|
||||
|
||||
def __init__(self, request_headers: dict):
|
||||
super().__init__(request_headers)
|
||||
# Default to HLS proxy endpoint, will be updated based on stream type
|
||||
self.mediaflow_endpoint = "hls_manifest_proxy"
|
||||
|
||||
# Patterns for stream URL extraction
|
||||
self.fallback_pattern = re.compile(
|
||||
r"source: [\'\"](.*?)[\'\"]\s*,\s*[\s\S]*?mimeType: [\'\"](application/x-mpegURL|application/vnd\.apple\.mpegURL|application/dash\+xml)[\'\"]",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
self.any_m3u8_pattern = re.compile(
|
||||
r'["\']?(https?://.*?\.m3u8(?:\?[^"\']*)?)["\']?',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
async def extract(self, url: str, stream_title: str = None, **kwargs) -> Dict[str, str]:
|
||||
"""Extract LiveTV URL and required headers.
|
||||
|
||||
Args:
|
||||
url: The channel page URL
|
||||
stream_title: Optional stream title to filter specific stream
|
||||
|
||||
Returns:
|
||||
Tuple[str, Dict[str, str]]: Stream URL and required headers
|
||||
"""
|
||||
try:
|
||||
# Get the channel page
|
||||
response = await self._make_request(url)
|
||||
self.base_headers["referer"] = urljoin(url, "/")
|
||||
|
||||
# Extract player API details
|
||||
player_api_base, method = await self._extract_player_api_base(response.text)
|
||||
if not player_api_base:
|
||||
raise ExtractorError("Failed to extract player API URL")
|
||||
|
||||
# Get player options
|
||||
options_data = await self._get_player_options(response.text)
|
||||
if not options_data:
|
||||
raise ExtractorError("No player options found")
|
||||
|
||||
# Process player options to find matching stream
|
||||
for option in options_data:
|
||||
current_title = option.get("title")
|
||||
if stream_title and current_title != stream_title:
|
||||
continue
|
||||
|
||||
# Get stream URL based on player option
|
||||
stream_data = await self._process_player_option(
|
||||
player_api_base, method, option.get("post"), option.get("nume"), option.get("type")
|
||||
)
|
||||
|
||||
if stream_data:
|
||||
stream_url = stream_data.get("url")
|
||||
if not stream_url:
|
||||
continue
|
||||
|
||||
response = {
|
||||
"destination_url": stream_url,
|
||||
"request_headers": self.base_headers,
|
||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||
}
|
||||
|
||||
# Set endpoint based on stream type
|
||||
if stream_data.get("type") == "mpd":
|
||||
if stream_data.get("drm_key_id") and stream_data.get("drm_key"):
|
||||
response.update(
|
||||
{
|
||||
"query_params": {
|
||||
"key_id": stream_data["drm_key_id"],
|
||||
"key": stream_data["drm_key"],
|
||||
},
|
||||
"mediaflow_endpoint": "mpd_manifest_proxy",
|
||||
}
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
raise ExtractorError("No valid stream found")
|
||||
|
||||
except Exception as e:
|
||||
raise ExtractorError(f"Extraction failed: {str(e)}")
|
||||
|
||||
async def _extract_player_api_base(self, html_content: str) -> Tuple[str | None, str | None]:
|
||||
"""Extract player API base URL and method."""
|
||||
admin_ajax_pattern = r'"player_api"\s*:\s*"([^"]+)".*?"play_method"\s*:\s*"([^"]+)"'
|
||||
match = re.search(admin_ajax_pattern, html_content)
|
||||
if not match:
|
||||
return None, None
|
||||
url = match.group(1).replace("\\/", "/")
|
||||
method = match.group(2)
|
||||
if method == "wp_json":
|
||||
return url, method
|
||||
url = urljoin(url, "/wp-admin/admin-ajax.php")
|
||||
return url, method
|
||||
|
||||
async def _get_player_options(self, html_content: str) -> list:
|
||||
"""Extract player options from HTML content."""
|
||||
pattern = r'<li[^>]*class=["\']dooplay_player_option["\'][^>]*data-type=["\']([^"\']*)["\'][^>]*data-post=["\']([^"\']*)["\'][^>]*data-nume=["\']([^"\']*)["\'][^>]*>.*?<span class=["\']title["\']>([^<]*)</span>'
|
||||
matches = re.finditer(pattern, html_content, re.DOTALL)
|
||||
return [
|
||||
{"type": match.group(1), "post": match.group(2), "nume": match.group(3), "title": match.group(4).strip()}
|
||||
for match in matches
|
||||
]
|
||||
|
||||
async def _process_player_option(self, api_base: str, method: str, post: str, nume: str, type_: str) -> Dict:
|
||||
"""Process player option to get stream URL."""
|
||||
if method == "wp_json":
|
||||
api_url = f"{api_base}{post}/{type_}/{nume}"
|
||||
response = await self._make_request(api_url)
|
||||
else:
|
||||
form_data = {"action": "doo_player_ajax", "post": post, "nume": nume, "type": type_}
|
||||
response = await self._make_request(api_base, method="POST", data=form_data)
|
||||
|
||||
# Get iframe URL from API response
|
||||
try:
|
||||
data = response.json()
|
||||
iframe_url = urljoin(api_base, data.get("embed_url", "").replace("\\/", "/"))
|
||||
|
||||
# Get stream URL from iframe
|
||||
iframe_response = await self._make_request(iframe_url)
|
||||
stream_data = await self._extract_stream_url(iframe_response, iframe_url)
|
||||
return stream_data
|
||||
|
||||
except Exception as e:
|
||||
raise ExtractorError(f"Failed to process player option: {str(e)}")
|
||||
|
||||
async def _extract_stream_url(self, iframe_response: Response, iframe_url: str) -> Dict:
|
||||
"""
|
||||
Extract final stream URL from iframe content.
|
||||
"""
|
||||
try:
|
||||
# Parse URL components
|
||||
parsed_url = urlparse(iframe_url)
|
||||
query_params = dict(param.split("=") for param in parsed_url.query.split("&") if "=" in param)
|
||||
|
||||
# Check if content is already a direct M3U8 stream
|
||||
content_types = ["application/x-mpegurl", "application/vnd.apple.mpegurl"]
|
||||
|
||||
if any(ext in iframe_response.headers["content-type"] for ext in content_types):
|
||||
return {"url": iframe_url, "type": "m3u8"}
|
||||
|
||||
stream_data = {}
|
||||
|
||||
# Check for source parameter in URL
|
||||
if "source" in query_params:
|
||||
stream_data = {
|
||||
"url": urljoin(iframe_url, unquote(query_params["source"])),
|
||||
"type": "m3u8",
|
||||
}
|
||||
|
||||
# Check for MPD stream with DRM
|
||||
elif "zy" in query_params and ".mpd``" in query_params["zy"]:
|
||||
data = query_params["zy"].split("``")
|
||||
url = data[0]
|
||||
key_id, key = data[1].split(":")
|
||||
stream_data = {"url": url, "type": "mpd", "drm_key_id": key_id, "drm_key": key}
|
||||
|
||||
# Check for tamilultra specific format
|
||||
elif "tamilultra" in iframe_url:
|
||||
stream_data = {"url": urljoin(iframe_url, parsed_url.query), "type": "m3u8"}
|
||||
|
||||
# Try pattern matching for stream URLs
|
||||
else:
|
||||
channel_id = query_params.get("id", [""])
|
||||
stream_url = None
|
||||
|
||||
html_content = iframe_response.text
|
||||
|
||||
if channel_id:
|
||||
# Try channel ID specific pattern
|
||||
pattern = rf'{re.escape(channel_id)}["\']:\s*{{\s*["\']?url["\']?\s*:\s*["\']([^"\']+)["\']'
|
||||
match = re.search(pattern, html_content)
|
||||
if match:
|
||||
stream_url = match.group(1)
|
||||
|
||||
# Try fallback patterns if channel ID pattern fails
|
||||
if not stream_url:
|
||||
for pattern in [self.fallback_pattern, self.any_m3u8_pattern]:
|
||||
match = pattern.search(html_content)
|
||||
if match:
|
||||
stream_url = match.group(1)
|
||||
break
|
||||
|
||||
if stream_url:
|
||||
stream_data = {"url": stream_url, "type": "m3u8"} # Default to m3u8, will be updated
|
||||
|
||||
# Check for MPD stream and extract DRM keys
|
||||
if stream_url.endswith(".mpd"):
|
||||
stream_data["type"] = "mpd"
|
||||
drm_data = await self._extract_drm_keys(html_content, channel_id)
|
||||
if drm_data:
|
||||
stream_data.update(drm_data)
|
||||
|
||||
# If no stream data found, raise error
|
||||
if not stream_data:
|
||||
raise ExtractorError("No valid stream URL found")
|
||||
|
||||
# Update stream type based on URL if not already set
|
||||
if stream_data.get("type") == "m3u8":
|
||||
if stream_data["url"].endswith(".mpd"):
|
||||
stream_data["type"] = "mpd"
|
||||
elif not any(ext in stream_data["url"] for ext in [".m3u8", ".m3u"]):
|
||||
stream_data["type"] = "m3u8" # Default to m3u8 if no extension found
|
||||
|
||||
return stream_data
|
||||
|
||||
except Exception as e:
|
||||
raise ExtractorError(f"Failed to extract stream URL: {str(e)}")
|
||||
|
||||
async def _extract_drm_keys(self, html_content: str, channel_id: str) -> Dict:
|
||||
"""
|
||||
Extract DRM keys for MPD streams.
|
||||
"""
|
||||
try:
|
||||
# Pattern for channel entry
|
||||
channel_pattern = rf'"{re.escape(channel_id)}":\s*{{[^}}]+}}'
|
||||
channel_match = re.search(channel_pattern, html_content)
|
||||
|
||||
if channel_match:
|
||||
channel_data = channel_match.group(0)
|
||||
|
||||
# Try clearkeys pattern first
|
||||
clearkey_pattern = r'["\']?clearkeys["\']?\s*:\s*{\s*["\'](.+?)["\']:\s*["\'](.+?)["\']'
|
||||
clearkey_match = re.search(clearkey_pattern, channel_data)
|
||||
|
||||
# Try k1/k2 pattern if clearkeys not found
|
||||
if not clearkey_match:
|
||||
k1k2_pattern = r'["\']?k1["\']?\s*:\s*["\'](.+?)["\'],\s*["\']?k2["\']?\s*:\s*["\'](.+?)["\']'
|
||||
k1k2_match = re.search(k1k2_pattern, channel_data)
|
||||
|
||||
if k1k2_match:
|
||||
return {"drm_key_id": k1k2_match.group(1), "drm_key": k1k2_match.group(2)}
|
||||
else:
|
||||
return {"drm_key_id": clearkey_match.group(1), "drm_key": clearkey_match.group(2)}
|
||||
|
||||
return {}
|
||||
|
||||
except Exception:
|
||||
return {}
|
||||
@@ -1,21 +1,21 @@
|
||||
import re
|
||||
import string
|
||||
from typing import Dict, Tuple
|
||||
from typing import Dict, Any
|
||||
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
|
||||
|
||||
|
||||
class MixdropExtractor(BaseExtractor):
|
||||
"""Mixdrop URL extractor."""
|
||||
|
||||
async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
|
||||
async def extract(self, url: str, **kwargs) -> Dict[str, Any]:
|
||||
"""Extract Mixdrop URL."""
|
||||
response = await self._make_request(url)
|
||||
response = await self._make_request(url, headers={"accept-language": "en-US,en;q=0.5"})
|
||||
|
||||
# Extract and decode URL
|
||||
match = re.search(r"\}\('(.+)',.+,'(.+)'\.split", response.text)
|
||||
match = re.search(r"}\('(.+)',.+,'(.+)'\.split", response.text)
|
||||
if not match:
|
||||
raise ValueError("Failed to extract URL components")
|
||||
raise ExtractorError("Failed to extract URL components")
|
||||
|
||||
s1, s2 = match.group(1, 2)
|
||||
schema = s1.split(";")[2][5:-1]
|
||||
@@ -28,4 +28,9 @@ class MixdropExtractor(BaseExtractor):
|
||||
# Construct final URL
|
||||
final_url = "https:" + "".join(char_map.get(c, c) for c in schema)
|
||||
|
||||
return final_url, {"User-Agent": self.base_headers["User-Agent"]}
|
||||
self.base_headers["referer"] = url
|
||||
return {
|
||||
"destination_url": final_url,
|
||||
"request_headers": self.base_headers,
|
||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import re
|
||||
from typing import Dict, Tuple
|
||||
from typing import Dict
|
||||
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor
|
||||
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
|
||||
|
||||
|
||||
class UqloadExtractor(BaseExtractor):
|
||||
"""Uqload URL extractor."""
|
||||
|
||||
async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
|
||||
referer = "https://uqload.to/"
|
||||
|
||||
async def extract(self, url: str, **kwargs) -> Dict[str, str]:
|
||||
"""Extract Uqload URL."""
|
||||
response = await self._make_request(url)
|
||||
|
||||
video_url_match = re.search(r'sources: \["(.*?)"\]', response.text)
|
||||
video_url_match = re.search(r'sources: \["(.*?)"]', response.text)
|
||||
if not video_url_match:
|
||||
raise ValueError("Failed to extract video URL")
|
||||
raise ExtractorError("Failed to extract video URL")
|
||||
|
||||
return video_url_match.group(1), {"Referer": "https://uqload.to/"}
|
||||
self.base_headers["referer"] = self.referer
|
||||
return {
|
||||
"destination_url": video_url_match.group(1),
|
||||
"request_headers": self.base_headers,
|
||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import httpx
|
||||
from fastapi import Request, Response, HTTPException
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from .configs import settings
|
||||
from .const import SUPPORTED_RESPONSE_HEADERS
|
||||
from .mpd_processor import process_manifest, process_playlist, process_segment
|
||||
from .schemas import HLSManifestParams, ProxyStreamParams, MPDManifestParams, MPDPlaylistParams, MPDSegmentParams
|
||||
|
||||
@@ -13,7 +13,7 @@ from mediaflow_proxy.schemas import GenerateUrlRequest
|
||||
from mediaflow_proxy.utils.crypto_utils import EncryptionHandler, EncryptionMiddleware
|
||||
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logging.basicConfig(level=settings.log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
app = FastAPI()
|
||||
api_password_query = APIKeyQuery(name="api_password", auto_error=False)
|
||||
api_password_header = APIKeyHeader(name="api_password", auto_error=False)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, HTTPException, Request, Depends
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from mediaflow_proxy.configs import settings
|
||||
from mediaflow_proxy.extractors.base import ExtractorError
|
||||
from mediaflow_proxy.extractors.factory import ExtractorFactory
|
||||
from mediaflow_proxy.schemas import ExtractorURLParams
|
||||
from mediaflow_proxy.utils.cache_utils import get_cached_extractor_result, set_cache_extractor_result
|
||||
from mediaflow_proxy.utils.http_utils import (
|
||||
encode_mediaflow_proxy_url,
|
||||
get_original_scheme,
|
||||
@@ -14,6 +16,7 @@ from mediaflow_proxy.utils.http_utils import (
|
||||
)
|
||||
|
||||
extractor_router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@extractor_router.get("/video")
|
||||
@@ -24,23 +27,32 @@ async def extract_url(
|
||||
):
|
||||
"""Extract clean links from various video hosting services."""
|
||||
try:
|
||||
extractor = ExtractorFactory.get_extractor(extractor_params.host, proxy_headers.request)
|
||||
final_url, headers = await extractor.extract(extractor_params.destination)
|
||||
cache_key = f"{extractor_params.host}_{extractor_params.model_dump_json()}"
|
||||
response = await get_cached_extractor_result(cache_key)
|
||||
if not response:
|
||||
extractor = ExtractorFactory.get_extractor(extractor_params.host, proxy_headers.request)
|
||||
response = await extractor.extract(extractor_params.destination, **extractor_params.extra_params)
|
||||
await set_cache_extractor_result(cache_key, response)
|
||||
|
||||
response["mediaflow_proxy_url"] = str(
|
||||
request.url_for(response.pop("mediaflow_endpoint")).replace(scheme=get_original_scheme(request))
|
||||
)
|
||||
|
||||
if extractor_params.redirect_stream:
|
||||
headers.update(proxy_headers.request)
|
||||
response["query_params"] = response.get("query_params", {})
|
||||
# Add API password to query params
|
||||
response["query_params"]["api_password"] = request.query_params.get("api_password")
|
||||
stream_url = encode_mediaflow_proxy_url(
|
||||
str(request.url_for("proxy_stream_endpoint").replace(scheme=get_original_scheme(request))),
|
||||
destination_url=final_url,
|
||||
query_params={"api_password": settings.api_password},
|
||||
request_headers=headers,
|
||||
**response,
|
||||
response_headers=proxy_headers.response,
|
||||
)
|
||||
return RedirectResponse(url=stream_url)
|
||||
return RedirectResponse(url=stream_url, status_code=302)
|
||||
|
||||
return {"url": final_url, "headers": headers}
|
||||
return response
|
||||
|
||||
except ValueError as e:
|
||||
except ExtractorError as e:
|
||||
logger.error(f"Extraction failed: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.exception(f"Extraction failed: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Extraction failed: {str(e)}")
|
||||
|
||||
@@ -24,7 +24,7 @@ proxy_router = APIRouter()
|
||||
|
||||
@proxy_router.head("/hls/manifest.m3u8")
|
||||
@proxy_router.get("/hls/manifest.m3u8")
|
||||
async def hls_stream_proxy(
|
||||
async def hls_manifest_proxy(
|
||||
request: Request,
|
||||
hls_params: Annotated[HLSManifestParams, Query()],
|
||||
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
|
||||
@@ -70,7 +70,7 @@ async def proxy_stream_endpoint(
|
||||
|
||||
|
||||
@proxy_router.get("/mpd/manifest.m3u8")
|
||||
async def manifest_endpoint(
|
||||
async def mpd_manifest_proxy(
|
||||
request: Request,
|
||||
manifest_params: Annotated[MPDManifestParams, Query()],
|
||||
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Literal
|
||||
from typing import Literal, Dict, Any
|
||||
|
||||
from pydantic import BaseModel, Field, IPvAnyAddress, ConfigDict
|
||||
|
||||
@@ -59,6 +59,12 @@ class MPDSegmentParams(GenericParams):
|
||||
|
||||
|
||||
class ExtractorURLParams(GenericParams):
|
||||
host: Literal["Doodstream", "Mixdrop", "Uqload"] = Field(..., description="The host to extract the URL from.")
|
||||
host: Literal["Doodstream", "Mixdrop", "Uqload", "LiveTV"] = Field(
|
||||
..., description="The host to extract the URL from."
|
||||
)
|
||||
destination: str = Field(..., description="The URL of the stream.", alias="d")
|
||||
redirect_stream: bool = Field(False, description="Whether to redirect to the stream endpoint automatically.")
|
||||
extra_params: Dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Additional parameters required for specific extractors (e.g., stream_title for LiveTV)",
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -89,7 +90,7 @@ class AsyncLRUMemoryCache:
|
||||
else:
|
||||
# Remove expired entry
|
||||
self._current_size -= entry.size
|
||||
del self._cache[key]
|
||||
self._cache.pop(key, None)
|
||||
return None
|
||||
|
||||
def set(self, key: str, entry: CacheEntry) -> None:
|
||||
@@ -121,12 +122,10 @@ class OptimizedHybridCache:
|
||||
cache_dir_name: str,
|
||||
ttl: int,
|
||||
max_memory_size: int = 100 * 1024 * 1024, # 100MB default
|
||||
file_shards: int = 256, # Number of subdirectories for sharding
|
||||
executor_workers: int = 4,
|
||||
):
|
||||
self.cache_dir = Path(tempfile.gettempdir()) / cache_dir_name
|
||||
self.ttl = ttl
|
||||
self.file_shards = file_shards
|
||||
self.memory_cache = AsyncLRUMemoryCache(maxsize=max_memory_size)
|
||||
self.stats = CacheStats()
|
||||
self._executor = ThreadPoolExecutor(max_workers=executor_workers)
|
||||
@@ -137,19 +136,15 @@ class OptimizedHybridCache:
|
||||
|
||||
def _init_cache_dirs(self):
|
||||
"""Initialize sharded cache directories."""
|
||||
for i in range(self.file_shards):
|
||||
shard_dir = self.cache_dir / f"shard_{i:03d}"
|
||||
os.makedirs(shard_dir, exist_ok=True)
|
||||
os.makedirs(self.cache_dir, exist_ok=True)
|
||||
|
||||
def _get_shard_path(self, key: str) -> Path:
|
||||
"""Get the appropriate shard directory for a key."""
|
||||
shard_num = hash(key) % self.file_shards
|
||||
return self.cache_dir / f"shard_{shard_num:03d}"
|
||||
def _get_md5_hash(self, key: str) -> str:
|
||||
"""Get the MD5 hash of a cache key."""
|
||||
return hashlib.md5(key.encode()).hexdigest()
|
||||
|
||||
def _get_file_path(self, key: str) -> Path:
|
||||
"""Get the file path for a cache key."""
|
||||
safe_key = str(hash(key))
|
||||
return self._get_shard_path(key) / safe_key
|
||||
return self.cache_dir / key
|
||||
|
||||
async def get(self, key: str, default: Any = None) -> Optional[bytes]:
|
||||
"""
|
||||
@@ -162,6 +157,7 @@ class OptimizedHybridCache:
|
||||
Returns:
|
||||
Cached value or default if not found
|
||||
"""
|
||||
key = self._get_md5_hash(key)
|
||||
# Try memory cache first
|
||||
entry = self.memory_cache.get(key)
|
||||
if entry is not None:
|
||||
@@ -227,6 +223,7 @@ class OptimizedHybridCache:
|
||||
# Create cache entry
|
||||
entry = CacheEntry(data=data, expires_at=expires_at, access_count=0, last_access=time.time(), size=len(data))
|
||||
|
||||
key = self._get_md5_hash(key)
|
||||
# Update memory cache
|
||||
self.memory_cache.set(key, entry)
|
||||
|
||||
@@ -287,14 +284,12 @@ class OptimizedHybridCache:
|
||||
logger.error(f"Error cleaning up file {file_path}: {e}")
|
||||
|
||||
# Clean up each shard
|
||||
for i in range(self.file_shards):
|
||||
shard_dir = self.cache_dir / f"shard_{i:03d}"
|
||||
try:
|
||||
async for entry in aiofiles.os.scandir(shard_dir):
|
||||
if entry.is_file() and not entry.name.endswith(".tmp"):
|
||||
await check_and_clean_file(Path(entry.path))
|
||||
except Exception as e:
|
||||
logger.error(f"Error scanning shard directory {shard_dir}: {e}")
|
||||
try:
|
||||
async for entry in aiofiles.os.scandir(self.cache_dir):
|
||||
if entry.is_file() and not entry.name.endswith(".tmp"):
|
||||
await check_and_clean_file(Path(entry.path))
|
||||
except Exception as e:
|
||||
logger.error(f"Error scanning shard directory {self.cache_dir}: {e}")
|
||||
|
||||
|
||||
# Create cache instances
|
||||
@@ -302,21 +297,24 @@ INIT_SEGMENT_CACHE = OptimizedHybridCache(
|
||||
cache_dir_name="init_segment_cache",
|
||||
ttl=3600, # 1 hour
|
||||
max_memory_size=500 * 1024 * 1024, # 500MB for init segments
|
||||
file_shards=512, # More shards for better distribution
|
||||
)
|
||||
|
||||
MPD_CACHE = OptimizedHybridCache(
|
||||
cache_dir_name="mpd_cache",
|
||||
ttl=300, # 5 minutes
|
||||
max_memory_size=100 * 1024 * 1024, # 100MB for MPD files
|
||||
file_shards=128,
|
||||
)
|
||||
|
||||
SPEEDTEST_CACHE = OptimizedHybridCache(
|
||||
cache_dir_name="speedtest_cache",
|
||||
ttl=3600, # 1 hour
|
||||
max_memory_size=50 * 1024 * 1024, # 50MB for speed test results
|
||||
file_shards=64,
|
||||
max_memory_size=50 * 1024 * 1024,
|
||||
)
|
||||
|
||||
EXTRACTOR_CACHE = OptimizedHybridCache(
|
||||
cache_dir_name="extractor_cache",
|
||||
ttl=5 * 60, # 5 minutes
|
||||
max_memory_size=50 * 1024 * 1024,
|
||||
)
|
||||
|
||||
|
||||
@@ -391,3 +389,23 @@ async def set_cache_speedtest(task_id: str, task: SpeedTestTask) -> bool:
|
||||
except Exception as e:
|
||||
logger.error(f"Error caching speed test data: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def get_cached_extractor_result(key: str) -> Optional[dict]:
|
||||
"""Get extractor result from cache."""
|
||||
cached_data = await EXTRACTOR_CACHE.get(key)
|
||||
if cached_data is not None:
|
||||
try:
|
||||
return json.loads(cached_data)
|
||||
except json.JSONDecodeError:
|
||||
await EXTRACTOR_CACHE.delete(key)
|
||||
return None
|
||||
|
||||
|
||||
async def set_cache_extractor_result(key: str, result: dict) -> bool:
|
||||
"""Cache extractor result."""
|
||||
try:
|
||||
return await EXTRACTOR_CACHE.set(key, json.dumps(result).encode())
|
||||
except Exception as e:
|
||||
logger.error(f"Error caching extractor result: {e}")
|
||||
return False
|
||||
|
||||
@@ -16,7 +16,9 @@ class M3U8Processor:
|
||||
"""
|
||||
self.request = request
|
||||
self.key_url = parse.urlparse(key_url) if key_url else None
|
||||
self.mediaflow_proxy_url = str(request.url_for("hls_stream_proxy").replace(scheme=get_original_scheme(request)))
|
||||
self.mediaflow_proxy_url = str(
|
||||
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
|
||||
)
|
||||
|
||||
async def process_m3u8(self, content: str, base_url: str) -> str:
|
||||
"""
|
||||
|
||||
Generated
+210
-7
@@ -101,6 +101,85 @@ files = [
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
|
||||
{file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
|
||||
{file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
|
||||
{file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
|
||||
{file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
|
||||
{file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
|
||||
{file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
|
||||
{file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
@@ -142,13 +221,13 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.4"
|
||||
version = "0.115.5"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"},
|
||||
{file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"},
|
||||
{file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"},
|
||||
{file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -194,13 +273,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f"},
|
||||
{file = "httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f"},
|
||||
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
|
||||
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -231,6 +310,7 @@ httpcore = "==1.*"
|
||||
idna = "*"
|
||||
sniffio = "*"
|
||||
socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""}
|
||||
zstandard = {version = ">=0.18.0", optional = true, markers = "extra == \"zstd\""}
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
@@ -302,6 +382,17 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
|
||||
type = ["mypy (>=1.11.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodome"
|
||||
version = "3.21.0"
|
||||
@@ -628,7 +719,119 @@ files = [
|
||||
{file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstandard"
|
||||
version = "0.23.0"
|
||||
description = "Zstandard bindings for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813"},
|
||||
{file = "zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160"},
|
||||
{file = "zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d"},
|
||||
{file = "zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd"},
|
||||
{file = "zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-win32.whl", hash = "sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9"},
|
||||
{file = "zstandard-0.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-win32.whl", hash = "sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274"},
|
||||
{file = "zstandard-0.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58"},
|
||||
{file = "zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
cffi = ["cffi (>=1.11)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10"
|
||||
content-hash = "31ae30f007ef7dacc5a13f41d1f88f3d2112e10e72846df59cb0956593bb33b9"
|
||||
content-hash = "d8791df38e0abbc748ac52240a33c7ba3c715402d6f881eb1ed708bbfa6e191b"
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ include = ["LICENSE", "README.md", "mediaflow_proxy/static/*"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.10"
|
||||
fastapi = "0.115.4"
|
||||
httpx = {extras = ["socks"], version = "^0.27.2"}
|
||||
fastapi = "0.115.5"
|
||||
httpx = {extras = ["socks", "zstd"], version = "^0.27.2"}
|
||||
tenacity = "^9.0.0"
|
||||
xmltodict = "^0.14.2"
|
||||
pydantic-settings = "^2.6.1"
|
||||
|
||||
Reference in New Issue
Block a user