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)
|
* `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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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