# PyRTMP: Pure Python RTMP server
![coverage](https://github.com/Eittipat/pyrtmp/blob/master/coverage.svg)
## Features
- ✅ Pure Python
- ✅ Easy to customize
- ✅ Production Ready
- ✅ UV loop
- ✅ PyPy
- ✅ Support RTMP(s)
- ✅ Support RTMPT(s)
## Announcement
After using this package for years in production server. It runs flawlessly without any problem.
So I decided to switch the development status from **Beta** to **Production** since version 0.2.0. Also,
I share my configuration at Deployment section below.
If you have any problems. Feel free to create issue on [GitHub](https://github.com/Eittipat/pyrtmp/issues).
## What's new
### 0.3.0
- Clean up, refactoring, bug fixes
- Add more testcases.
- Add support to Python 3.11
- Add GitHub action workflows
- Add [RTMP to FFMPEG](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_ffmpeg.py) example
- Add [RTMP to FLV](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_flvdump.py) example
- Add [RTMPT](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_rtmpt.py) example
## Installation
Install from PyPI:
```
pip install pyrtmp
```
Install from source:
```
pip install pyrtmp@git+https://github.com/Eittipat/pyrtmp.git
```
## Quickstart
Let say we want to create a simple RTMP server that can save all incoming stream to FLV file.
We can do it by creating a subclass of [*Simple RTMP
controller*](https://github.com/Eittipat/pyrtmp/blob/master/pyrtmp/rtmp.py)
and override some methods.
Here is the example:
```python
import asyncio
import os
import logging
from pyrtmp import StreamClosedException
from pyrtmp.flv import FLVFileWriter, FLVMediaType
from pyrtmp.session_manager import SessionManager
from pyrtmp.rtmp import SimpleRTMPController, RTMPProtocol, SimpleRTMPServer
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
class RTMP2FLVController(SimpleRTMPController):
def __init__(self, output_directory: str):
self.output_directory = output_directory
super().__init__()
async def on_ns_publish(self, session, message) -> None:
publishing_name = message.publishing_name
file_path = os.path.join(self.output_directory, f"{publishing_name}.flv")
session.state = FLVFileWriter(output=file_path)
await super().on_ns_publish(session, message)
async def on_metadata(self, session, message) -> None:
session.state.write(0, message.to_raw_meta(), FLVMediaType.OBJECT)
await super().on_metadata(session, message)
async def on_video_message(self, session, message) -> None:
session.state.write(message.timestamp, message.payload, FLVMediaType.VIDEO)
await super().on_video_message(session, message)
async def on_audio_message(self, session, message) -> None:
session.state.write(message.timestamp, message.payload, FLVMediaType.AUDIO)
await super().on_audio_message(session, message)
async def on_stream_closed(self, session: SessionManager, exception: StreamClosedException) -> None:
session.state.close()
await super().on_stream_closed(session, exception)
class SimpleServer(SimpleRTMPServer):
def __init__(self, output_directory: str):
self.output_directory = output_directory
super().__init__()
async def create(self, host: str, port: int):
loop = asyncio.get_event_loop()
self.server = await loop.create_server(
lambda: RTMPProtocol(controller=RTMP2FLVController(self.output_directory)),
host=host,
port=port,
)
async def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
server = SimpleServer(output_directory=current_dir)
await server.create(host='0.0.0.0', port=1935)
await server.start()
await server.wait_closed()
if __name__ == "__main__":
asyncio.run(main())
```
Next, we can test our server by executing the following command:
```
ffmpeg -i my_test_file.flv -c:v copy -c:a copy -f flv rtmp://127.0.0.1:1935/test/sample
```
Your flv file will be saved in the same directory as your python script.
## Deployment
In production environment, You should run multiple instances of RTMP server and use load balancer to distribute incoming
stream.
I recommend to use `Nginx` as a load balancer and `Supervisord` to manage your RTMP server instances.
Also, `uvloop` or `pypy` can be used to boost your performance.
Here is nginx configuration example:
```
stream {
upstream stream_backend {
127.0.0.1:1936;
127.0.0.1:1937;
}
server {
listen 1935;
proxy_pass stream_backend;
}
}
```
## Benchmark
Coming soon.
## Roadmap
- Support AMF3
- ReStream / Client Mode
- Documentation
Raw data
{
"_id": null,
"home_page": "https://github.com/Eittipat/pyrtmp.git",
"name": "pyrtmp",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "RTMP, RTMPT, asyncio",
"author": "Eittipat.K",
"author_email": "iammop@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/c5/5b/c44361d8681f7c79610ecbdffab30c6ba4ebf0651e8511e64bdc915c491a/pyrtmp-0.3.1.tar.gz",
"platform": null,
"description": "# PyRTMP: Pure Python RTMP server\n![coverage](https://github.com/Eittipat/pyrtmp/blob/master/coverage.svg)\n## Features\n\n- \u2705 Pure Python\n- \u2705 Easy to customize\n- \u2705 Production Ready\n- \u2705 UV loop\n- \u2705 PyPy\n- \u2705 Support RTMP(s)\n- \u2705 Support RTMPT(s)\n\n## Announcement\n\nAfter using this package for years in production server. It runs flawlessly without any problem.\nSo I decided to switch the development status from **Beta** to **Production** since version 0.2.0. Also,\nI share my configuration at Deployment section below.\n\nIf you have any problems. Feel free to create issue on [GitHub](https://github.com/Eittipat/pyrtmp/issues).\n\n## What's new\n\n### 0.3.0\n\n- Clean up, refactoring, bug fixes\n- Add more testcases.\n- Add support to Python 3.11\n- Add GitHub action workflows\n- Add [RTMP to FFMPEG](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_ffmpeg.py) example\n- Add [RTMP to FLV](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_flvdump.py) example\n- Add [RTMPT](https://github.com/Eittipat/pyrtmp/blob/master/example/demo_rtmpt.py) example\n\n## Installation\n\nInstall from PyPI:\n```\npip install pyrtmp\n```\nInstall from source:\n```\npip install pyrtmp@git+https://github.com/Eittipat/pyrtmp.git\n```\n\n## Quickstart\n\nLet say we want to create a simple RTMP server that can save all incoming stream to FLV file.\nWe can do it by creating a subclass of [*Simple RTMP\ncontroller*](https://github.com/Eittipat/pyrtmp/blob/master/pyrtmp/rtmp.py)\nand override some methods.\nHere is the example:\n\n```python\nimport asyncio\nimport os\nimport logging\n\nfrom pyrtmp import StreamClosedException\nfrom pyrtmp.flv import FLVFileWriter, FLVMediaType\nfrom pyrtmp.session_manager import SessionManager\nfrom pyrtmp.rtmp import SimpleRTMPController, RTMPProtocol, SimpleRTMPServer\n\nlogging.basicConfig(level=logging.DEBUG)\nlogger = logging.getLogger(__name__)\nlogger.setLevel(logging.DEBUG)\n\n\nclass RTMP2FLVController(SimpleRTMPController):\n\n def __init__(self, output_directory: str):\n self.output_directory = output_directory\n super().__init__()\n\n async def on_ns_publish(self, session, message) -> None:\n publishing_name = message.publishing_name\n file_path = os.path.join(self.output_directory, f\"{publishing_name}.flv\")\n session.state = FLVFileWriter(output=file_path)\n await super().on_ns_publish(session, message)\n\n async def on_metadata(self, session, message) -> None:\n session.state.write(0, message.to_raw_meta(), FLVMediaType.OBJECT)\n await super().on_metadata(session, message)\n\n async def on_video_message(self, session, message) -> None:\n session.state.write(message.timestamp, message.payload, FLVMediaType.VIDEO)\n await super().on_video_message(session, message)\n\n async def on_audio_message(self, session, message) -> None:\n session.state.write(message.timestamp, message.payload, FLVMediaType.AUDIO)\n await super().on_audio_message(session, message)\n\n async def on_stream_closed(self, session: SessionManager, exception: StreamClosedException) -> None:\n session.state.close()\n await super().on_stream_closed(session, exception)\n\n\nclass SimpleServer(SimpleRTMPServer):\n\n def __init__(self, output_directory: str):\n self.output_directory = output_directory\n super().__init__()\n\n async def create(self, host: str, port: int):\n loop = asyncio.get_event_loop()\n self.server = await loop.create_server(\n lambda: RTMPProtocol(controller=RTMP2FLVController(self.output_directory)),\n host=host,\n port=port,\n )\n\n\nasync def main():\n current_dir = os.path.dirname(os.path.abspath(__file__))\n server = SimpleServer(output_directory=current_dir)\n await server.create(host='0.0.0.0', port=1935)\n await server.start()\n await server.wait_closed()\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\nNext, we can test our server by executing the following command:\n\n```\nffmpeg -i my_test_file.flv -c:v copy -c:a copy -f flv rtmp://127.0.0.1:1935/test/sample\n```\n\nYour flv file will be saved in the same directory as your python script.\n\n## Deployment\n\nIn production environment, You should run multiple instances of RTMP server and use load balancer to distribute incoming\nstream.\nI recommend to use `Nginx` as a load balancer and `Supervisord` to manage your RTMP server instances.\nAlso, `uvloop` or `pypy` can be used to boost your performance.\n\nHere is nginx configuration example:\n\n```\nstream {\n\n upstream stream_backend {\n 127.0.0.1:1936;\n 127.0.0.1:1937;\n }\n\n server {\n listen 1935;\n proxy_pass stream_backend;\n }\n}\n```\n\n## Benchmark\n\nComing soon.\n\n## Roadmap\n\n- Support AMF3\n- ReStream / Client Mode\n- Documentation\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "PyRTMP: Pure Python RTMP server",
"version": "0.3.1",
"project_urls": {
"Download": "https://github.com/Eittipat/pyrtmp/releases/tag/v0.3.0",
"Homepage": "https://github.com/Eittipat/pyrtmp.git"
},
"split_keywords": [
"rtmp",
" rtmpt",
" asyncio"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c55bc44361d8681f7c79610ecbdffab30c6ba4ebf0651e8511e64bdc915c491a",
"md5": "e0690db9a2d3567ecb184afe3f980c70",
"sha256": "0cf5659e2b7d76d1e659e5ace3ced481fd904cb546c0e4b7a96e6f32a9cc97ac"
},
"downloads": -1,
"filename": "pyrtmp-0.3.1.tar.gz",
"has_sig": false,
"md5_digest": "e0690db9a2d3567ecb184afe3f980c70",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 17365,
"upload_time": "2024-05-26T07:11:47",
"upload_time_iso_8601": "2024-05-26T07:11:47.642221Z",
"url": "https://files.pythonhosted.org/packages/c5/5b/c44361d8681f7c79610ecbdffab30c6ba4ebf0651e8511e64bdc915c491a/pyrtmp-0.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-05-26 07:11:47",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Eittipat",
"github_project": "pyrtmp",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "pyrtmp"
}