Add M3U8 to RTMP streamer script
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
.venv
|
||||||
|
*.env
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.m3u8
|
||||||
|
*.m3u
|
||||||
@@ -1,17 +1,88 @@
|
|||||||
# 4h_tool
|
# M3U8 to RTMP Streamer
|
||||||
|
|
||||||
**obs tool for streaming video files using playlist**
|
Python script to stream M3U8 (HLS) live streams to RTMP servers using ffmpeg without re-encoding.
|
||||||
|
|
||||||
allows you to create and manage playlists of video files that can be streamed through obs. supports various video formats and provides playlist controls for seamless streaming sessions.
|
## Features
|
||||||
|
|
||||||
## features
|
- Direct stream passthrough (no re-encoding)
|
||||||
|
- Supports M3U8/HLS playlists
|
||||||
|
- Streams to RTMP servers
|
||||||
|
- Real-time output monitoring
|
||||||
|
- Graceful shutdown on Ctrl+C
|
||||||
|
|
||||||
- playlist creation and management
|
## Requirements
|
||||||
- video file format support
|
|
||||||
- obs integration
|
|
||||||
- playlist controls (play, pause, next, previous)
|
|
||||||
- queue management
|
|
||||||
|
|
||||||
---
|
- Python 3.7+
|
||||||
|
- FFmpeg installed and in PATH (or provide path with --ffmpeg)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install FFmpeg:
|
||||||
|
- Windows: Download from https://ffmpeg.org/download.html
|
||||||
|
- macOS: `brew install ffmpeg`
|
||||||
|
- Linux: `sudo apt install ffmpeg` or `sudo yum install ffmpeg`
|
||||||
|
|
||||||
|
2. No Python dependencies required (uses standard library)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python stream_m3u_to_rtmp.py <m3u_url> <rtmp_url>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stream from M3U8 URL to RTMP server
|
||||||
|
python stream_m3u_to_rtmp.py \
|
||||||
|
"https://example.com/live/stream.m3u8" \
|
||||||
|
"rtmp://live.twitch.tv/app/your_stream_key"
|
||||||
|
|
||||||
|
# Stream from local M3U8 file
|
||||||
|
python stream_m3u_to_rtmp.py \
|
||||||
|
"playlist.m3u8" \
|
||||||
|
"rtmp://server.com/live/stream_key"
|
||||||
|
|
||||||
|
# Specify custom ffmpeg path
|
||||||
|
python stream_m3u_to_rtmp.py \
|
||||||
|
"https://example.com/stream.m3u8" \
|
||||||
|
"rtmp://server.com/live/key" \
|
||||||
|
--ffmpeg "C:/ffmpeg/bin/ffmpeg.exe"
|
||||||
|
```
|
||||||
|
|
||||||
|
## RTMP URL Format
|
||||||
|
|
||||||
|
RTMP URLs typically follow this format:
|
||||||
|
```
|
||||||
|
rtmp://server.com:port/app/stream_key
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Twitch: `rtmp://live.twitch.tv/app/YOUR_STREAM_KEY`
|
||||||
|
- YouTube: `rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY`
|
||||||
|
- Custom server: `rtmp://your-server.com:1935/live/stream_key`
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The script uses ffmpeg with:
|
||||||
|
- `-c copy`: Copies all codecs without re-encoding (passthrough)
|
||||||
|
- `-f flv`: Outputs in FLV format (required for RTMP)
|
||||||
|
- `-re`: Reads input at native frame rate
|
||||||
|
- Direct stream from M3U8 to RTMP
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The stream will continue until stopped (Ctrl+C) or the source ends
|
||||||
|
- No re-encoding means very low CPU usage
|
||||||
|
- Requires the source stream codecs to be compatible with RTMP/FLV
|
||||||
|
- If codec compatibility issues occur, you may need to re-encode (remove `-c copy`)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **FFmpeg not found**: Install FFmpeg or use `--ffmpeg` to specify path
|
||||||
|
- **Connection errors**: Check RTMP URL and network connectivity
|
||||||
|
- **Codec errors**: Source codecs may not be RTMP-compatible, may need re-encoding
|
||||||
|
- **Stream stops**: Check source M3U8 URL is accessible and valid
|
||||||
|
|
||||||
*built with ai assistance (cursor with chatgpt)*
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Stream M3U8 (HLS) live stream to RTMP server using ffmpeg without re-encoding.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def stream_m3u_to_rtmp(m3u_url, rtmp_url, ffmpeg_path='ffmpeg'):
|
||||||
|
"""
|
||||||
|
Stream M3U8 playlist to RTMP server without re-encoding.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
m3u_url: URL or path to M3U8 playlist file
|
||||||
|
rtmp_url: RTMP server URL (e.g., rtmp://server/live/stream_key)
|
||||||
|
ffmpeg_path: Path to ffmpeg executable (default: 'ffmpeg')
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FFmpeg command for direct stream (no re-encoding)
|
||||||
|
# -i: input M3U8 URL
|
||||||
|
# -c copy: copy codecs without re-encoding
|
||||||
|
# -f flv: output format FLV (required for RTMP)
|
||||||
|
# -flvflags no_duration_filesize: avoid issues with FLV format
|
||||||
|
# -re: read input at native frame rate (important for live streams)
|
||||||
|
# -stream_loop -1: loop the stream indefinitely (for live streams, this might not be needed)
|
||||||
|
|
||||||
|
ffmpeg_cmd = [
|
||||||
|
ffmpeg_path,
|
||||||
|
'-i', m3u_url,
|
||||||
|
'-c', 'copy', # Copy all streams without re-encoding
|
||||||
|
'-f', 'flv', # RTMP requires FLV format
|
||||||
|
'-flvflags', 'no_duration_filesize',
|
||||||
|
'-re', # Read input at native frame rate
|
||||||
|
rtmp_url
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Starting stream from: {m3u_url}")
|
||||||
|
print(f"Streaming to: {rtmp_url}")
|
||||||
|
print(f"FFmpeg command: {' '.join(ffmpeg_cmd)}")
|
||||||
|
print("\nPress Ctrl+C to stop streaming...\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Start ffmpeg process
|
||||||
|
process = subprocess.Popen(
|
||||||
|
ffmpeg_cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True,
|
||||||
|
bufsize=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print ffmpeg output in real-time
|
||||||
|
def print_output(pipe):
|
||||||
|
for line in iter(pipe.readline, ''):
|
||||||
|
if line:
|
||||||
|
print(line.strip())
|
||||||
|
|
||||||
|
# Monitor both stdout and stderr
|
||||||
|
import threading
|
||||||
|
stdout_thread = threading.Thread(target=print_output, args=(process.stdout,))
|
||||||
|
stderr_thread = threading.Thread(target=print_output, args=(process.stderr,))
|
||||||
|
|
||||||
|
stdout_thread.daemon = True
|
||||||
|
stderr_thread.daemon = True
|
||||||
|
stdout_thread.start()
|
||||||
|
stderr_thread.start()
|
||||||
|
|
||||||
|
# Wait for process to complete
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
if process.returncode != 0:
|
||||||
|
print(f"\nError: FFmpeg exited with code {process.returncode}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("\nStream ended successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nStopping stream...")
|
||||||
|
if process:
|
||||||
|
process.terminate()
|
||||||
|
try:
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
print("Stream stopped")
|
||||||
|
return False
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: FFmpeg not found at '{ffmpeg_path}'")
|
||||||
|
print("Please install FFmpeg or provide the correct path")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Stream M3U8 (HLS) live stream to RTMP server without re-encoding'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'm3u_url',
|
||||||
|
help='URL or path to M3U8 playlist file'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'rtmp_url',
|
||||||
|
help='RTMP server URL (e.g., rtmp://server/live/stream_key)'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--ffmpeg',
|
||||||
|
default='ffmpeg',
|
||||||
|
help='Path to ffmpeg executable (default: ffmpeg)'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
success = stream_m3u_to_rtmp(args.m3u_url, args.rtmp_url, args.ffmpeg)
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
Reference in New Issue
Block a user