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