8 * @package Adapter_Cutycapt
9 * @author Christian Weiske <cweiske@cweiske.de>
10 * @copyright 2014 Christian Weiske
11 * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12 * @link http://cweiske.de/phancap.htm
17 * Screenshot rendering using the "cutycapt" command line tool.
20 * @package Adapter_Cutycapt
21 * @author Christian Weiske <cweiske@cweiske.de>
22 * @copyright 2014 Christian Weiske
23 * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
24 * @version Release: @package_version@
25 * @link http://cweiske.de/phancap.htm
27 class Adapter_Cutycapt
39 protected $lockFile = null;
42 * Check if all dependencies are available.
44 * @return mixed TRUE if all is fine, array with error messages otherwise
46 public function isAvailable()
48 $old = error_reporting(error_reporting() & ~E_STRICT);
50 if (\System::which('xvfb-run') === false) {
51 $arErrors[] = '"xvfb-run" is not installed';
53 if (\System::which('cutycapt') === false) {
54 $arErrors[] = '"cutycapt" is not installed';
56 if (\System::which('convert') === false) {
57 $arErrors[] = '"convert" (imagemagick) is not installed';
59 if (\System::which('timeout') === false) {
60 $arErrors[] = '"timeout" (GNU coreutils) is not installed';
63 error_reporting($old);
64 if (count($arErrors)) {
72 * Render a website screenshot
74 * @param Image $img Image configuration
75 * @param Options $options Screenshot configuration
78 * @throws \Exception When something fails
80 public function render(Image $img, Options $options)
82 $format = $options->values['sformat'];
83 if ($format == 'jpg') {
87 $maxWaitTime = 30;//seconds
88 if (isset($this->config->cutycapt['maxWaitTime'])) {
89 $maxWaitTime = (int) $this->config->cutycapt['maxWaitTime'];
93 if (isset($this->config->cutycapt['parameters'])) {
94 $parameters = $this->config->cutycapt['parameters'];
97 $serverNumber = $this->getServerNumber($options);
98 $tmpPath = $img->getPath() . '-tmp';
100 . ' --url=' . escapeshellarg($options->values['url'])
101 . ' --out-format=' . escapeshellarg($format)
102 . ' --out=' . escapeshellarg($tmpPath)
103 . ' --max-wait=' . (($maxWaitTime - 1) * 1000)
104 . ' --min-width=' . $options->values['bwidth'];
105 if ($options->values['bheight'] !== null) {
106 $cmd .= ' --min-height=' . $options->values['bheight'];
108 if (strlen($parameters) > 0) {
109 $cmd .= ' ' . $parameters;
112 $xvfbcmd = 'xvfb-run'
114 . ' --server-args="-screen 0, 1024x768x24"'
115 . ' --server-num=' . $serverNumber;
116 //cutycapt hangs sometimes - https://sourceforge.net/p/cutycapt/bugs/8/
117 // we kill it if it does not exit itself
118 Executor::runForSomeTime(
119 $xvfbcmd . ' ' . $cmd, $maxWaitTime, 'cutycapt'
122 //cutycapt does not report timeouts via exit status
123 // https://sourceforge.net/p/cutycapt/bugs/11/
124 if (!file_exists($tmpPath)) {
125 throw new \Exception('Error running cutycapt (wait timeout)', 1);
128 $this->resize($tmpPath, $img, $options);
132 * Get a free X server number.
134 * Each xvfb-run process needs its own free server number.
135 * Needed for multiple parallel requests.
137 * @return integer Server number
139 protected function getServerNumber()
141 //clean stale lock files
148 $f = $this->config->cacheDir . 'tmp-curlycapt-server-' . $num . '.lock';
149 $this->lockHdl = fopen($f, 'w');
150 if (flock($this->lockHdl, LOCK_EX | LOCK_NB)) {
151 $this->lockFile = $f;
155 fclose($this->lockHdl);
157 } while ($num < 200);
160 throw new \Exception('Too many requests running');
163 $this->lockFile = $f;
168 * Unlock lock file and clean up old lock files
172 public function cleanup()
174 if ($this->lockFile !== null && $this->lockHdl) {
175 flock($this->lockHdl, LOCK_UN);
176 unlink($this->lockFile);
180 $this->config->cacheDir . 'tmp-curlycapt-server-*.lock'
184 foreach ($lockFiles as $file) {
185 if ($now - filemtime($file) > 120) {
186 //delete stale lock file; probably something crashed.
193 * Convert an image to the given size.
195 * Target file is the path of $img.
197 * @param string $tmpPath Path of image to be scaled.
198 * @param Image $img Image configuration
199 * @param Options $options Screenshot configuration
203 protected function resize($tmpPath, Image $img, Options $options)
205 if ($options->values['sformat'] == 'pdf') {
207 rename($tmpPath, $img->getPath());
212 if ($options->values['smode'] == 'screen') {
213 $crop = ' -crop ' . $options->values['swidth']
214 . 'x' . $options->values['sheight']
218 $convertcmd = 'convert'
219 . ' ' . escapeshellarg($tmpPath)
220 . ' -resize ' . $options->values['swidth']
222 . ' ' . escapeshellarg($img->getPath());
223 Executor::run($convertcmd, 'convert');
228 * Set phancap configuration
230 * @param Config $config Phancap configuration
234 public function setConfig(Config $config)
236 $this->config = $config;