add CEF browser alternative using cefpython3
This commit is contained in:
parent
f3b3327afe
commit
7aaef13e18
|
@ -6,7 +6,6 @@ Multi-DJ live streaming toolkit.
|
|||
|
||||
* `liquidsoap` (git master)
|
||||
* Python 3.6
|
||||
- `trio_cdp`
|
||||
- `pyzmq`
|
||||
- `twitchio`
|
||||
- `trio_cdp` (for CDP-based `browser_source`)
|
||||
- `cefpython3` (for CEF-based `browser_source`)
|
||||
* `ffmpeg` (with ZMQ support)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .base import OBSProfile, OBSSource, OBSScene
|
||||
from . import browser, scene, text, video
|
||||
from . import cef_browser, scene, text, video
|
||||
|
|
|
@ -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]
|
||||
)]
|
Loading…
Reference in New Issue