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;
 | |
|     }
 | |
| }
 | 
