import subprocess import os import sys import time import signal import io import threading from queue import Queue, Empty from PIL import Image from selenium import webdriver from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.core.os_manager import ChromeType class HeadlessWebStreamer: def __init__(self, website_url, music_url, stream_key, fps=20): self.website_url = website_url self.music_url = music_url self.stream_key = stream_key or os.getenv("YT_STREAM_KEY") self.youtube_url = "rtmp://a.rtmp.youtube.com/live2" self.fps = fps self.is_running = False self.driver = None self.process = None self.frame_queue = Queue(maxsize=5) signal.signal(signal.SIGINT, self._exit_gracefully) signal.signal(signal.SIGTERM, self._exit_gracefully) def _exit_gracefully(self, sig, frame): self.stop_stream() sys.exit(0) def init_browser(self): print("🌐 Инициализация Chromium...") try: options = ChromeOptions() options.add_argument('--headless=new') options.add_argument('--no-sandbox') options.add_argument('--disable-dev-shm-usage') options.add_argument('--disable-gpu') # Задаем "длинное" окно, чтобы захватить низ сайта options.add_argument('--window-size=1280,1200') options.binary_location = "/usr/bin/chromium" driver_path = ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install() self.driver = webdriver.Chrome(service=Service(driver_path), options=options) self.driver.get(self.website_url) time.sleep(5) return True except Exception as e: print(f"❌ Ошибка браузера: {e}") return False def start_stream(self): if not self.stream_key: print("❌ Ошибка: Ключ трансляции не найден!") return if not self.init_browser(): return # ИСПРАВЛЕННЫЙ ФИЛЬТР FFMPEG: # 1. scale=-1:720 — уменьшаем высоту до 720, сохраняя пропорции ширины # 2. pad=1280:720 — помещаем результат на холст 1280x720 (добавляем черные поля по бокам) video_filter = ( "scale=-1:720," "pad=1280:720:(1280-iw)/2:0:black," "setsar=1:1,setdar=16/9,format=yuv420p" ) ffmpeg_cmd = [ 'ffmpeg', '-y', '-f', 'image2pipe', '-vcodec', 'mjpeg', '-r', str(self.fps), '-i', '-', '-re', '-stream_loop', '-1', '-i', self.music_url, '-vf', video_filter, '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency', '-g', str(self.fps*2), '-b:v', '2500k', '-c:a', 'aac', '-b:a', '128k', '-ar', '44100', '-map', '0:v:0', '-map', '1:a:0', '-f', 'flv', f"{self.youtube_url}/{self.stream_key}" ] self.process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE, stdout=sys.stdout, stderr=sys.stdout) self.is_running = True def capture_worker(): while self.is_running: try: png = self.driver.get_screenshot_as_png() img = Image.open(io.BytesIO(png)).convert('RGB') if self.frame_queue.full(): try: self.frame_queue.get_nowait() except: pass self.frame_queue.put(img) except: break time.sleep(1/self.fps) threading.Thread(target=capture_worker, daemon=True).start() print(f"🚀 Стрим запущен. Формат: Авто-вписывание в 16:9.") while self.is_running: if self.process.poll() is not None: break try: img = self.frame_queue.get(timeout=10) img_byte_arr = io.BytesIO() img.save(img_byte_arr, format='JPEG', quality=85) self.process.stdin.write(img_byte_arr.getvalue()) self.process.stdin.flush() except (BrokenPipeError, OSError): break except Empty: continue self.stop_stream() def stop_stream(self): self.is_running = False if self.driver: self.driver.quit() if self.process: try: self.process.stdin.close() self.process.terminate() except: pass print("⏹️ Трансляция остановлена.") if __name__ == "__main__": MY_MUSIC = "https://dash.rid3usercontent.run.place/cdn/music/funk.mp3" WEBSITE = "https://status-lin.web.app/" target_music = os.getenv("MUSIC_URL", MY_MUSIC) stream_key = os.getenv("YT_STREAM_KEY") streamer = HeadlessWebStreamer(WEBSITE, target_music, stream_key) streamer.start_stream()