add CEF browser alternative using cefpython3

This commit is contained in:
Shiz 2020-05-19 19:34:26 +02:00
parent f3b3327afe
commit 7aaef13e18
3 changed files with 123 additions and 4 deletions

View File

@ -6,7 +6,6 @@ Multi-DJ live streaming toolkit.
* `liquidsoap` (git master) * `liquidsoap` (git master)
* Python 3.6 * Python 3.6
- `trio_cdp` - `trio_cdp` (for CDP-based `browser_source`)
- `pyzmq` - `cefpython3` (for CEF-based `browser_source`)
- `twitchio`
* `ffmpeg` (with ZMQ support) * `ffmpeg` (with ZMQ support)

View File

@ -1,2 +1,2 @@
from .base import OBSProfile, OBSSource, OBSScene from .base import OBSProfile, OBSSource, OBSScene
from . import browser, scene, text, video from . import cef_browser, scene, text, video

View File

@ -0,0 +1,120 @@
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 = None
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()
parent_window_handle = 0
window_info.SetAsOffscreen(parent_window_handle)
self.browser = cef.CreateBrowserSync(window_info=window_info, settings=browser_settings, url=self.url)
def run_browser(self):
self.browser.SetClientHandler(CEFHandler(self))
self.browser.SendFocusEvent(True)
self.browser.WasResized()
cef.MessageLoop()
cef.Shutdown()
def output_frames(self):
with open(self.outfile, 'wb') as f:
while True:
t = time.monotonic()
if self.frame:
f.write(self.frame)
time.sleep(1.0 / self.rate - (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):
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, probesize=8192
)],
runners=[(runner.run_browser, True), runner.output_frames]
)]