hemisphere/src/overlay/obs/cef_browser.py

123 lines
3.9 KiB
Python

import os
import shutil
import tempfile
import sys
import time
from cefpython3 import cefpython as cef
from ..filters import FFmpegInput, FFmpegChain, FFmpegFilter
from .base import OBSSource
sys.excepthook = cef.ExceptHook
class CEFHandler:
def __init__(self, capturer):
self.capturer = capturer
def OnLoadError(self, browser, frame, error_code, failed_url, **_):
print('load_error', error_code, failed_url)
if not frame.IsMain():
return
cef.PostTask(cef.TID_UI, self.capturer.exit, browser)
def GetViewRect(self, rect_out, **_):
# rect_out --> [x, y, width, height]
print('get_view_rect')
rect_out.extend([0, 0, self.capturer.dimensions[0], self.capturer.dimensions[1]])
return True
def OnPaint(self, browser, element_type, paint_buffer, **_):
print('paint')
if element_type == cef.PET_VIEW:
self.capturer.frame = paint_buffer.GetBytes(mode="rgba", origin="top-left")
class CEFBrowserCapturer:
def __init__(self, url, outfile, dimensions=(1280, 720), rate=10):
self.headless = True
self.dimensions = dimensions
self.url = url
self.outfile = outfile
self.frame = b'\x00' * 4 * self.dimensions[0] * self.dimensions[1]
self.rate = rate
settings = {
"windowless_rendering_enabled": True,
}
switches = {
"disable-gpu": "",
"disable-gpu-compositing": "",
"enable-begin-frame-scheduling": "",
"disable-surfaces": "",
}
browser_settings = {
"windowless_frame_rate": 30,
}
cef.Initialize(settings=settings, switches=switches)
window_info = cef.WindowInfo()
window_info.SetAsOffscreen(0)
self.browser = cef.CreateBrowserSync(window_info=window_info, settings=browser_settings, url=self.url)
def __del__(self):
self.browser.CloseBrowser()
cef.Shutdown()
def run_browser(self):
self.browser.SetClientHandler(CEFHandler(self))
self.browser.SendFocusEvent(True)
self.browser.WasResized()
cef.MessageLoop()
def output_frames(self):
with open(self.outfile, 'wb') as f:
frame_duration = 1.0 / self.rate
while True:
t = time.monotonic()
f.write(self.frame)
time.sleep(max(0.0, frame_duration - (time.monotonic() - t)))
def exit(self, browser=None):
browser = browser or self.browser
browser.CloseBrowser()
cef.QuitMessageLoop()
@OBSSource.type('browser_source')
class OBSBrowserSource(OBSSource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.local = False
self.url = None
self.width = 1280
self.height = 720
self.audio = False
self.tempdir = tempfile.mkdtemp()
def __del__(self):
shutil.rmtree(self.tempdir, ignore_errors=True)
def load(self, data):
self.name = data['name']
self.local = data['settings'].get('is_local_file', False)
if self.local:
self.url = data['settings']['local_file']
else:
self.url = data['settings']['url']
self.width = data['settings']['width']
self.height = data['settings']['height']
self.audio = data['settings'].get('reroute_audio', False)
def to_ffmpeg(self, scene, item):
fifo_path = os.path.join(self.tempdir, 'frame.bin')
os.mkfifo(fifo_path)
runner = CEFBrowserCapturer(self.url, fifo_path, dimensions=(self.width, self.height))
return [FFmpegChain(
inputs=[FFmpegInput(fifo_path,
f='rawvideo', r=runner.rate, s='{}x{}'.format(self.width, self.height), pix_fmt='rgba',
follow=1
)],
runners=[(runner.run_browser, True), runner.output_frames]
)]