From edfe004c696da10bd55516e88ad57a0e8cc554b7 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Sun, 11 Jan 2026 23:27:17 -0500 Subject: [PATCH] Add M3U8 to RTMP streamer script --- .gitignore | 16 ++++++ README.md | 93 +++++++++++++++++++++++++++---- stream_m3u_to_rtmp.py | 124 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 11 deletions(-) create mode 100644 .gitignore create mode 100644 stream_m3u_to_rtmp.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53ff03d --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +*.env +.DS_Store +*.log +.vscode/ +.idea/ +*.m3u8 +*.m3u diff --git a/README.md b/README.md index 3aebceb..ae8ad15 100644 --- a/README.md +++ b/README.md @@ -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 -- video file format support -- obs integration -- playlist controls (play, pause, next, previous) -- queue management +## Requirements ---- +- 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 +``` + +### 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)* diff --git a/stream_m3u_to_rtmp.py b/stream_m3u_to_rtmp.py new file mode 100644 index 0000000..2e9b1de --- /dev/null +++ b/stream_m3u_to_rtmp.py @@ -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() +