Spaces:
Paused
Paused
Upload 25 files
Browse files- Dockerfile +5 -3
- LICENSE +21 -0
- README.md +215 -13
- mediaflow_proxy/handlers.py +65 -40
Dockerfile
CHANGED
|
@@ -3,7 +3,7 @@ FROM python:3.12-slim
|
|
| 3 |
# Set environment variables
|
| 4 |
ENV PYTHONDONTWRITEBYTECODE="1"
|
| 5 |
ENV PYTHONUNBUFFERED="1"
|
| 6 |
-
ENV PORT="
|
| 7 |
|
| 8 |
# Set work directory
|
| 9 |
WORKDIR /mediaflow_proxy
|
|
@@ -22,6 +22,8 @@ USER mediaflow_proxy
|
|
| 22 |
RUN pip install --user --no-cache-dir poetry
|
| 23 |
|
| 24 |
# Copy only requirements to cache them in docker layer
|
|
|
|
|
|
|
| 25 |
# Project initialization:
|
| 26 |
RUN poetry config virtualenvs.in-project true \
|
| 27 |
&& poetry install --no-interaction --no-ansi --no-dev
|
|
@@ -30,7 +32,7 @@ RUN poetry config virtualenvs.in-project true \
|
|
| 30 |
COPY --chown=mediaflow_proxy:mediaflow_proxy . /mediaflow_proxy
|
| 31 |
|
| 32 |
# Expose the port the app runs on
|
| 33 |
-
EXPOSE
|
| 34 |
|
| 35 |
# Activate virtual environment and run the application with Gunicorn
|
| 36 |
-
CMD ["poetry", "run", "gunicorn", "mediaflow_proxy.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:
|
|
|
|
| 3 |
# Set environment variables
|
| 4 |
ENV PYTHONDONTWRITEBYTECODE="1"
|
| 5 |
ENV PYTHONUNBUFFERED="1"
|
| 6 |
+
ENV PORT="8888"
|
| 7 |
|
| 8 |
# Set work directory
|
| 9 |
WORKDIR /mediaflow_proxy
|
|
|
|
| 22 |
RUN pip install --user --no-cache-dir poetry
|
| 23 |
|
| 24 |
# Copy only requirements to cache them in docker layer
|
| 25 |
+
COPY --chown=mediaflow_proxy:mediaflow_proxy pyproject.toml poetry.lock* /mediaflow_proxy/
|
| 26 |
+
|
| 27 |
# Project initialization:
|
| 28 |
RUN poetry config virtualenvs.in-project true \
|
| 29 |
&& poetry install --no-interaction --no-ansi --no-dev
|
|
|
|
| 32 |
COPY --chown=mediaflow_proxy:mediaflow_proxy . /mediaflow_proxy
|
| 33 |
|
| 34 |
# Expose the port the app runs on
|
| 35 |
+
EXPOSE 8888
|
| 36 |
|
| 37 |
# Activate virtual environment and run the application with Gunicorn
|
| 38 |
+
CMD ["poetry", "run", "gunicorn", "mediaflow_proxy.main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8888", "--timeout", "120", "--max-requests", "500", "--max-requests-jitter", "200"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) [2024] [Mohamed Zumair]
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,13 +1,215 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MediaFlow Proxy
|
| 2 |
+
|
| 3 |
+
<div style="text-align: center;">
|
| 4 |
+
<img src="https://cdn.githubraw.com/mhdzumair/mediaflow-proxy/main/static/logo.png" alt="MediaFlow Proxy Logo" width="200" style="border-radius: 15px;">
|
| 5 |
+
</div>
|
| 6 |
+
|
| 7 |
+
MediaFlow Proxy is a powerful and flexible solution for proxifying various types of media streams. It supports HTTP(S) links, HLS (M3U8) streams, and MPEG-DASH streams, including DRM-protected content. This proxy can convert MPEG-DASH DRM-protected streams to decrypted HLS live streams in real-time, making it one of the fastest live decrypter servers available.
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
- Convert MPEG-DASH streams (DRM-protected and non-protected) to HLS
|
| 12 |
+
- Support for Clear Key DRM-protected MPD DASH streams
|
| 13 |
+
- Support for non-DRM protected DASH live and VOD streams
|
| 14 |
+
- Proxy HTTP/HTTPS links with custom headers
|
| 15 |
+
- Proxy and modify HLS (M3U8) streams in real-time with custom headers and key URL modifications for bypassing some sneaky restrictions.
|
| 16 |
+
- Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
|
| 17 |
+
- Support for HTTP/HTTPS/SOCKS5 proxy forwarding
|
| 18 |
+
- Protect against unauthorized access and network bandwidth abuses
|
| 19 |
+
|
| 20 |
+
## Installation
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
### Option 1: Self-Hosted Deployment
|
| 24 |
+
|
| 25 |
+
#### Using Docker from Docker Hub
|
| 26 |
+
|
| 27 |
+
1. Pull & Run the Docker image:
|
| 28 |
+
```
|
| 29 |
+
docker run -p 8888:8888 -e API_PASSWORD=your_password mhdzumair/mediaflow-proxy
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
#### Using Poetry
|
| 33 |
+
|
| 34 |
+
1. Clone the repository:
|
| 35 |
+
```
|
| 36 |
+
git clone https://github.com/mhdzumair/mediaflow-proxy.git
|
| 37 |
+
cd mediaflow-proxy
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
2. Install dependencies using Poetry:
|
| 41 |
+
```
|
| 42 |
+
poetry install
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
3. Set the `API_PASSWORD` environment variable in `.env`:
|
| 46 |
+
```
|
| 47 |
+
echo "API_PASSWORD=your_password" > .env
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
4. Run the FastAPI server:
|
| 51 |
+
```
|
| 52 |
+
poetry run uvicorn mediaflow_proxy.main:app --host 0.0.0.0 --port 8888
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
#### Build and Run Docker Image Locally
|
| 57 |
+
|
| 58 |
+
1. Build the Docker image:
|
| 59 |
+
```
|
| 60 |
+
docker build -t mediaflow-proxy .
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
2. Run the Docker container:
|
| 64 |
+
```
|
| 65 |
+
docker run -p 8888:8888 -e API_PASSWORD=your_password mediaflow-proxy
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
#### Configuration
|
| 69 |
+
|
| 70 |
+
Set the following environment variables:
|
| 71 |
+
|
| 72 |
+
- `API_PASSWORD`: Required. Protects against unauthorized access and API network abuses.
|
| 73 |
+
- `PROXY_URL`: Optional. HTTP/HTTPS/SOCKS5 proxy URL for forwarding network requests.
|
| 74 |
+
- `MPD_LIVE_STREAM_DELAY`: Optional. Delay in seconds for live DASH streams. This is useful to prevent buffering issues with live streams. Default is `30` seconds.
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
### Option 2: Premium Hosted Service (ElfHosted)
|
| 78 |
+
<div style="text-align: center;">
|
| 79 |
+
<img src="https://store.elfhosted.com/wp-content/uploads/2024/08/mediaflow-proxy.jpg" alt="ElfHosted Logo" width="200" style="border-radius: 15px;">
|
| 80 |
+
</div>
|
| 81 |
+
For a hassle-free, high-performance deployment of MediaFlow Proxy, consider the premium hosted service through ElfHosted.
|
| 82 |
+
|
| 83 |
+
To purchase:
|
| 84 |
+
1. Visit [https://store.elfhosted.com/product/mediaflow-proxy](https://store.elfhosted.com/product/mediaflow-proxy)
|
| 85 |
+
2. Follow ElfHosted's setup instructions
|
| 86 |
+
|
| 87 |
+
Benefits:
|
| 88 |
+
- Instant setup and automatic updates
|
| 89 |
+
- High performance and 24/7 availability
|
| 90 |
+
- No server maintenance required
|
| 91 |
+
|
| 92 |
+
Ideal for users who want a reliable, plug-and-play solution without the technical overhead of self-hosting.
|
| 93 |
+
|
| 94 |
+
## Usage
|
| 95 |
+
|
| 96 |
+
### Endpoints
|
| 97 |
+
|
| 98 |
+
1. `/proxy/hls`: Proxify HLS streams
|
| 99 |
+
2. `/proxy/stream`: Proxy generic http video streams
|
| 100 |
+
3. `/proxy/mpd/manifest`: Process MPD manifests
|
| 101 |
+
4. `/proxy/mpd/playlist`: Generate HLS playlists from MPD
|
| 102 |
+
5. `/proxy/mpd/segment`: Process and decrypt media segments
|
| 103 |
+
6. `/proxy/ip`: Get the public IP address of the MediaFlow Proxy server
|
| 104 |
+
|
| 105 |
+
Once the server is running, for more details on the available endpoints and their parameters, visit the Swagger UI at `http://localhost:8888/docs`.
|
| 106 |
+
|
| 107 |
+
### Examples
|
| 108 |
+
|
| 109 |
+
#### Proxy HTTPS Stream
|
| 110 |
+
|
| 111 |
+
```bash
|
| 112 |
+
mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
#### Proxy HLS Stream with Headers
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
mpv "http://localhost:8888/proxy/hls?d=https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8&h_referer=https://apple.com/&h_origin=https://apple.com&h_user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36&api_password=your_password"
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
#### Live DASH Stream (Non-DRM Protected)
|
| 122 |
+
|
| 123 |
+
```bash
|
| 124 |
+
mpv -v "http://localhost:8888/proxy/mpd/manifest?d=https://livesim.dashif.org/livesim/chunkdur_1/ato_7/testpic4_8s/Manifest.mpd&api_password=your_password"
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
#### VOD DASH Stream (DRM Protected)
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
mpv -v "http://localhost:8888/proxy/mpd/manifest?d=https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd&key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=your_password"
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
Note: The `key` and `key_id` parameters are automatically processed if they're not in the correct format.
|
| 134 |
+
|
| 135 |
+
### URL Encoding
|
| 136 |
+
|
| 137 |
+
For players like VLC that require properly encoded URLs, use the `encode_mediaflow_proxy_url` function:
|
| 138 |
+
|
| 139 |
+
```python
|
| 140 |
+
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
|
| 141 |
+
|
| 142 |
+
encoded_url = encode_mediaflow_proxy_url(
|
| 143 |
+
mediaflow_proxy_url="http://127.0.0.1:8888",
|
| 144 |
+
endpoint="/proxy/mpd/manifest",
|
| 145 |
+
destination_url="https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd",
|
| 146 |
+
query_params={
|
| 147 |
+
"key_id": "nrQFDeRLSAKTLifXUIPiZg",
|
| 148 |
+
"key": "FmY0xnWCPCNaSpRG-tUuTQ",
|
| 149 |
+
"api_password": "your_password"
|
| 150 |
+
},
|
| 151 |
+
request_headers={
|
| 152 |
+
"referer": "https://media.axprod.net/",
|
| 153 |
+
"origin": "https://media.axprod.net",
|
| 154 |
+
}
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
print(encoded_url)
|
| 158 |
+
|
| 159 |
+
# http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=your_password&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd&h_referer=https%3A%2F%2Fmedia.axprod.net%2F&h_origin=https%3A%2F%2Fmedia.axprod.net
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
This will output a properly encoded URL that can be used with players like VLC.
|
| 163 |
+
|
| 164 |
+
```bash
|
| 165 |
+
vlc "http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=dedsec&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd"
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
### Using MediaFlow Proxy with Debrid Services and Stremio Addons
|
| 169 |
+
|
| 170 |
+
MediaFlow Proxy can be particularly useful when working with Debrid services (like Real-Debrid, AllDebrid) and Stremio addons. The `/proxy/ip` endpoint allows you to retrieve the public IP address of the MediaFlow Proxy server, which is crucial for routing Debrid streams correctly.
|
| 171 |
+
|
| 172 |
+
When a Stremio addon needs to create a video URL for a Debrid service, it typically needs to provide the user's public IP address. However, when routing the Debrid stream through MediaFlow Proxy, you should use the IP address of the MediaFlow Proxy server instead.
|
| 173 |
+
|
| 174 |
+
Here's how to utilize MediaFlow Proxy in this scenario:
|
| 175 |
+
|
| 176 |
+
1. If MediaFlow Proxy is accessible over the internet:
|
| 177 |
+
- Use the `/proxy/ip` endpoint to get the MediaFlow Proxy server's public IP.
|
| 178 |
+
- Use this IP when creating Debrid service URLs in your Stremio addon.
|
| 179 |
+
|
| 180 |
+
2. If MediaFlow Proxy is set up locally:
|
| 181 |
+
- Stremio addons can directly use the client's IP address.
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
## Future Development
|
| 185 |
+
|
| 186 |
+
- Add support for Widevine and PlayReady decryption
|
| 187 |
+
|
| 188 |
+
## Acknowledgements and Inspirations
|
| 189 |
+
|
| 190 |
+
MediaFlow Proxy was developed with inspiration from various projects and resources:
|
| 191 |
+
|
| 192 |
+
- [Stremio Server](https://github.com/Stremio/stremio-server) for HLS Proxify implementation, which inspired our HLS M3u8 Manifest parsing and redirection proxify support.
|
| 193 |
+
- [Comet Debrid proxy](https://github.com/g0ldyy/comet) for the idea of proxifying HTTPS video streams.
|
| 194 |
+
- [mp4decrypt](https://www.bento4.com/developers/dash/encryption_and_drm/), [mp4box](https://wiki.gpac.io/xmlformats/Common-Encryption/), and [devine](https://github.com/devine-dl/devine) for insights on parsing MPD and decrypting Clear Key DRM protected content.
|
| 195 |
+
- Test URLs were sourced from:
|
| 196 |
+
- [OTTVerse MPEG-DASH MPD Examples](https://ottverse.com/free-mpeg-dash-mpd-manifest-example-test-urls/)
|
| 197 |
+
- [OTTVerse HLS M3U8 Examples](https://ottverse.com/free-hls-m3u8-test-urls/)
|
| 198 |
+
- [Bitmovin Stream Test](https://bitmovin.com/demos/stream-test)
|
| 199 |
+
- [Bitmovin DRM Demo](https://bitmovin.com/demos/drm)
|
| 200 |
+
- [DASH-IF Reference Player](http://reference.dashif.org/dash.js/nightly/samples/)
|
| 201 |
+
- [HLS Protocol RFC](https://www.rfc-editor.org/rfc/rfc8216) for understanding the HLS protocol specifications.
|
| 202 |
+
- Claude 3.5 Sonnet for code assistance and brainstorming.
|
| 203 |
+
|
| 204 |
+
## Contributing
|
| 205 |
+
|
| 206 |
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
| 207 |
+
|
| 208 |
+
## License
|
| 209 |
+
|
| 210 |
+
[MIT License](LICENSE)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
## Disclaimer
|
| 214 |
+
|
| 215 |
+
This project is for educational purposes only. The developers of MediaFlow Proxy are not responsible for any misuse of this software. Please ensure that you have the necessary permissions to access and use the media streams you are proxying.
|
mediaflow_proxy/handlers.py
CHANGED
|
@@ -31,17 +31,43 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
|
|
| 31 |
Returns:
|
| 32 |
Response: The HTTP response with the processed m3u8 playlist or streamed content.
|
| 33 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
-
if destination.endswith((".m3u", ".m3u8"))
|
| 36 |
-
return await fetch_and_process_m3u8(destination, headers, request, key_url)
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
except httpx.HTTPStatusError as e:
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
except Exception as e:
|
| 43 |
-
|
| 44 |
-
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
async def proxy_stream(method: str, video_url: str, headers: dict):
|
|
@@ -90,23 +116,27 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
|
|
| 90 |
background=BackgroundTask(streamer.close),
|
| 91 |
)
|
| 92 |
except httpx.HTTPStatusError as e:
|
| 93 |
-
logger.error(f"Upstream service error while handling {method} request: {e}")
|
| 94 |
await client.aclose()
|
|
|
|
| 95 |
return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
|
| 96 |
except DownloadError as e:
|
|
|
|
| 97 |
logger.error(f"Error downloading {video_url}: {e}")
|
| 98 |
-
return Response(status_code=
|
| 99 |
except Exception as e:
|
| 100 |
-
logger.error(f"Internal server error while handling {method} request: {e}")
|
| 101 |
await client.aclose()
|
|
|
|
| 102 |
return Response(status_code=502, content=f"Internal server error: {e}")
|
| 103 |
|
| 104 |
|
| 105 |
-
async def fetch_and_process_m3u8(
|
|
|
|
|
|
|
| 106 |
"""
|
| 107 |
Fetches and processes the m3u8 playlist, converting it to an HLS playlist.
|
| 108 |
|
| 109 |
Args:
|
|
|
|
| 110 |
url (str): The URL of the m3u8 playlist.
|
| 111 |
headers (dict): The headers to include in the request.
|
| 112 |
request (Request): The incoming HTTP request.
|
|
@@ -115,34 +145,29 @@ async def fetch_and_process_m3u8(url: str, headers: dict, request: Request, key_
|
|
| 115 |
Returns:
|
| 116 |
Response: The HTTP response with the processed m3u8 playlist.
|
| 117 |
"""
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
logger.error(f"Error downloading m3u8: {url}")
|
| 142 |
-
return Response(status_code=502, content=str(e))
|
| 143 |
-
except Exception as e:
|
| 144 |
-
logger.exception(f"Unexpected error while processing m3u8: {e}")
|
| 145 |
-
return Response(status_code=502, content=str(e))
|
| 146 |
|
| 147 |
|
| 148 |
async def handle_drm_key_data(key_id, key, drm_info):
|
|
|
|
| 31 |
Returns:
|
| 32 |
Response: The HTTP response with the processed m3u8 playlist or streamed content.
|
| 33 |
"""
|
| 34 |
+
client = httpx.AsyncClient(
|
| 35 |
+
follow_redirects=True,
|
| 36 |
+
timeout=httpx.Timeout(30.0),
|
| 37 |
+
limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
|
| 38 |
+
proxy=settings.proxy_url,
|
| 39 |
+
)
|
| 40 |
+
streamer = Streamer(client)
|
| 41 |
try:
|
| 42 |
+
if destination.endswith((".m3u", ".m3u8")):
|
| 43 |
+
return await fetch_and_process_m3u8(streamer, destination, headers, request, key_url)
|
| 44 |
+
|
| 45 |
+
response = await streamer.head(destination, headers)
|
| 46 |
+
if "mpegurl" in response.headers.get("content-type", "").lower():
|
| 47 |
+
return await fetch_and_process_m3u8(streamer, destination, headers, request, key_url)
|
| 48 |
+
|
| 49 |
+
headers.update({"accept-ranges": headers.get("range", "bytes=0-")})
|
| 50 |
+
# handle the encoding response header, since decompression is handled by the httpx
|
| 51 |
+
if "content-encoding" in response.headers:
|
| 52 |
+
del response.headers["content-encoding"]
|
| 53 |
+
|
| 54 |
+
return StreamingResponse(
|
| 55 |
+
streamer.stream_content(destination, headers),
|
| 56 |
+
headers=response.headers,
|
| 57 |
+
background=BackgroundTask(streamer.close),
|
| 58 |
+
)
|
| 59 |
except httpx.HTTPStatusError as e:
|
| 60 |
+
await client.aclose()
|
| 61 |
+
logger.error(f"Upstream service error while handling request: {e}")
|
| 62 |
+
return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
|
| 63 |
+
except DownloadError as e:
|
| 64 |
+
await client.aclose()
|
| 65 |
+
logger.error(f"Error downloading {destination}: {e}")
|
| 66 |
+
return Response(status_code=e.status_code, content=str(e))
|
| 67 |
except Exception as e:
|
| 68 |
+
await client.aclose()
|
| 69 |
+
logger.error(f"Internal server error while handling request: {e}")
|
| 70 |
+
return Response(status_code=502, content=f"Internal server error: {e}")
|
| 71 |
|
| 72 |
|
| 73 |
async def proxy_stream(method: str, video_url: str, headers: dict):
|
|
|
|
| 116 |
background=BackgroundTask(streamer.close),
|
| 117 |
)
|
| 118 |
except httpx.HTTPStatusError as e:
|
|
|
|
| 119 |
await client.aclose()
|
| 120 |
+
logger.error(f"Upstream service error while handling {method} request: {e}")
|
| 121 |
return Response(status_code=e.response.status_code, content=f"Upstream service error: {e}")
|
| 122 |
except DownloadError as e:
|
| 123 |
+
await client.aclose()
|
| 124 |
logger.error(f"Error downloading {video_url}: {e}")
|
| 125 |
+
return Response(status_code=e.status_code, content=str(e))
|
| 126 |
except Exception as e:
|
|
|
|
| 127 |
await client.aclose()
|
| 128 |
+
logger.error(f"Internal server error while handling {method} request: {e}")
|
| 129 |
return Response(status_code=502, content=f"Internal server error: {e}")
|
| 130 |
|
| 131 |
|
| 132 |
+
async def fetch_and_process_m3u8(
|
| 133 |
+
streamer: Streamer, url: str, headers: dict, request: Request, key_url: HttpUrl = None
|
| 134 |
+
):
|
| 135 |
"""
|
| 136 |
Fetches and processes the m3u8 playlist, converting it to an HLS playlist.
|
| 137 |
|
| 138 |
Args:
|
| 139 |
+
streamer (Streamer): The HTTP client to use for streaming.
|
| 140 |
url (str): The URL of the m3u8 playlist.
|
| 141 |
headers (dict): The headers to include in the request.
|
| 142 |
request (Request): The incoming HTTP request.
|
|
|
|
| 145 |
Returns:
|
| 146 |
Response: The HTTP response with the processed m3u8 playlist.
|
| 147 |
"""
|
| 148 |
+
try:
|
| 149 |
+
content = await streamer.get_text(url, headers)
|
| 150 |
+
processor = M3U8Processor(request, key_url)
|
| 151 |
+
processed_content = await processor.process_m3u8(content, str(streamer.response.url))
|
| 152 |
+
return Response(
|
| 153 |
+
content=processed_content,
|
| 154 |
+
media_type="application/vnd.apple.mpegurl",
|
| 155 |
+
headers={
|
| 156 |
+
"Content-Disposition": "inline",
|
| 157 |
+
"Accept-Ranges": "none",
|
| 158 |
+
},
|
| 159 |
+
)
|
| 160 |
+
except httpx.HTTPStatusError as e:
|
| 161 |
+
logger.error(f"HTTP error while fetching m3u8: {e}")
|
| 162 |
+
return Response(status_code=e.response.status_code, content=str(e))
|
| 163 |
+
except DownloadError as e:
|
| 164 |
+
logger.error(f"Error downloading m3u8: {url}")
|
| 165 |
+
return Response(status_code=502, content=str(e))
|
| 166 |
+
except Exception as e:
|
| 167 |
+
logger.exception(f"Unexpected error while processing m3u8: {e}")
|
| 168 |
+
return Response(status_code=502, content=str(e))
|
| 169 |
+
finally:
|
| 170 |
+
await streamer.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
|
| 173 |
async def handle_drm_key_data(key_id, key, drm_info):
|