mirror of
https://github.com/facefusion/facefusion.git
synced 2026-05-12 18:32:18 +02:00
make it work under macos
This commit is contained in:
+29
-2
@@ -15,8 +15,13 @@ SOURCE_FILE : str = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.a
|
||||
def is_windows() -> bool:
|
||||
return platform.system().lower() == 'windows'
|
||||
|
||||
def is_macos() -> bool:
|
||||
return platform.system().lower() == 'darwin'
|
||||
|
||||
if is_windows():
|
||||
VIDEO_FILE : str = 'C:\\Users\\info\\Downloads\\face8k.mp4'
|
||||
elif is_macos():
|
||||
VIDEO_FILE : str = '/Users/henry/Downloads/copy_face_instant.mp4'
|
||||
else:
|
||||
VIDEO_FILE : str = '/home/henry/Documents/examples/download.mp4'
|
||||
|
||||
@@ -27,7 +32,7 @@ def safe_print(text : str) -> None:
|
||||
except UnicodeEncodeError:
|
||||
print(text.encode('ascii', errors='replace').decode('ascii'))
|
||||
|
||||
MODES =\
|
||||
_ALL_MODES =\
|
||||
[
|
||||
'whip-mediamtx',
|
||||
'whip-python',
|
||||
@@ -38,12 +43,14 @@ MODES =\
|
||||
'ws-mjpeg'
|
||||
]
|
||||
|
||||
MODES = [ m for m in _ALL_MODES if not (is_macos() and m == 'whip-mediamtx') ]
|
||||
|
||||
|
||||
def start_api() -> subprocess.Popen:
|
||||
env = os.environ.copy()
|
||||
python_cmd = 'python' if is_windows() else 'python3'
|
||||
|
||||
if not is_windows():
|
||||
if not is_windows() and not is_macos():
|
||||
env['LD_LIBRARY_PATH'] = '/home/henry/local/lib:' + env.get('LD_LIBRARY_PATH', '')
|
||||
|
||||
proc = subprocess.Popen(
|
||||
@@ -103,12 +110,32 @@ def kill_port_windows(port : int) -> None:
|
||||
subprocess.run([ 'taskkill', '/F', '/PID', pid ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
|
||||
|
||||
def kill_port_macos(port : int) -> None:
|
||||
pids = set()
|
||||
|
||||
for proto in [ 'tcp', 'udp' ]:
|
||||
result = subprocess.run(
|
||||
[ 'lsof', '-ti', proto + ':' + str(port) ],
|
||||
capture_output = True, text = True
|
||||
)
|
||||
|
||||
for pid in result.stdout.split():
|
||||
if pid.isdigit():
|
||||
pids.add(pid)
|
||||
|
||||
for pid in pids:
|
||||
subprocess.run([ 'kill', '-9', pid ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
|
||||
|
||||
def kill_stale() -> None:
|
||||
ports = [ API_PORT, 8889, 8189, 9997, 8890, 8891, 8892 ]
|
||||
|
||||
if is_windows():
|
||||
for port in ports:
|
||||
kill_port_windows(port)
|
||||
elif is_macos():
|
||||
for port in ports:
|
||||
kill_port_macos(port)
|
||||
else:
|
||||
subprocess.run([ 'fuser', '-k', str(API_PORT) + '/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
subprocess.run([ 'fuser', '-k', '8889/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
|
||||
@@ -13,14 +13,14 @@ from facefusion.apis.endpoints.metrics import get_metrics, websocket_metrics
|
||||
from facefusion.apis.endpoints.ping import websocket_ping
|
||||
from facefusion.apis.endpoints.session import create_session, destroy_session, get_session, refresh_session
|
||||
from facefusion.apis.endpoints.state import get_state, set_state
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.common_helper import is_linux, is_windows
|
||||
from facefusion.apis.endpoints.stream import websocket_stream, websocket_stream_audio, websocket_stream_live, websocket_stream_mjpeg, websocket_stream_rtc, websocket_stream_rtc_relay, websocket_stream_whip, websocket_stream_whip_dc, websocket_stream_whip_py
|
||||
from facefusion.apis.middlewares.session import create_session_guard
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app : Starlette) -> AsyncGenerator[None, None]:
|
||||
if not is_windows():
|
||||
if is_linux():
|
||||
mediamtx.start()
|
||||
mediamtx.wait_for_ready()
|
||||
|
||||
@@ -45,7 +45,7 @@ async def lifespan(app : Starlette) -> AsyncGenerator[None, None]:
|
||||
|
||||
yield
|
||||
|
||||
if not is_windows():
|
||||
if is_linux():
|
||||
mediamtx.stop()
|
||||
|
||||
try:
|
||||
|
||||
@@ -12,6 +12,7 @@ from starlette.websockets import WebSocket
|
||||
|
||||
from facefusion import logger, session_context, session_manager, state_manager
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.apis.stream_helper import STREAM_AUDIO_RATE
|
||||
from facefusion.apis.api_helper import get_sec_websocket_protocol
|
||||
from facefusion.apis.session_helper import extract_access_token
|
||||
from facefusion import mediamtx
|
||||
@@ -158,7 +159,24 @@ async def websocket_stream_whip(websocket : WebSocket) -> None:
|
||||
await websocket.close()
|
||||
|
||||
|
||||
def run_fmp4_pipeline(latest_frame_holder : list, lock : threading.Lock, stop_event : threading.Event, output_chunks : List[bytes], output_lock : threading.Lock, audio_write_fd_holder : list) -> None:
|
||||
def run_audio_silence_feeder(audio_write_fd_holder : list, stop_event : threading.Event, audio_active_event : threading.Event) -> None:
|
||||
frame_bytes = STREAM_AUDIO_RATE // 50 * 2 * 2
|
||||
silence = b'\x00' * frame_bytes
|
||||
|
||||
while not stop_event.is_set():
|
||||
if not audio_active_event.is_set():
|
||||
fd = audio_write_fd_holder[0]
|
||||
|
||||
if fd > 0:
|
||||
try:
|
||||
_os.write(fd, silence)
|
||||
except OSError:
|
||||
break
|
||||
|
||||
time.sleep(0.02)
|
||||
|
||||
|
||||
def run_fmp4_pipeline(latest_frame_holder : list, lock : threading.Lock, stop_event : threading.Event, output_chunks : List[bytes], output_lock : threading.Lock, audio_write_fd_holder : list, audio_active_event : threading.Event) -> None:
|
||||
encoder = None
|
||||
audio_write_fd = -1
|
||||
reader_thread = None
|
||||
@@ -194,6 +212,8 @@ def run_fmp4_pipeline(latest_frame_holder : list, lock : threading.Lock, stop_ev
|
||||
audio_write_fd_holder[0] = audio_write_fd
|
||||
reader_thread = threading.Thread(target = read_fmp4_output, args = (encoder, output_chunks, output_lock), daemon = True)
|
||||
reader_thread.start()
|
||||
silence_thread = threading.Thread(target = run_audio_silence_feeder, args = (audio_write_fd_holder, stop_event, audio_active_event), daemon = True)
|
||||
silence_thread.start()
|
||||
logger.info('fmp4 encoder started ' + str(width) + 'x' + str(height), __name__)
|
||||
|
||||
feed_whip_frame(encoder, temp_vision_frame)
|
||||
@@ -222,7 +242,8 @@ async def websocket_stream_live(websocket : WebSocket) -> None:
|
||||
lock = threading.Lock()
|
||||
output_lock = threading.Lock()
|
||||
stop_event = threading.Event()
|
||||
worker = threading.Thread(target = run_fmp4_pipeline, args = (latest_frame_holder, lock, stop_event, output_chunks, output_lock, audio_write_fd_holder), daemon = True)
|
||||
audio_active_event = threading.Event()
|
||||
worker = threading.Thread(target = run_fmp4_pipeline, args = (latest_frame_holder, lock, stop_event, output_chunks, output_lock, audio_write_fd_holder, audio_active_event), daemon = True)
|
||||
worker.start()
|
||||
|
||||
try:
|
||||
@@ -245,6 +266,7 @@ async def websocket_stream_live(websocket : WebSocket) -> None:
|
||||
latest_frame_holder[0] = frame
|
||||
|
||||
if data[:2] != JPEG_MAGIC and audio_write_fd_holder[0] > 0:
|
||||
audio_active_event.set()
|
||||
feed_whip_audio(audio_write_fd_holder[0], data)
|
||||
|
||||
except Exception as exception:
|
||||
|
||||
@@ -6,6 +6,8 @@ from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from facefusion.common_helper import is_linux
|
||||
|
||||
|
||||
MEDIAMTX_WHIP_PORT : int = 8889
|
||||
MEDIAMTX_API_PORT : int = 9997
|
||||
@@ -56,9 +58,10 @@ def stop() -> None:
|
||||
|
||||
|
||||
def stop_stale() -> None:
|
||||
subprocess.run([ 'fuser', '-k', str(MEDIAMTX_WHIP_PORT) + '/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
subprocess.run([ 'fuser', '-k', '8189/udp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
subprocess.run([ 'fuser', '-k', str(MEDIAMTX_API_PORT) + '/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
if is_linux():
|
||||
subprocess.run([ 'fuser', '-k', str(MEDIAMTX_WHIP_PORT) + '/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
subprocess.run([ 'fuser', '-k', '8189/udp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
subprocess.run([ 'fuser', '-k', str(MEDIAMTX_API_PORT) + '/tcp' ], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
+9
-7
@@ -7,7 +7,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from typing import Dict, List, Optional, TypeAlias
|
||||
|
||||
from facefusion import logger
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.common_helper import is_macos, is_windows
|
||||
|
||||
RtcLib : TypeAlias = ctypes.CDLL
|
||||
WHEP_PORT : int = 8892
|
||||
@@ -87,6 +87,8 @@ def find_library() -> Optional[str]:
|
||||
|
||||
if is_windows():
|
||||
return os.path.join(bin_dir, 'windows-x64-openssl-h264-vp8-av1-opus-datachannel-0.24.1.dll')
|
||||
if is_macos():
|
||||
return os.path.join(bin_dir, 'macos-universal-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.dylib')
|
||||
return os.path.join(bin_dir, 'linux-x64-openssl-h264-vp8-av1-opus-libdatachannel-0.24.1.so')
|
||||
|
||||
|
||||
@@ -206,6 +208,8 @@ next_rtp_port : int = 16000
|
||||
|
||||
|
||||
def create_session(stream_path : str) -> None:
|
||||
global video_frame_count
|
||||
video_frame_count = 0
|
||||
sessions[stream_path] = {'viewers': [], 'tracks': [], 'rtp_port': 0, 'rtp_fd': None}
|
||||
|
||||
|
||||
@@ -288,6 +292,7 @@ def send_audio_to_viewers(stream_path : str, opus_data : bytes) -> None:
|
||||
|
||||
|
||||
send_start_time : float = 0
|
||||
video_frame_count : int = 0
|
||||
audio_pts : int = 0
|
||||
opus_enc = None
|
||||
audio_buffer : bytearray = bytearray()
|
||||
@@ -296,7 +301,7 @@ OPUS_FRAME_SAMPLES : int = 960
|
||||
|
||||
|
||||
def send_to_viewers(stream_path : str, data : bytes) -> None:
|
||||
global send_start_time
|
||||
global video_frame_count
|
||||
|
||||
session = sessions.get(stream_path)
|
||||
|
||||
@@ -308,11 +313,8 @@ def send_to_viewers(stream_path : str, data : bytes) -> None:
|
||||
if not viewers:
|
||||
return
|
||||
|
||||
if send_start_time == 0:
|
||||
send_start_time = _time.monotonic()
|
||||
|
||||
elapsed = _time.monotonic() - send_start_time
|
||||
timestamp = int(elapsed * 90000) & 0xFFFFFFFF
|
||||
timestamp = int(video_frame_count * 3000) & 0xFFFFFFFF
|
||||
video_frame_count += 1
|
||||
buf = ctypes.create_string_buffer(data)
|
||||
data_len = len(data)
|
||||
|
||||
|
||||
+7
-3
@@ -260,7 +260,7 @@
|
||||
</div>
|
||||
|
||||
<div class="video-area">
|
||||
<video id="outputVideo" autoplay playsinline></video>
|
||||
<video id="outputVideo" autoplay muted playsinline></video>
|
||||
</div>
|
||||
<video id="inputVideo" autoplay muted playsinline style="display:none;"></video>
|
||||
|
||||
@@ -415,7 +415,7 @@ function startStats() {
|
||||
prevBytes = bytes;
|
||||
prevFrames = frames;
|
||||
prevStatsTime = now;
|
||||
setStat('statFrames', report.framesReceived || 0);
|
||||
setStat('statFrames', report.framesDecoded || 0);
|
||||
|
||||
if (report.frameWidth && report.frameHeight) {
|
||||
setStat('statResolution', report.frameWidth + 'x' + report.frameHeight);
|
||||
@@ -822,7 +822,11 @@ async function connectWhep() {
|
||||
|
||||
pc.ontrack = function(event) {
|
||||
log('remote track: ' + event.track.kind + ' (' + Math.round(performance.now() - t0) + 'ms)', 'ok');
|
||||
document.getElementById('outputVideo').srcObject = event.streams[0];
|
||||
var video = document.getElementById('outputVideo');
|
||||
video.srcObject = event.streams[0];
|
||||
video.play().then(function() {
|
||||
video.muted = false;
|
||||
}).catch(function(e) { log('play error: ' + e.message, 'error'); });
|
||||
};
|
||||
|
||||
pc.addTransceiver('video', { direction: 'recvonly' });
|
||||
|
||||
Reference in New Issue
Block a user