This commit is contained in:
2026-02-10 14:35:18 +03:00
commit 4952195307
3 changed files with 231 additions and 0 deletions

Binary file not shown.

100
main.py Normal file
View File

@@ -0,0 +1,100 @@
import subprocess
import threading
import time
import shlex
import sys
from datetime import datetime
FFMPEG = "ffmpeg" # или r"C:\ffmpeg\bin\ffmpeg.exe"
# ВХОД: обычно listener (vMix = caller)
IN_URL = "srt://127.0.0.1:9000?mode=caller&reuseaddr=1"
# ВЫХОД: обычно caller (приемник = listener)
# OUT_URL = "srt://127.0.0.1:9001?mode=listener&latency=200000&transtype=live&connect_timeout=5000"
# OUT_URL = "srt://0.0.0.0:9001?mode=listener&transtype=live&connect_timeout=5000"
OUT_URL = "srt://0.0.0.0:9001?mode=listener&reuseaddr=1"
# Если хочешь, чтобы ffmpeg "держал" выход и ждал подключение (удобно для VLC):
# OUT_URL = "srt://0.0.0.0:9001?mode=listener&latency=200000&reuseaddr=1&transtype=live"
# тогда VLC открывай как caller: srt://127.0.0.1:9001?latency=200000
LOGLEVEL = "info" # debug для диагностики
def now():
return datetime.now().strftime("%H:%M:%S")
def reader_thread(pipe, prefix):
"""Читает stderr ffmpeg и печатает, чтобы не забивался буфер."""
try:
for line in iter(pipe.readline, ""):
if not line:
break
line = line.rstrip("\n")
if line:
print(f"[{now()}] {prefix} {line}")
except Exception:
pass
def build_cmd():
return [
FFMPEG,
"-hide_banner",
"-loglevel", LOGLEVEL,
# полезно для лайва
"-fflags", "+genpts",
"-flags", "low_delay",
# вход
"-i", IN_URL,
# без перекодирования (минимальная задержка/нагрузка)
"-c", "copy",
"-f", "mpegts",
# выход
OUT_URL,
]
def run_forever():
backoff = 1.0
backoff_max = 10.0
while True:
cmd = build_cmd()
print(f"[{now()}] Starting ffmpeg:\n {shlex.join(cmd) if hasattr(shlex,'join') else ' '.join(cmd)}")
try:
proc = subprocess.Popen(
cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
t = threading.Thread(target=reader_thread, args=(proc.stderr, "ffmpeg:"), daemon=True)
t.start()
rc = proc.wait()
print(f"[{now()}] ffmpeg exited with code {rc}")
except FileNotFoundError:
print(f"[{now()}] ERROR: ffmpeg not found. Set FFMPEG path correctly.")
sys.exit(2)
except Exception as e:
print(f"[{now()}] ERROR running ffmpeg: {e}")
rc = -1
# Если поток отвалился или приемник недоступен — ffmpeg завершится.
# Мы НЕ падаем, а перезапускаем.
sleep_s = backoff
print(f"[{now()}] Restarting in {sleep_s:.1f}s...\n")
time.sleep(sleep_s)
backoff = min(backoff * 1.5, backoff_max)
if __name__ == "__main__":
run_forever()

131
main2.py Normal file
View File

@@ -0,0 +1,131 @@
import ffmpeg
import threading
import time
import sys
from datetime import datetime
LOGLEVEL = "info" # поставь "debug" для диагностики
# 1) ВХОД от vMix: ffmpeg слушает, vMix звонит
IN_URL = "srt://0.0.0.0:9000?mode=listener&reuseaddr=1&transtype=live"
# 2) ЛОКАЛЬНЫЙ буфер (UDP внутри одного ПК)
# Любой свободный порт, лучше 10000+
LOCAL_UDP_OUT = "udp://127.0.0.1:10000?pkt_size=1316&fifo_size=5000000&overrun_nonfatal=1"
LOCAL_UDP_IN = "udp://127.0.0.1:10000?fifo_size=5000000&overrun_nonfatal=1"
# 3) ВЫХОД: вариант A (рекомендую для VLC/мобилы) — OUT listener, клиенты подключаются caller
OUT_URL = "srt://0.0.0.0:9001?mode=listener&reuseaddr=1&transtype=live"
# Если тебе нужен OUT caller на конкретное устройство, используй вместо этого:
# OUT_URL = "srt://10.10.1.238:9001?mode=caller&latency=200000&transtype=live&connect_timeout=5000"
def now():
return datetime.now().strftime("%H:%M:%S")
def pump_stderr(proc, tag):
"""Читает stderr, чтобы процесс не подвис из-за заполненного буфера."""
try:
for raw in iter(proc.stderr.readline, b""):
if not raw:
break
line = raw.decode("utf-8", errors="replace").rstrip("\n")
if line:
print(f"[{now()}] {tag}: {line}")
except Exception:
pass
def start_ingest():
"""
SRT-IN -> UDP localhost (не зависит от OUT!)
"""
stream = (
ffmpeg
.input(IN_URL)
.output(LOCAL_UDP_OUT, format="mpegts", codec="copy")
.global_args(
"-hide_banner",
"-loglevel", LOGLEVEL,
"-fflags", "+genpts",
"-flags", "low_delay",
)
)
proc = stream.run_async(pipe_stderr=True)
threading.Thread(target=pump_stderr, args=(proc, "INGEST"), daemon=True).start()
return proc
def start_egress():
"""
UDP localhost -> SRT-OUT
(можно падать/перезапускаться сколько угодно — IN не трогаем)
"""
stream = (
ffmpeg
.input(LOCAL_UDP_IN, format="mpegts")
.output(OUT_URL, format="mpegts", codec="copy")
.global_args(
"-hide_banner",
"-loglevel", LOGLEVEL,
"-fflags", "+genpts",
"-flags", "low_delay",
)
)
proc = stream.run_async(pipe_stderr=True)
threading.Thread(target=pump_stderr, args=(proc, "EGRESS"), daemon=True).start()
return proc
def run_forever():
# INGEST: держим почти “вечно”, перезапускаем только если упал вход
ingest_backoff = 1.0
ingest_backoff_max = 10.0
# EGRESS: можно перезапускать часто (если клиент/сеть отвалилась)
egress_backoff = 0.5
egress_backoff_max = 5.0
ingest_proc = None
while True:
# 1) Убедимся, что INGEST жив
if ingest_proc is None or ingest_proc.poll() is not None:
if ingest_proc is not None:
print(f"[{now()}] INGEST exited rc={ingest_proc.returncode}")
print(f"[{now()}] Restarting INGEST in {ingest_backoff:.1f}s...\n")
time.sleep(ingest_backoff)
ingest_backoff = min(ingest_backoff * 1.5, ingest_backoff_max)
print(f"[{now()}] Starting INGEST...")
try:
ingest_proc = start_ingest()
ingest_backoff = 1.0 # сброс после успешного запуска
except FileNotFoundError:
print(f"[{now()}] ERROR: ffmpeg.exe not found (PATH).")
sys.exit(2)
except Exception as e:
print(f"[{now()}] ERROR starting INGEST: {e}")
time.sleep(ingest_backoff)
continue
# 2) Поднимаем/держим EGRESS отдельно
print(f"[{now()}] Starting EGRESS...")
try:
egress_proc = start_egress()
except Exception as e:
print(f"[{now()}] ERROR starting EGRESS: {e}")
time.sleep(egress_backoff)
egress_backoff = min(egress_backoff * 1.5, egress_backoff_max)
continue
rc = egress_proc.wait()
print(f"[{now()}] EGRESS exited rc={rc}")
print(f"[{now()}] Restarting EGRESS in {egress_backoff:.1f}s...\n")
time.sleep(egress_backoff)
egress_backoff = min(egress_backoff * 1.5, egress_backoff_max)
if __name__ == "__main__":
try:
run_forever()
except KeyboardInterrupt:
sys.exit(0)