fgsdfgs
This commit is contained in:
BIN
ffmpeg-8.0.1-essentials_build.zip
Normal file
BIN
ffmpeg-8.0.1-essentials_build.zip
Normal file
Binary file not shown.
100
main.py
Normal file
100
main.py
Normal 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
131
main2.py
Normal 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)
|
||||||
Reference in New Issue
Block a user