Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ffmpeg.py to support m3u8 streams #116

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

tboy1337
Copy link

@tboy1337 tboy1337 commented Jan 7, 2025

example command

ffmpeg -re -reconnect 1 -reconnect_streamed 1 -i "https://s103.ipcamlive.com/streams/67hwgc4zuven56bzl/stream.m3u8" -f lavfi -i "anullsrc=channel_layout=stereo:sample_rate=44100" -c:v libx264 -preset veryfast -maxrate 3000k -bufsize 6000k -pix_fmt yuv420p -vf "scale=1280:720" -g:v 60 -c:a aac -b:a 128k -ar 44100 -f flv "rtmp://a.rtmp.youtube.com/live2/STREAM_KEY"

@tboy1337
Copy link
Author

tboy1337 commented Jan 8, 2025

import subprocess
import json
import sys

# M3U8 URL
M3U8_URL = "https://s103.ipcamlive.com/streams/67hwgc4zuven56bzl/stream.m3u8"

def detect_stream_info(input_url):
    """Detect stream information using FFprobe."""
    try:
        # Use FFprobe to get stream information in JSON format
        ffprobe_command = [
            "ffprobe",
            "-v", "quiet",
            "-print_format", "json",
            "-show_streams",
            input_url
        ]
        
        result = subprocess.run(ffprobe_command, capture_output=True, text=True)
        
        if result.returncode != 0:
            raise Exception(f"FFprobe failed with error: {result.stderr}")
        
        # Parse the JSON output
        probe_data = json.loads(result.stdout)
        
        # Find the video stream
        video_stream = next((stream for stream in probe_data['streams'] 
                           if stream['codec_type'] == 'video'), None)
        
        if not video_stream:
            raise Exception("No video stream found")
            
        # Extract width, height and frame rate
        width = int(video_stream.get('width', 1280))
        height = int(video_stream.get('height', 720))
        
        # Handle frame rate which might be in fractional format
        fps_str = video_stream.get('r_frame_rate', '30/1')
        if '/' in fps_str:
            num, den = map(int, fps_str.split('/'))
            fps = num / den
        else:
            fps = float(fps_str)
        
        # Validate dimensions
        if width <= 0 or height <= 0:
            print("Invalid dimensions detected, falling back to default resolution")
            width, height = 1280, 720
        
        # Ensure dimensions are divisible by 2
        if width % 2 != 0:
            width = (width // 2) * 2
        if height % 2 != 0:
            height = (height // 2) * 2
        
        print(f"Stream information detected:")
        print(f"Resolution: {width}x{height}")
        print(f"Frame rate: {fps} FPS")
        
        return fps, width, height
        
    except Exception as e:
        print(f"Error detecting stream info: {e}")
        print("Falling back to default values")
        return 30.0, 1280, 720

# Detect stream information
FRAME_RATE, WIDTH, HEIGHT = detect_stream_info(M3U8_URL)

# Replace with your YouTube Live stream key
YOUTUBE_STREAM_KEY = "STREAM_KEY"

# YouTube RTMPS URL
YOUTUBE_RTMPS_URL = f"rtmps://a.rtmps.youtube.com/live2/{YOUTUBE_STREAM_KEY}"

# Keyframe interval (2 * frame rate)
KEYFRAME_INTERVAL = 2 * FRAME_RATE

# FFmpeg command
ffmpeg_command = [
    "ffmpeg",
    "-re",  # Read input in real-time
    "-reconnect", "1",  # Enable reconnection
    "-reconnect_streamed", "1",  # Reconnect if the stream ends
    "-i", M3U8_URL,  # Input URL (video stream)
    "-f", "lavfi",  # Use the lavfi (Libavfilter) virtual input device
    "-i", "anullsrc=channel_layout=stereo:sample_rate=44100",  # Generate silent audio
    "-c:v", "libx264",  # Video codec
    "-preset", "veryfast",  # Encoding speed
    "-maxrate", "3000k",  # Max bitrate
    "-bufsize", "6000k",  # Buffer size
    "-pix_fmt", "yuv420p",  # Pixel format
    "-vf", f"scale={WIDTH}:{HEIGHT}:force_original_aspect_ratio=decrease",  # Maintain original resolution with aspect ratio protection
    "-g:v", str(int(KEYFRAME_INTERVAL)),  # Keyframe interval (2 * frame rate)
    "-c:a", "aac",  # Audio codec
    "-b:a", "128k",  # Audio bitrate
    "-ar", "44100",  # Audio sample rate
    "-f", "flv",  # Output format
    YOUTUBE_RTMPS_URL  # Output URL (RTMPS)
]

# Print the complete command for debugging
print("\nFFmpeg command:")
print(" ".join(ffmpeg_command))

# Run the FFmpeg command
try:
    subprocess.run(ffmpeg_command, check=True)
except subprocess.CalledProcessError as e:
    print(f"An error occurred: {e}")
    sys.exit(1)

@tboy1337
Copy link
Author

tboy1337 commented Jan 8, 2025

Switched from FFmpeg to FFprobe for stream detection
Uses JSON output format from FFprobe for more reliable parsing
Properly handles fractional frame rates
More robust error handling and validation
Cleaner JSON-based parsing instead of regex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant