🎉 Hello world
This commit is contained in:
commit
c8bbc35886
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/.idea
|
||||||
|
/main.php
|
||||||
|
/test.jpg
|
||||||
|
/pool/*
|
||||||
|
!/pool/.gitkeep
|
||||||
|
/vendor
|
||||||
|
/config.json
|
21
composer.json
Normal file
21
composer.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "shikiryu/webgobbler",
|
||||||
|
"description": "WebGobbler in PHP",
|
||||||
|
"type": "project",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Clément",
|
||||||
|
"email": "clement@desmidt.fr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"fabpot/goutte": "^4.0",
|
||||||
|
"ext-imagick": "*"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Shikiryu\\WebGobbler\\": "src/",
|
||||||
|
"Shikiryu\\WebGobbler\\Collector\\": "src/Collector"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1348
composer.lock
generated
Normal file
1348
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
config.json.template
Normal file
16
config.json.template
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"collectors": [],
|
||||||
|
"pool": {
|
||||||
|
"directory": "/home/user/webcollage/pool",
|
||||||
|
"nb_images": 10
|
||||||
|
},
|
||||||
|
"assembler": {
|
||||||
|
"sizex": 2000,
|
||||||
|
"sizey": 2000,
|
||||||
|
"mirror": false,
|
||||||
|
"emboss": true,
|
||||||
|
"invert": false,
|
||||||
|
"nbx": 5,
|
||||||
|
"nby": 5
|
||||||
|
}
|
||||||
|
}
|
0
pool/.gitkeep
Normal file
0
pool/.gitkeep
Normal file
37
src/Assembler.php
Normal file
37
src/Assembler.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler;
|
||||||
|
|
||||||
|
abstract class Assembler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Shikiryu\WebGobbler\Pool
|
||||||
|
*/
|
||||||
|
protected $pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Shikiryu\WebGobbler\Config
|
||||||
|
*/
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembler constructor.
|
||||||
|
*
|
||||||
|
* @param \Shikiryu\WebGobbler\Pool $pool
|
||||||
|
* @param \Shikiryu\WebGobbler\Config $config
|
||||||
|
*/
|
||||||
|
public function __construct(Pool $pool, Config $config)
|
||||||
|
{
|
||||||
|
$this->pool = $pool;
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
abstract public function saveTo($file);
|
||||||
|
|
||||||
|
abstract public function display();
|
||||||
|
}
|
48
src/Assembler/Mosaic.php
Normal file
48
src/Assembler/Mosaic.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
use Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
class Mosaic extends Assembler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \ImagickException
|
||||||
|
*/
|
||||||
|
public function saveTo($file)
|
||||||
|
{
|
||||||
|
$final_image = new \Imagick();
|
||||||
|
$final_image->setColorspace(\Imagick::COLORSPACE_RGB);
|
||||||
|
$final_image->newImage($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey'), 'none');
|
||||||
|
$image_size_x = $this->config->get('assembler.sizex') / $this->config->get('assembler.nbx');
|
||||||
|
$image_size_y = $this->config->get('assembler.sizey') / $this->config->get('assembler.nby');
|
||||||
|
for ($y = 0; $y < $this->config->get('assembler.nby'); $y++) {
|
||||||
|
for ($x = 0; $x < $this->config->get('assembler.nbx'); $x++) {
|
||||||
|
$image = new \Imagick($this->pool->getImage());
|
||||||
|
if ($image->getColorspace() !== \Imagick::COLORSPACE_RGB) {
|
||||||
|
$image->setColorspace(\Imagick::COLORSPACE_RGB);
|
||||||
|
}
|
||||||
|
$image->scaleImage($image_size_x, $image_size_y, true);
|
||||||
|
$final_image->compositeImage($image, \Imagick::COMPOSITE_DEFAULT, $x * $image_size_x, $y * $image_size_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.mirror')) {
|
||||||
|
$final_image->flopImage();
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.emboss')) {
|
||||||
|
$final_image->embossImage(0, 1);
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.invert')) {
|
||||||
|
$final_image->negateImage(false);
|
||||||
|
}
|
||||||
|
$final_image->writeImage($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display()
|
||||||
|
{
|
||||||
|
// TODO: Implement display() method.
|
||||||
|
}
|
||||||
|
}
|
44
src/Assembler/Simple.php
Normal file
44
src/Assembler/Simple.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
use Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
class Simple extends Assembler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
*/
|
||||||
|
public function saveTo($file)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$image = new \Imagick($this->pool->getImage());
|
||||||
|
if ($image->getColorspace() !== \Imagick::COLORSPACE_RGB) {
|
||||||
|
$image->setColorspace(\Imagick::COLORSPACE_RGB);
|
||||||
|
}
|
||||||
|
$size = $image->getSize();
|
||||||
|
$imagex = $size['columns'];
|
||||||
|
$imagey = $size['rows'];
|
||||||
|
if ($imagex !== $this->config->get('assembler.sizex') || $imagey !== $this->config->get('assembler.sizey')) {
|
||||||
|
$image->thumbnailImage($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey'));
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.mirror')) {
|
||||||
|
$image->flopImage();
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.emboss')) {
|
||||||
|
$image->embossImage(0, 1);
|
||||||
|
}
|
||||||
|
if (true === $this->config->get('assembler.invert')) {
|
||||||
|
$image->flipImage();
|
||||||
|
}
|
||||||
|
$image->writeImage($file);
|
||||||
|
} catch (\ImagickException $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display()
|
||||||
|
{
|
||||||
|
// TODO: Implement display() method.
|
||||||
|
}
|
||||||
|
}
|
177
src/Assembler/Superpose.php
Normal file
177
src/Assembler/Superpose.php
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
use Imagick;
|
||||||
|
use Shikiryu\WebGobbler\Assembler;
|
||||||
|
|
||||||
|
class Superpose extends Assembler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Imagick
|
||||||
|
*/
|
||||||
|
private $current_image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \ImagickException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function saveTo($file)
|
||||||
|
{
|
||||||
|
$image_to_superpose = new \Imagick($this->pool->getImage());
|
||||||
|
$image_x = $image_to_superpose->getImageWidth();
|
||||||
|
$image_y = $image_to_superpose->getImageHeight();
|
||||||
|
if ($image_x < 32 || $image_y < 32) {
|
||||||
|
throw new \Exception('Image too small.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->current_image = $image_to_superpose;
|
||||||
|
|
||||||
|
$nb_images = $this->config->get('assembler.superpose.min_num_images', 5);
|
||||||
|
for ($i = 0; $i < $nb_images; $i++) {
|
||||||
|
$this->current_image = $this->superpose();
|
||||||
|
}
|
||||||
|
$this->current_image->writeImage($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function superpose()
|
||||||
|
{
|
||||||
|
return $this->superposeOneImage($this->current_image, new \Imagick($this->pool->getImage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Superposes one image in the current image.
|
||||||
|
* This method must only be called by the assembler_superpose thread !
|
||||||
|
*
|
||||||
|
* @param \Imagick $current_image
|
||||||
|
* @param \Imagick $image_to_superpose
|
||||||
|
*
|
||||||
|
* @return \Imagick
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function superposeOneImage($current_image, $image_to_superpose)
|
||||||
|
{
|
||||||
|
if ($this->config->get('assembler.superpose.variante', 0) === 1) {
|
||||||
|
$current_image->brightnessContrastImage(0.99, 1);
|
||||||
|
}
|
||||||
|
if ($image_to_superpose->getColorspace() !== \Imagick::COLORSPACE_RGB) {
|
||||||
|
$image_to_superpose->setColorspace(\Imagick::COLORSPACE_RGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the image is bigger than current image, scale it down to 1/2 of final picture dimensions
|
||||||
|
# (while keeping its ratio)
|
||||||
|
$image_x = $image_to_superpose->getImageWidth();
|
||||||
|
$image_y = $image_to_superpose->getImageHeight();
|
||||||
|
if ($image_x > $this->config->get('assembler.sizex') || $image_y > $this->config->get('assembler.sizey')) {
|
||||||
|
try {
|
||||||
|
$image_to_superpose->scaleImage($this->config->get('assembler.sizex') / 2, $this->config->get('assembler.sizey') / 2, true);
|
||||||
|
} catch (\ImagickException $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scale down/up image if required.
|
||||||
|
/*scaleValue = self.CONFIG["assembler.superpose.scale"]
|
||||||
|
if str(scaleValue) != "1.0":
|
||||||
|
try:
|
||||||
|
imageToSuperpose.thumbnail((int(float(imagex)*scaleValue),int(float(imagey)*scaleValue)),Image.ANTIALIAS)
|
||||||
|
except TypeError: #TypeError: unsubscriptable object ; Spurious exception in PIL. :-(
|
||||||
|
raise BadImage
|
||||||
|
(imagex,imagey) = imageToSuperpose.size*/
|
||||||
|
|
||||||
|
# Compensate for poorly-contrasted images on the web
|
||||||
|
/*try:
|
||||||
|
imageToSuperpose = ImageOps.autocontrast(imageToSuperpose)
|
||||||
|
except TypeError: # Aaron tells me that this exception occurs with PNG images.
|
||||||
|
raise BadImage*/
|
||||||
|
|
||||||
|
# Some image are too white.
|
||||||
|
# For example, the photo of a coin on a white background.
|
||||||
|
# These picture degrad the quality of the final image.
|
||||||
|
# We try to dectect them by summing the value of the pixels
|
||||||
|
# on the borders.
|
||||||
|
# If the image is considered "white", we invert it.
|
||||||
|
/*pixelcount = 1 # 1 to prevent divide by zero error.
|
||||||
|
valuecount = 0
|
||||||
|
try:
|
||||||
|
for x in range(0,imagex,20):
|
||||||
|
(r,g,b) = imageToSuperpose.getpixel((x,5))
|
||||||
|
valuecount += r+g+b
|
||||||
|
(r,g,b) = imageToSuperpose.getpixel((x,imagey-5))
|
||||||
|
valuecount += r+g+b
|
||||||
|
pixelcount += 2
|
||||||
|
for y in range(0,imagey,20):
|
||||||
|
(r,g,b) = imageToSuperpose.getpixel((5,y))
|
||||||
|
valuecount += r+g+b
|
||||||
|
(r,g,b) = imageToSuperpose.getpixel((imagex-5,y))
|
||||||
|
valuecount += r+g+b
|
||||||
|
pixelcount += 2
|
||||||
|
except TypeError: #unsubscriptable object Arrggghh... not again !
|
||||||
|
raise BadImage # Aggrrreeeuuuu...
|
||||||
|
|
||||||
|
# If the average r+g+b of the border pixels exceed this value,
|
||||||
|
# we consider the image is too white, and we invert it.
|
||||||
|
if (100*(valuecount/(255*3))/pixelcount)>60: # Cut at 60%. (100% is RGB=(255,255,255))
|
||||||
|
imageToSuperpose = ImageOps.invert(imageToSuperpose)*/
|
||||||
|
|
||||||
|
$paste_coords_x = random_int(-$image_x, $this->config->get('assembler.sizex'));
|
||||||
|
$paste_coords_y = random_int(-$image_y, $this->config->get('assembler.sizey'));
|
||||||
|
|
||||||
|
# Darken image borders
|
||||||
|
$image_to_superpose = $this->darkenImageBorder($image_to_superpose, 30);
|
||||||
|
|
||||||
|
if ($this->config->get('assembler.superpose.randomrotation', false)) {
|
||||||
|
$image_to_superpose->rotateImage('none', random_int(0, 359));
|
||||||
|
$image_to_superpose = $this->darkenImageBorder($image_to_superpose, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask_image = ImageOps.autocontrast(imageToSuperpose.convert('L'))
|
||||||
|
|
||||||
|
// if (self.CONFIG["assembler.superpose.variante"]==1) and (random.randint(0,100)<5): # Invert the transparency of 5% of the images (Except if we are in variante 1 mode)
|
||||||
|
// mask_image = ImageOps.invert(mask_image)
|
||||||
|
$current_image->compositeImage($image_to_superpose, \Imagick::COMPOSITE_DEFAULT, $paste_coords_x, $paste_coords_y);
|
||||||
|
|
||||||
|
if ($this->config->get('assembler.superpose.variante') === 0) {
|
||||||
|
$current_image->equalizeImage();
|
||||||
|
} else {
|
||||||
|
$current_image->autoLevelImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $current_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Imagick $image
|
||||||
|
* @param int|null $border_size
|
||||||
|
*
|
||||||
|
* @return \Imagick
|
||||||
|
*/
|
||||||
|
private function darkenImageBorder(Imagick $image, $border_size = null)
|
||||||
|
{
|
||||||
|
if (null === $border_size) {
|
||||||
|
$border_size = $this->config->get('assembler.superpose.bordersmooth');
|
||||||
|
}
|
||||||
|
|
||||||
|
$image_x = $image->getImageWidth();
|
||||||
|
$image_y = $image->getImageHeight();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $border_size; $i++) {
|
||||||
|
$draw = new \ImagickDraw();
|
||||||
|
$draw->setStrokeOpacity(($border_size-$i)/$border_size);
|
||||||
|
$draw->setStrokeWidth(1);
|
||||||
|
$draw->setStrokeColor('black');
|
||||||
|
$draw->setFillColor('none');
|
||||||
|
$draw->rectangle($i, $i, $image_x - $i, $image_y - $i);
|
||||||
|
$image->drawImage($draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display()
|
||||||
|
{
|
||||||
|
// TODO: Implement display() method.
|
||||||
|
}
|
||||||
|
}
|
120
src/Collector.php
Normal file
120
src/Collector.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler;
|
||||||
|
|
||||||
|
abstract class Collector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Collector constructor.
|
||||||
|
*
|
||||||
|
* @param \Shikiryu\WebGobbler\Pool $pool
|
||||||
|
*/
|
||||||
|
public function __construct(Pool $pool)
|
||||||
|
{
|
||||||
|
$this->pool = $pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPoolDirectory()
|
||||||
|
{
|
||||||
|
return sprintf('%s/%s', $this->pool->getPoolDirectory(), $this->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
abstract public function getRandomImage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $number
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
abstract public function getRandomImages(int $number);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random word.
|
||||||
|
* This method can be used by all derived classes.
|
||||||
|
* Usefull to get random result from search engines when you do not have
|
||||||
|
* a dictionnary at hand.
|
||||||
|
* The generated word can be a number (containing only digits),
|
||||||
|
* a word (containing only letters) or both mixed.
|
||||||
|
* Output: string (a random word)
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function generateRandomWord()
|
||||||
|
{
|
||||||
|
$word = '1';
|
||||||
|
try {
|
||||||
|
if (random_int(0, 100) < 30) { // Sometimes use only digits
|
||||||
|
if (random_int(0, 100) < 30) {
|
||||||
|
$word = random_int(1, 999);
|
||||||
|
} else {
|
||||||
|
$word = random_int(1, 999999);
|
||||||
|
}
|
||||||
|
} else { // Generate a word containing letters
|
||||||
|
$word = '';
|
||||||
|
$charset = 'abcdefghijklmnopqrstuvwxyz'; // Search for random word containing letter only.
|
||||||
|
if (random_int(0, 100) < 60) { // Sometimes include digits with letters
|
||||||
|
$charset = 'abcdefghijklmnopqrstuvwxyz' . 'abcdefghijklmnopqrstuvwxyz' . '0123456789'; // *2 to have more letters than digits
|
||||||
|
}
|
||||||
|
$charset = str_split($charset);
|
||||||
|
for ($i = 0, $l = random_int(2, 5); $i <= $l; $i++) { // Only generate short words (2 to 5 characters)
|
||||||
|
$word .= $charset[array_rand($charset)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return $word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $parts
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function reverse_url(array $parts) {
|
||||||
|
if (array_key_exists('query', $parts) && is_array($parts['query'])) {
|
||||||
|
$parts['query'] = http_build_query($parts['query']);
|
||||||
|
}
|
||||||
|
return (isset($parts['scheme']) ? "{$parts['scheme']}:" : '') .
|
||||||
|
((isset($parts['user']) || isset($parts['host'])) ? '//' : '') .
|
||||||
|
(isset($parts['user']) ? "{$parts['user']}" : '') .
|
||||||
|
(isset($parts['pass']) ? ":{$parts['pass']}" : '') .
|
||||||
|
(isset($parts['user']) ? '@' : '') .
|
||||||
|
(isset($parts['host']) ? "{$parts['host']}" : '') .
|
||||||
|
(isset($parts['port']) ? ":{$parts['port']}" : '') .
|
||||||
|
(isset($parts['path']) ? "{$parts['path']}" : '') .
|
||||||
|
(isset($parts['query']) ? "?{$parts['query']}" : '') .
|
||||||
|
(isset($parts['fragment']) ? "#{$parts['fragment']}" : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Shikiryu\WebGobbler\Pool $pool
|
||||||
|
*
|
||||||
|
* @return \Shikiryu\WebGobbler\Collector[]
|
||||||
|
*/
|
||||||
|
public static function getAllCollectors(Pool $pool)
|
||||||
|
{
|
||||||
|
$collectors = [];
|
||||||
|
foreach (glob(__DIR__ . '/Collector/*.php') as $class) {
|
||||||
|
$classname = sprintf('Shikiryu\\WebGobbler\\Collector\\%s', basename($class, '.php'));
|
||||||
|
$tmp_class = new $classname($pool);
|
||||||
|
if (is_subclass_of($tmp_class, __CLASS__)) {
|
||||||
|
$collectors[] = $tmp_class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $collectors;
|
||||||
|
}
|
||||||
|
}
|
65
src/Collector/DeviantartCollector.php
Normal file
65
src/Collector/DeviantartCollector.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
use Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
class DeviantartCollector extends Collector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public const RANDOM_URL = 'https://www.deviantart.com/popular/deviations';
|
||||||
|
/**
|
||||||
|
* Regular expression to extract the maximum deviantionID from homepage
|
||||||
|
*/
|
||||||
|
public const RE_ALLDEVIATIONID = '/href="https:\/\/www.deviantart.com\/[^\/]+\/art\/([^"]+)"/m';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getRandomImage()
|
||||||
|
{
|
||||||
|
return $this->getRandomImages(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $number
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getRandomImages(int $number = 1)
|
||||||
|
{
|
||||||
|
$html = file_get_contents(self::RANDOM_URL);
|
||||||
|
preg_match_all(self::RE_ALLDEVIATIONID, $html, $deviant_ids);
|
||||||
|
$deviant_ids = array_map(static function ($deviant_id) {
|
||||||
|
$array = explode('-', $deviant_id);
|
||||||
|
|
||||||
|
return (int)end($array);
|
||||||
|
}, $deviant_ids[1]);
|
||||||
|
$deviant_ids = array_unique($deviant_ids);
|
||||||
|
|
||||||
|
$index_to_download = array_rand($deviant_ids, $number);
|
||||||
|
if (!is_array($index_to_download)) {
|
||||||
|
$index_to_download = [$index_to_download];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($index_to_download as $deviant_id) {
|
||||||
|
$client = new Client();
|
||||||
|
$crawler = $client->request('GET', 'https://www.deviantart.com/deviation/'.$deviant_ids[$deviant_id]);
|
||||||
|
$img_url = $crawler->filter('[data-hook="art_stage"] img')->eq(0)->attr('src');
|
||||||
|
file_put_contents($this->getPoolDirectory() . '/' . basename(parse_url($img_url, PHP_URL_PATH)), file_get_contents($img_url));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $number; // Fixme
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'collector_deviantart';
|
||||||
|
}
|
||||||
|
}
|
43
src/Collector/LocalCollector.php
Normal file
43
src/Collector/LocalCollector.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
use Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
class LocalCollector extends Collector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $directory_to_scan = '';
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $filepaths = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|void
|
||||||
|
*/
|
||||||
|
public function getRandomImage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'collector_local';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $number
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getRandomImages(int $number)
|
||||||
|
{
|
||||||
|
// TODO: Implement getRandomImages() method.
|
||||||
|
}
|
||||||
|
}
|
61
src/Collector/YahooImageCollector.php
Normal file
61
src/Collector/YahooImageCollector.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
use Goutte\Client;
|
||||||
|
use Shikiryu\WebGobbler\Collector;
|
||||||
|
|
||||||
|
class YahooImageCollector extends Collector
|
||||||
|
{
|
||||||
|
public const SEARCH_URL = 'https://images.search.yahoo.com/search/images?p=%s';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'collector_yahooimage';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getRandomImage()
|
||||||
|
{
|
||||||
|
$word_to_search = $this->generateRandomWord();
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$crawler = $client->request('GET', sprintf(self::SEARCH_URL, $word_to_search));
|
||||||
|
$imgs = $crawler->filter('noscript img');
|
||||||
|
if ($imgs->count() < 20) {
|
||||||
|
return $this->getRandomImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$img_url = $imgs->eq(random_int(0, $imgs->count() - 1))->attr('src');
|
||||||
|
$parsed_url = parse_url( $img_url );
|
||||||
|
parse_str( $parsed_url['query'] , $url_vars );
|
||||||
|
$name = $url_vars['id'];
|
||||||
|
unset($url_vars['w'], $url_vars['h']);
|
||||||
|
$parsed_url['query'] = $url_vars;
|
||||||
|
$img_url = $this->reverse_url($parsed_url);
|
||||||
|
|
||||||
|
file_put_contents($this->getPoolDirectory() . '/'. $name . '.jpg', file_get_contents($img_url));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $number
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getRandomImages(int $number)
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
for ($i = 0; $i < $number; $i++) {
|
||||||
|
$count += $this->getRandomImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
72
src/Config.php
Normal file
72
src/Config.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
protected $data = null;
|
||||||
|
|
||||||
|
protected $cache = [];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config constructor.
|
||||||
|
* @throws \JsonException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __construct($config_file)
|
||||||
|
{
|
||||||
|
if (!is_readable($config_file)) {
|
||||||
|
throw new \Exception('Config file doesn\'t exist or is unreadable.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data = json_decode(file_get_contents($config_file), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @param null $default
|
||||||
|
*
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
public function get($key, $default = null)
|
||||||
|
{
|
||||||
|
if ($this->has($key)) {
|
||||||
|
return $this->cache[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has($key)
|
||||||
|
{
|
||||||
|
// Check if already cached
|
||||||
|
if (isset($this->cache[$key])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$segments = explode('.', $key);
|
||||||
|
$root = $this->data;
|
||||||
|
|
||||||
|
// nested case
|
||||||
|
foreach ($segments as $segment) {
|
||||||
|
if (array_key_exists($segment, $root)) {
|
||||||
|
$root = $root[$segment];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cache for the given key
|
||||||
|
$this->cache[$key] = $root;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
109
src/Pool.php
Normal file
109
src/Pool.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Shikiryu\WebGobbler;
|
||||||
|
|
||||||
|
class Pool
|
||||||
|
{
|
||||||
|
protected $file_list = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Shikiryu\WebGobbler\Collector[]
|
||||||
|
*/
|
||||||
|
protected $collectors = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $pool_directory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $nb_images = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool constructor.
|
||||||
|
*
|
||||||
|
* @param array $config
|
||||||
|
*/
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$collectors = [];
|
||||||
|
if (array_key_exists('collectors', $config)) {
|
||||||
|
$collectors = $config['collectors'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$all_collectors = Collector::getAllCollectors($this);
|
||||||
|
if (empty($collectors)) {
|
||||||
|
$this->collectors = $all_collectors;
|
||||||
|
} else {
|
||||||
|
foreach ($all_collectors as $collector) {
|
||||||
|
if (in_array($collector->getName(), $collectors, true)) {
|
||||||
|
$this->collectors[] = $collector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pool_dir = $config['directory'];
|
||||||
|
if (!is_dir($pool_dir) && !mkdir($pool_dir) && !is_dir($pool_dir)) {
|
||||||
|
throw new \RuntimeException(sprintf('Directory "%s" was not created', $pool_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pool_directory = $pool_dir;
|
||||||
|
|
||||||
|
$this->nb_images = $config['nb_images']; // FIXME
|
||||||
|
|
||||||
|
$this->prepareCollectors();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPoolDirectory()
|
||||||
|
{
|
||||||
|
return $this->pool_directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getImage()
|
||||||
|
{
|
||||||
|
$images = $this->getFileList();
|
||||||
|
|
||||||
|
$index = array_rand($images);
|
||||||
|
$image = $images[$index];
|
||||||
|
|
||||||
|
unset($this->file_list[$index]);
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getFileList()
|
||||||
|
{
|
||||||
|
if (!empty($this->file_list)) {
|
||||||
|
return $this->file_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_list = [];
|
||||||
|
foreach ($this->collectors as $collector) {
|
||||||
|
$directory = $collector->getPoolDirectory();
|
||||||
|
$file_list = array_merge($file_list, glob($directory.'/*.{jpg,gif,png}', GLOB_BRACE));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->file_list = $file_list;
|
||||||
|
|
||||||
|
return $file_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareCollectors()
|
||||||
|
{
|
||||||
|
foreach ($this->collectors as $collector) {
|
||||||
|
$directory = $collector->getPoolDirectory();
|
||||||
|
$images = glob($directory.'/*.{jpg,gif,png}', GLOB_BRACE);
|
||||||
|
if (count($images) < $this->nb_images) {
|
||||||
|
$collector->getRandomImages($this->nb_images - count($images));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user