Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,14 @@
' --headless --nologo --nofirststartwizard --invisible --norestore '.
'--convert-to png --outdir ',

/**
* Set the URL of the Imaginary service to send image previews to.
* Also requires the OC\Preview\Imaginary provider to be enabled.
*
* See https://github.com/h2non/imaginary
*/
'preview_imaginary_url' => 'http://previews_hpb:8088/',

/**
* Only register providers that have been explicitly enabled
*
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@
'OCP\\ISearch' => $baseDir . '/lib/public/ISearch.php',
'OCP\\IServerContainer' => $baseDir . '/lib/public/IServerContainer.php',
'OCP\\ISession' => $baseDir . '/lib/public/ISession.php',
'OCP\\IStreamImage' => $baseDir . '/lib/public/IStreamImage.php',
'OCP\\ITagManager' => $baseDir . '/lib/public/ITagManager.php',
'OCP\\ITags' => $baseDir . '/lib/public/ITags.php',
'OCP\\ITempManager' => $baseDir . '/lib/public/ITempManager.php',
Expand Down Expand Up @@ -1299,6 +1300,7 @@
'OC\\Preview\\HEIC' => $baseDir . '/lib/private/Preview/HEIC.php',
'OC\\Preview\\Illustrator' => $baseDir . '/lib/private/Preview/Illustrator.php',
'OC\\Preview\\Image' => $baseDir . '/lib/private/Preview/Image.php',
'OC\\Preview\\Imaginary' => $baseDir . '/lib/private/Preview/Imaginary.php',
'OC\\Preview\\JPEG' => $baseDir . '/lib/private/Preview/JPEG.php',
'OC\\Preview\\Krita' => $baseDir . '/lib/private/Preview/Krita.php',
'OC\\Preview\\MP3' => $baseDir . '/lib/private/Preview/MP3.php',
Expand Down Expand Up @@ -1455,6 +1457,7 @@
'OC\\Share\\Helper' => $baseDir . '/lib/private/Share/Helper.php',
'OC\\Share\\SearchResultSorter' => $baseDir . '/lib/private/Share/SearchResultSorter.php',
'OC\\Share\\Share' => $baseDir . '/lib/private/Share/Share.php',
'OC\\StreamImage' => $baseDir . '/lib/private/StreamImage.php',
'OC\\Streamer' => $baseDir . '/lib/private/Streamer.php',
'OC\\SubAdmin' => $baseDir . '/lib/private/SubAdmin.php',
'OC\\Support\\CrashReport\\Registry' => $baseDir . '/lib/private/Support/CrashReport/Registry.php',
Expand Down
3 changes: 3 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\ISearch' => __DIR__ . '/../../..' . '/lib/public/ISearch.php',
'OCP\\IServerContainer' => __DIR__ . '/../../..' . '/lib/public/IServerContainer.php',
'OCP\\ISession' => __DIR__ . '/../../..' . '/lib/public/ISession.php',
'OCP\\IStreamImage' => __DIR__ . '/../../..' . '/lib/public/IStreamImage.php',
'OCP\\ITagManager' => __DIR__ . '/../../..' . '/lib/public/ITagManager.php',
'OCP\\ITags' => __DIR__ . '/../../..' . '/lib/public/ITags.php',
'OCP\\ITempManager' => __DIR__ . '/../../..' . '/lib/public/ITempManager.php',
Expand Down Expand Up @@ -1328,6 +1329,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Preview\\HEIC' => __DIR__ . '/../../..' . '/lib/private/Preview/HEIC.php',
'OC\\Preview\\Illustrator' => __DIR__ . '/../../..' . '/lib/private/Preview/Illustrator.php',
'OC\\Preview\\Image' => __DIR__ . '/../../..' . '/lib/private/Preview/Image.php',
'OC\\Preview\\Imaginary' => __DIR__ . '/../../..' . '/lib/private/Preview/Imaginary.php',
'OC\\Preview\\JPEG' => __DIR__ . '/../../..' . '/lib/private/Preview/JPEG.php',
'OC\\Preview\\Krita' => __DIR__ . '/../../..' . '/lib/private/Preview/Krita.php',
'OC\\Preview\\MP3' => __DIR__ . '/../../..' . '/lib/private/Preview/MP3.php',
Expand Down Expand Up @@ -1484,6 +1486,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Share\\Helper' => __DIR__ . '/../../..' . '/lib/private/Share/Helper.php',
'OC\\Share\\SearchResultSorter' => __DIR__ . '/../../..' . '/lib/private/Share/SearchResultSorter.php',
'OC\\Share\\Share' => __DIR__ . '/../../..' . '/lib/private/Share/Share.php',
'OC\\StreamImage' => __DIR__ . '/../../..' . '/lib/private/StreamImage.php',
'OC\\Streamer' => __DIR__ . '/../../..' . '/lib/private/Streamer.php',
'OC\\SubAdmin' => __DIR__ . '/../../..' . '/lib/private/SubAdmin.php',
'OC\\Support\\CrashReport\\Registry' => __DIR__ . '/../../..' . '/lib/private/Support/CrashReport/Registry.php',
Expand Down
3 changes: 2 additions & 1 deletion lib/private/Http/Client/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ public function post(string $uri, array $options = []): IResponse {
unset($options['body']);
}
$response = $this->client->request('post', $uri, $this->buildRequestOptions($options));
return new Response($response);
$isStream = isset($options['stream']) && $options['stream'];
return new Response($response, $isStream);
}

/**
Expand Down
82 changes: 81 additions & 1 deletion lib/private/Preview/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use OCP\IConfig;
use OCP\IImage;
use OCP\IPreview;
use OCP\IStreamImage;
use OCP\Preview\IProviderV2;
use OCP\Preview\IVersionedPreviewFile;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -136,6 +137,22 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
$previewVersion = $file->getPreviewVersion() . '-';
}

if (count($specifications) === 1
&& (($specifications[0]['width'] === 250 && $specifications[0]['height'] === 250)
|| ($specifications[0]['width'] === 150 && $specifications[0]['height'] === 150))
&& preg_match(Imaginary::supportedMimeTypes(), $mimeType)
&& $this->config->getSystemValueString('preview_imaginary_url', 'invalid') !== 'invalid') {
$crop = $specifications[0]['crop'] ?? false;
$preview = $this->getSmallImagePreview($previewFolder, $file, $mimeType, $previewVersion, $crop);

if ($preview->getSize() === 0) {
$preview->delete();
throw new NotFoundException('Cached preview size 0, invalid!');
}

return $preview;
}

// Get the max preview and infer the max preview sizes from that
$maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion);
$maxPreviewImage = null; // only load the image when we need it
Expand Down Expand Up @@ -204,6 +221,65 @@ public function generatePreviews(File $file, array $specifications, $mimeType =
return $preview;
}

private function getSmallImagePreview(ISimpleFolder $previewFolder, File $file, string $mimeType, string $prefix, bool $crop) {
$nodes = $previewFolder->getDirectoryListing();

foreach ($nodes as $node) {
$name = $node->getName();
if (($prefix === '' || strpos($name, $prefix) === 0)
&& (str_starts_with($name, '256-256-crop') && $crop || str_starts_with($name, '256-256') && !$crop)) {
return $node;
}
}

$previewProviders = $this->previewManager->getProviders();
foreach ($previewProviders as $supportedMimeType => $providers) {
// Filter out providers that does not support this mime
if (!preg_match($supportedMimeType, $mimeType)) {
continue;
}

foreach ($providers as $providerClosure) {
$provider = $this->helper->getProvider($providerClosure);
if (!($provider instanceof IProviderV2)) {
continue;
}

if (!$provider->isAvailable($file)) {
continue;
}

$preview = $this->helper->getThumbnail($provider, $file, 256, 256, true);

if (!($preview instanceof IImage)) {
continue;
}

// Try to get the extension.
try {
$ext = $this->getExtention($preview->dataMimeType());
} catch (\InvalidArgumentException $e) {
// Just continue to the next iteration if this preview doesn't have a valid mimetype
continue;
}

$path = $this->generatePath(256, 256, $crop, $preview->dataMimeType(), $prefix);
try {
$file = $previewFolder->newFile($path);
if ($preview instanceof IStreamImage) {
$file->putContent($preview->resource());
} else {
$file->putContent($preview->data());
}
} catch (NotPermittedException $e) {
throw new NotFoundException();
}

return $file;
}
}
}

/**
* @param ISimpleFolder $previewFolder
* @param File $file
Expand Down Expand Up @@ -259,7 +335,11 @@ private function getMaxPreview(ISimpleFolder $previewFolder, File $file, $mimeTy
$path = $prefix . (string)$preview->width() . '-' . (string)$preview->height() . '-max.' . $ext;
try {
$file = $previewFolder->newFile($path);
$file->putContent($preview->data());
if ($preview instanceof IStreamImage) {
$file->putContent($preview->resource());
} else {
$file->putContent($preview->data());
}
} catch (NotPermittedException $e) {
throw new NotFoundException();
}
Expand Down
7 changes: 5 additions & 2 deletions lib/private/Preview/GeneratorHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ public function __construct(IRootFolder $rootFolder, IConfig $config) {
*
* @return bool|IImage
*/
public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight) {
return $provider->getThumbnail($file, $maxWidth, $maxHeight);
public function getThumbnail(IProviderV2 $provider, File $file, $maxWidth, $maxHeight, bool $crop = false) {
if ($provider instanceof Imaginary) {
return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
}
return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
}

/**
Expand Down
136 changes: 136 additions & 0 deletions lib/private/Preview/Imaginary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* @copyright Copyright (c) 2020, Nextcloud, GmbH.
*
* @author Vincent Petry <[email protected]>
* @author Carl Schwan <[email protected]>
*
* @license AGPL-3.0-or-later
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace OC\Preview;

use OCP\Files\File;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IImage;

use OC\StreamImage;
use Psr\Log\LoggerInterface;

class Imaginary extends ProviderV2 {
/** @var IConfig */
private $config;

/** @var IClientService */
private $service;

/** @var LoggerInterface */
private $logger;

public function __construct(array $config) {
parent::__construct($config);
$this->config = \OC::$server->get(IConfig::class);
$this->service = \OC::$server->get(IClientService::class);
$this->logger = \OC::$server->get(LoggerInterface::class);
}

/**
* {@inheritDoc}
*/
public function getMimeType(): string {
return self::supportedMimeTypes();
}

public static function supportedMimeTypes(): string {
return '/image\/(bmp|x-bitmap|png|jpeg|gif|heic|svg|webp)/';
}

public function getCroppedThumbnail(File $file, int $maxX, int $maxY, bool $crop): ?IImage {
$maxSizeForImages = $this->config->getSystemValue('preview_max_filesize_image', 50);

$size = $file->getSize();

if ($maxSizeForImages !== -1 && $size > ($maxSizeForImages * 1024 * 1024)) {
return null;
}

$imaginaryUrl = $this->config->getSystemValueString('preview_imaginary_url', 'invalid');
if ($imaginaryUrl === 'invalid') {
$this->logger->error('Imaginary preview provider is enabled, but no url is configured. Please provide the url of your imaginary server to the \'preview_imaginary_url\' config variable.');
return null;
}
$imaginaryUrl = rtrim($imaginaryUrl, '/');

// Object store
$stream = $file->fopen('r');

$httpClient = $this->service->newClient();

switch ($file->getMimeType()) {
case 'image/gif':
case 'image/png':
$mimeType = 'png';
break;
default:
$mimeType = 'jpeg';
}

$parameters = [
'width' => $maxX,
'height' => $maxY,
'stripmeta' => 'true',
'type' => $mimeType,
];


try {
$response = $httpClient->post(
$imaginaryUrl . ($crop ? '/smartcrop' : '/fit'), [
'query' => $parameters,
'stream' => true,
'content-type' => $file->getMimeType(),
'body' => $stream,
'nextcloud' => ['allow_local_address' => true],
]);
} catch (\Exception $e) {
$this->logger->error('Imaginary preview generation failed: ' . $e->getMessage(), [
'exception' => $e,
]);
return null;
}

if ($response->getStatusCode() !== 200) {
$this->logger->error('Imaginary preview generation failed: ' . json_decode($response->getBody())['message']);
return null;
}

if ($response->getHeader('X-Image-Width') && $response->getHeader('X-Image-Height')) {
$maxX = (int)$response->getHeader('X-Image-Width');
$maxY = (int)$response->getHeader('X-Image-Height');
}

$image = new StreamImage($response->getBody(), $response->getHeader('Content-Type'), $maxX, $maxY);
return $image->valid() ? $image : null;
}

/**
* {@inheritDoc}
*/
public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
return $this->getCroppedThumbnail($file, $maxX, $maxY, false);
}
}
1 change: 1 addition & 0 deletions lib/private/PreviewManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ protected function registerCoreProviders() {
$this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
$this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg/');
$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
$this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());

// SVG, Office and Bitmap require imagick
if (extension_loaded('imagick')) {
Expand Down
Loading