121 lines
3.8 KiB
Python
121 lines
3.8 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 = 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]
|
|
)]
|