346 lines
8.8 KiB
PHP
346 lines
8.8 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* @author Sascha Nitsch (grumpydeveloper)
|
|
**/
|
|
|
|
namespace ScriptedBrowser;
|
|
|
|
/**
|
|
* Calculate bezier blending from 0...1
|
|
*
|
|
* @param float $t input value 0...1
|
|
* @return float
|
|
*/
|
|
function bezierBlend(float $t)
|
|
{
|
|
return $t * $t * (3.0 - 2.0 * $t);
|
|
}
|
|
|
|
/**
|
|
* main class
|
|
*/
|
|
class Main
|
|
{
|
|
/**
|
|
* control instance
|
|
*
|
|
* @var Control
|
|
*/
|
|
private $control;
|
|
|
|
/**
|
|
* display id
|
|
* @var int $display
|
|
*/
|
|
private $display;
|
|
|
|
/**
|
|
* recording process
|
|
* @var ?resource
|
|
*/
|
|
private $ffmpeg = null;
|
|
|
|
/**
|
|
* frame rate of recording
|
|
*
|
|
* @var int $frameRate
|
|
*/
|
|
private $frameRate = 30;
|
|
|
|
/**
|
|
* X mouse offset of browser to left edge
|
|
*
|
|
* @var int $mouseOffsetX
|
|
*/
|
|
private $mouseOffsetX = 0;
|
|
|
|
/**
|
|
* Y mouse offset of browser to top edge
|
|
*
|
|
* @var int $mouseOffsetY
|
|
*/
|
|
private $mouseOffsetY = 0;
|
|
|
|
/**
|
|
* mouse movement speed (pixel per second)
|
|
*
|
|
* @var int $mouseSpeed
|
|
*/
|
|
private $mouseSpeed = 500;
|
|
|
|
/**
|
|
* mouse position X
|
|
*
|
|
* @var int $mouseX
|
|
*/
|
|
private $mouseX = 0;
|
|
|
|
/**
|
|
* mouse position Y
|
|
*
|
|
* @var int $mouseY
|
|
*/
|
|
private $mouseY = 0;
|
|
|
|
/**
|
|
* typing speed in characters/s
|
|
*
|
|
* @var int $typeSpeed
|
|
*/
|
|
private $typeSpeed = 5;
|
|
|
|
/**
|
|
* var storage
|
|
*
|
|
* @var array<string, string>
|
|
*/
|
|
private $vars = [];
|
|
|
|
/**
|
|
* constructor
|
|
*
|
|
* @param string $serverHost server host name
|
|
* @param int $serverPort server port
|
|
* @param int $display VNC display id
|
|
*/
|
|
public function __construct($serverHost, $serverPort, $display = 6)
|
|
{
|
|
require 'commands/base.php';
|
|
$this->display = $display;
|
|
$this->control = new Control($serverHost, $serverPort, $display);
|
|
// open vnc session
|
|
$this->control->openVNC();
|
|
// open xdotool
|
|
$this->control->openInput();
|
|
// open/connect to browser
|
|
$sessionFile = @fopen('.session', 'r');
|
|
$session = null;
|
|
if ($sessionFile !== false) {
|
|
$session = trim(fgets($sessionFile));
|
|
fclose($sessionFile);
|
|
if ($session == '') {
|
|
$session = null;
|
|
}
|
|
}
|
|
$newSession = $this->control->openGeckoDriver($session);
|
|
if ($newSession !== $session) {
|
|
$sessionFile = fopen('.session', 'w');
|
|
fwrite($sessionFile, $newSession."\n");
|
|
fclose($sessionFile);
|
|
}
|
|
$this->control->mousemove(959, 499);
|
|
$windowpos = $this->control->fullscreen();
|
|
$this->control->mousemove(960, 500);
|
|
$this->mouseX = 960;
|
|
$this->mouseY = 500;
|
|
$this->mouseOffsetX = $windowpos['x'];
|
|
$this->mouseOffsetY = $windowpos['y'];
|
|
}
|
|
|
|
/**
|
|
* get frame rate
|
|
*
|
|
* @return int frame rate
|
|
*/
|
|
public function getFrameRate()
|
|
{
|
|
return $this->frameRate;
|
|
}
|
|
|
|
/**
|
|
* get mouse offset
|
|
*
|
|
* @return array<int> X,Y offset
|
|
*/
|
|
public function getMouseOffset()
|
|
{
|
|
return [$this->mouseOffsetX, $this->mouseOffsetY];
|
|
}
|
|
|
|
/**
|
|
* get mouse speed
|
|
*
|
|
* @return int speed in pixel/s
|
|
*/
|
|
public function getMouseSpeed()
|
|
{
|
|
return $this->mouseSpeed;
|
|
}
|
|
|
|
/**
|
|
* get mouse X coordinate
|
|
*
|
|
* @return int X coordinate
|
|
*/
|
|
public function getMouseX()
|
|
{
|
|
return $this->mouseX;
|
|
}
|
|
|
|
/**
|
|
* get mouse X coordinate
|
|
*
|
|
* @return int X coordinate
|
|
*/
|
|
public function getMouseY()
|
|
{
|
|
return $this->mouseY;
|
|
}
|
|
|
|
/**
|
|
* get typeing speed
|
|
*
|
|
* @return int typing speed
|
|
*/
|
|
public function getTypeSpeed()
|
|
{
|
|
return $this->typeSpeed;
|
|
}
|
|
|
|
public function run(string $instructions) : void
|
|
{
|
|
$this->control->mousemove(960, 500);
|
|
|
|
$steps = file_get_contents($instructions);
|
|
$steps = preg_replace('/[\r\n]/', '', $steps);
|
|
$steps = preg_replace('!/\*.*\*/!', '', $steps);
|
|
$plans = json_decode($steps, true);
|
|
if ($plans === null) {
|
|
echo "error reading config\n";
|
|
return;
|
|
}
|
|
$plans = $plans["plans"];
|
|
foreach ($plans as $plan) {
|
|
echo "running " . $plan['id'] . "\n";
|
|
$video = $plan['video'];
|
|
$framerate = $plan['framerate'];
|
|
$this->mouseSpeed = $plan['mousespeed'];
|
|
$this->typeSpeed = $plan['typespeed'];
|
|
$starttime = 0;
|
|
$this->vars = $plan['vars'];
|
|
foreach ($plan['actions'] as $action) {
|
|
if (is_string($action)) {
|
|
$tokens = explode(' ', $action, 2);
|
|
$action = [
|
|
'name' => $tokens[0],
|
|
'parameter' => (sizeof($tokens) < 2) ? '' : $tokens[1]
|
|
];
|
|
}
|
|
$actionname = $action['name'];
|
|
if ($actionname[0] === '-') {
|
|
continue;
|
|
}
|
|
if ($starttime > 0) {
|
|
echo $actionname;
|
|
fflush(STDOUT);
|
|
}
|
|
switch ($actionname) {
|
|
case 'startrecording':
|
|
$this->ffmpeg = popen(
|
|
"ffmpeg -video_size 1920x1080 -framerate $framerate -f x11grab -i :" . $this->display . ".0 -c:v libx264 -preset ultrafast -qp 0 $video -y 2>>/dev/null > /dev/null",
|
|
"w"
|
|
);
|
|
$starttime = hrtime(true);
|
|
echo 'start recording';
|
|
break;
|
|
case 'stoprecording':
|
|
$this->stopRecording();
|
|
$starttime = 0;
|
|
echo "\n";
|
|
break;
|
|
default:
|
|
$functionName = '\\ScriptedBrowser\\Commands\\' . $actionname;
|
|
if (!function_exists($functionName)) {
|
|
$file = 'scriptedbrowser/commands/' . $actionname . '.php';
|
|
if (file_exists($file)) {
|
|
require_once($file);
|
|
}
|
|
}
|
|
if (function_exists($functionName)) {
|
|
if (!$functionName($this, $this->control, $action)) {
|
|
echo "\r" . $action['name'] . " ". $action['parameter'] . " failed\n";
|
|
$this->stopRecording();
|
|
return;
|
|
}
|
|
} else {
|
|
echo "unsup " . $action['name'] . " ". array_key_exists('parameter', $action) ? $action['parameter'] : ''. "\n";
|
|
$this->stopRecording();
|
|
return;
|
|
}
|
|
}
|
|
if ($starttime > 0) {
|
|
$now = hrtime(true);
|
|
$d = ($now - $starttime);
|
|
$sec = $d / 1000000000;
|
|
$frame = ($sec - floor($sec)) * $framerate;
|
|
printf(" -> %d:%.0f (%.3f)\n", $sec, $frame, $sec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* set mouse X position (internal state only)
|
|
*
|
|
* @param int $mouseX new mouse X position
|
|
* @return void
|
|
*/
|
|
public function setMouseX($mouseX)
|
|
{
|
|
$this->mouseX = $mouseX;
|
|
}
|
|
|
|
/**
|
|
* set mouse Y position (internal state only)
|
|
*
|
|
* @param int $mouseY new mouse Y position
|
|
* @return void
|
|
*/
|
|
public function setMouseY($mouseY)
|
|
{
|
|
$this->mouseY = $mouseY;
|
|
}
|
|
|
|
/**
|
|
* programmtically set var
|
|
*
|
|
* @param string $name name to set
|
|
* @param string $value value to set
|
|
* @return void
|
|
*/
|
|
public function setVar($name, $value)
|
|
{
|
|
$this->vars[$name] = $value;
|
|
}
|
|
|
|
/**
|
|
* stop recording
|
|
*
|
|
* @return void
|
|
*/
|
|
public function stopRecording()
|
|
{
|
|
if ($this->ffmpeg !== null) {
|
|
fwrite($this->ffmpeg, "q");
|
|
pclose($this->ffmpeg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* replace vars in input string
|
|
*
|
|
* @param string $input input string
|
|
* @return string input string with var replacements
|
|
*/
|
|
public function vars(string $input) : string
|
|
{
|
|
// find ${...}
|
|
while (preg_match("/\\$\\{([^\\}]+)\\}/", $input, $matches) == 1) {
|
|
$replacement = $this->vars[$matches[1]];
|
|
$input = str_replace($matches[0], $replacement, $input);
|
|
}
|
|
return $input;
|
|
}
|
|
}
|