123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- <?php
- /**
- * Copyright (c) 2013 Frank Karlitschek frank@owncloud.org
- * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
- *
- * Thumbnails:
- * structure of filename:
- * /data/user/thumbnails/pathhash/x-y.png
- *
- */
- namespace OC;
- require_once 'preview/image.php';
- require_once 'preview/movies.php';
- require_once 'preview/mp3.php';
- require_once 'preview/pdf.php';
- require_once 'preview/svg.php';
- require_once 'preview/txt.php';
- require_once 'preview/unknown.php';
- require_once 'preview/office.php';
- class Preview {
- //the thumbnail folder
- const THUMBNAILS_FOLDER = 'thumbnails';
- //config
- private $maxScaleFactor;
- private $configMaxX;
- private $configMaxY;
- //fileview object
- private $fileView = null;
- private $userView = null;
- //vars
- private $file;
- private $maxX;
- private $maxY;
- private $scalingup;
- private $mimetype;
- //preview images object
- /**
- * @var \OC_Image
- */
- private $preview;
- //preview providers
- static private $providers = array();
- static private $registeredProviders = array();
- /**
- * @brief check if thumbnail or bigger version of thumbnail of file is cached
- * @param string $user userid - if no user is given, OC_User::getUser will be used
- * @param string $root path of root
- * @param string $file The path to the file where you want a thumbnail from
- * @param int $maxX The maximum X size of the thumbnail. It can be smaller depending on the shape of the image
- * @param int $maxY The maximum Y size of the thumbnail. It can be smaller depending on the shape of the image
- * @param bool $scalingUp Disable/Enable upscaling of previews
- * @return mixed (bool / string)
- * false if thumbnail does not exist
- * path to thumbnail if thumbnail exists
- */
- public function __construct($user='', $root='/', $file='', $maxX=1, $maxY=1, $scalingUp=true) {
- //init fileviews
- if($user === ''){
- $user = \OC_User::getUser();
- }
- $this->fileView = new \OC\Files\View('/' . $user . '/' . $root);
- $this->userView = new \OC\Files\View('/' . $user);
- //set config
- $this->configMaxX = \OC_Config::getValue('preview_max_x', null);
- $this->configMaxY = \OC_Config::getValue('preview_max_y', null);
- $this->maxScaleFactor = \OC_Config::getValue('preview_max_scale_factor', 2);
- //save parameters
- $this->setFile($file);
- $this->setMaxX($maxX);
- $this->setMaxY($maxY);
- $this->setScalingUp($scalingUp);
- $this->preview = null;
- //check if there are preview backends
- if(empty(self::$providers)) {
- self::initProviders();
- }
- if(empty(self::$providers)) {
- \OC_Log::write('core', 'No preview providers exist', \OC_Log::ERROR);
- throw new \Exception('No preview providers');
- }
- }
- /**
- * @brief returns the path of the file you want a thumbnail from
- * @return string
- */
- public function getFile() {
- return $this->file;
- }
- /**
- * @brief returns the max width of the preview
- * @return integer
- */
- public function getMaxX() {
- return $this->maxX;
- }
- /**
- * @brief returns the max height of the preview
- * @return integer
- */
- public function getMaxY() {
- return $this->maxY;
- }
- /**
- * @brief returns whether or not scalingup is enabled
- * @return bool
- */
- public function getScalingUp() {
- return $this->scalingup;
- }
- /**
- * @brief returns the name of the thumbnailfolder
- * @return string
- */
- public function getThumbnailsFolder() {
- return self::THUMBNAILS_FOLDER;
- }
- /**
- * @brief returns the max scale factor
- * @return integer
- */
- public function getMaxScaleFactor() {
- return $this->maxScaleFactor;
- }
- /**
- * @brief returns the max width set in ownCloud's config
- * @return integer
- */
- public function getConfigMaxX() {
- return $this->configMaxX;
- }
- /**
- * @brief returns the max height set in ownCloud's config
- * @return integer
- */
- public function getConfigMaxY() {
- return $this->configMaxY;
- }
- /**
- * @brief set the path of the file you want a thumbnail from
- * @param string $file
- * @return $this
- */
- public function setFile($file) {
- $this->file = $file;
- if ($file !== '') {
- $this->mimetype = $this->fileView->getMimeType($this->file);
- }
- return $this;
- }
- /**
- * @brief set mimetype explicitely
- * @param string $mimetype
- */
- public function setMimetype($mimetype) {
- $this->mimetype = $mimetype;
- }
- /**
- * @brief set the the max width of the preview
- * @param int $maxX
- * @return $this
- */
- public function setMaxX($maxX=1) {
- if($maxX <= 0) {
- throw new \Exception('Cannot set width of 0 or smaller!');
- }
- $configMaxX = $this->getConfigMaxX();
- if(!is_null($configMaxX)) {
- if($maxX > $configMaxX) {
- \OC_Log::write('core', 'maxX reduced from ' . $maxX . ' to ' . $configMaxX, \OC_Log::DEBUG);
- $maxX = $configMaxX;
- }
- }
- $this->maxX = $maxX;
- return $this;
- }
- /**
- * @brief set the the max height of the preview
- * @param int $maxY
- * @return $this
- */
- public function setMaxY($maxY=1) {
- if($maxY <= 0) {
- throw new \Exception('Cannot set height of 0 or smaller!');
- }
- $configMaxY = $this->getConfigMaxY();
- if(!is_null($configMaxY)) {
- if($maxY > $configMaxY) {
- \OC_Log::write('core', 'maxX reduced from ' . $maxY . ' to ' . $configMaxY, \OC_Log::DEBUG);
- $maxY = $configMaxY;
- }
- }
- $this->maxY = $maxY;
- return $this;
- }
- /**
- * @brief set whether or not scalingup is enabled
- * @param bool $scalingUp
- * @return $this
- */
- public function setScalingup($scalingUp) {
- if($this->getMaxScaleFactor() === 1) {
- $scalingUp = false;
- }
- $this->scalingup = $scalingUp;
- return $this;
- }
- /**
- * @brief check if all parameters are valid
- * @return bool
- */
- public function isFileValid() {
- $file = $this->getFile();
- if($file === '') {
- \OC_Log::write('core', 'No filename passed', \OC_Log::DEBUG);
- return false;
- }
- if(!$this->fileView->file_exists($file)) {
- \OC_Log::write('core', 'File:"' . $file . '" not found', \OC_Log::DEBUG);
- return false;
- }
- return true;
- }
- /**
- * @brief deletes previews of a file with specific x and y
- * @return bool
- */
- public function deletePreview() {
- $file = $this->getFile();
- $fileInfo = $this->fileView->getFileInfo($file);
- $fileId = $fileInfo['fileid'];
- $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/' . $this->getMaxX() . '-' . $this->getMaxY() . '.png';
- $this->userView->unlink($previewPath);
- return !$this->userView->file_exists($previewPath);
- }
- /**
- * @brief deletes all previews of a file
- * @return bool
- */
- public function deleteAllPreviews() {
- $file = $this->getFile();
- $fileInfo = $this->fileView->getFileInfo($file);
- $fileId = $fileInfo['fileid'];
- $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/';
- $this->userView->deleteAll($previewPath);
- $this->userView->rmdir($previewPath);
- return !$this->userView->is_dir($previewPath);
- }
- /**
- * @brief check if thumbnail or bigger version of thumbnail of file is cached
- * @return mixed (bool / string)
- * false if thumbnail does not exist
- * path to thumbnail if thumbnail exists
- */
- private function isCached() {
- $file = $this->getFile();
- $maxX = $this->getMaxX();
- $maxY = $this->getMaxY();
- $scalingUp = $this->getScalingUp();
- $maxScaleFactor = $this->getMaxScaleFactor();
- $fileInfo = $this->fileView->getFileInfo($file);
- $fileId = $fileInfo['fileid'];
- if(is_null($fileId)) {
- return false;
- }
- $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/';
- if(!$this->userView->is_dir($previewPath)) {
- return false;
- }
- //does a preview with the wanted height and width already exist?
- if($this->userView->file_exists($previewPath . $maxX . '-' . $maxY . '.png')) {
- return $previewPath . $maxX . '-' . $maxY . '.png';
- }
- $wantedAspectRatio = (float) ($maxX / $maxY);
- //array for usable cached thumbnails
- $possibleThumbnails = array();
- $allThumbnails = $this->userView->getDirectoryContent($previewPath);
- foreach($allThumbnails as $thumbnail) {
- $name = rtrim($thumbnail['name'], '.png');
- $size = explode('-', $name);
- $x = (int) $size[0];
- $y = (int) $size[1];
- $aspectRatio = (float) ($x / $y);
- if($aspectRatio !== $wantedAspectRatio) {
- continue;
- }
- if($x < $maxX || $y < $maxY) {
- if($scalingUp) {
- $scalefactor = $maxX / $x;
- if($scalefactor > $maxScaleFactor) {
- continue;
- }
- }else{
- continue;
- }
- }
- $possibleThumbnails[$x] = $thumbnail['path'];
- }
- if(count($possibleThumbnails) === 0) {
- return false;
- }
- if(count($possibleThumbnails) === 1) {
- return current($possibleThumbnails);
- }
- ksort($possibleThumbnails);
- if(key(reset($possibleThumbnails)) > $maxX) {
- return current(reset($possibleThumbnails));
- }
- if(key(end($possibleThumbnails)) < $maxX) {
- return current(end($possibleThumbnails));
- }
- foreach($possibleThumbnails as $width => $path) {
- if($width < $maxX) {
- continue;
- }else{
- return $path;
- }
- }
- }
- /**
- * @brief return a preview of a file
- * @return \OC_Image
- */
- public function getPreview() {
- if(!is_null($this->preview) && $this->preview->valid()){
- return $this->preview;
- }
- $this->preview = null;
- $file = $this->getFile();
- $maxX = $this->getMaxX();
- $maxY = $this->getMaxY();
- $scalingUp = $this->getScalingUp();
- $fileInfo = $this->fileView->getFileInfo($file);
- $fileId = $fileInfo['fileid'];
- $cached = $this->isCached();
- if($cached) {
- $image = new \OC_Image($this->userView->file_get_contents($cached, 'r'));
- $this->preview = $image->valid() ? $image : null;
- $this->resizeAndCrop();
- }
- if(is_null($this->preview)) {
- $preview = null;
- foreach(self::$providers as $supportedMimetype => $provider) {
- if(!preg_match($supportedMimetype, $this->mimetype)) {
- continue;
- }
- \OC_Log::write('core', 'Generating preview for "' . $file . '" with "' . get_class($provider) . '"', \OC_Log::DEBUG);
- $preview = $provider->getThumbnail($file, $maxX, $maxY, $scalingUp, $this->fileView);
- if(!($preview instanceof \OC_Image)) {
- continue;
- }
- $this->preview = $preview;
- $this->resizeAndCrop();
- $previewPath = $this->getThumbnailsFolder() . '/' . $fileId . '/';
- $cachePath = $previewPath . $maxX . '-' . $maxY . '.png';
- if($this->userView->is_dir($this->getThumbnailsFolder() . '/') === false) {
- $this->userView->mkdir($this->getThumbnailsFolder() . '/');
- }
- if($this->userView->is_dir($previewPath) === false) {
- $this->userView->mkdir($previewPath);
- }
- $this->userView->file_put_contents($cachePath, $preview->data());
- break;
- }
- }
- if(is_null($this->preview)) {
- $this->preview = new \OC_Image();
- }
- return $this->preview;
- }
- /**
- * @brief show preview
- * @return void
- */
- public function showPreview() {
- \OCP\Response::enableCaching(3600 * 24); // 24 hours
- if(is_null($this->preview)) {
- $this->getPreview();
- }
- $this->preview->show();
- return;
- }
- /**
- * @brief show preview
- * @return void
- */
- public function show() {
- $this->showPreview();
- return;
- }
- /**
- * @brief resize, crop and fix orientation
- * @return void
- */
- private function resizeAndCrop() {
- $image = $this->preview;
- $x = $this->getMaxX();
- $y = $this->getMaxY();
- $scalingUp = $this->getScalingUp();
- $maxscalefactor = $this->getMaxScaleFactor();
- if(!($image instanceof \OC_Image)) {
- \OC_Log::write('core', '$this->preview is not an instance of OC_Image', \OC_Log::DEBUG);
- return;
- }
- $image->fixOrientation();
- $realx = (int) $image->width();
- $realy = (int) $image->height();
- if($x === $realx && $y === $realy) {
- $this->preview = $image;
- return;
- }
- $factorX = $x / $realx;
- $factorY = $y / $realy;
- if($factorX >= $factorY) {
- $factor = $factorX;
- }else{
- $factor = $factorY;
- }
- if($scalingUp === false) {
- if($factor > 1) {
- $factor = 1;
- }
- }
- if(!is_null($maxscalefactor)) {
- if($factor > $maxscalefactor) {
- \OC_Log::write('core', 'scalefactor reduced from ' . $factor . ' to ' . $maxscalefactor, \OC_Log::DEBUG);
- $factor = $maxscalefactor;
- }
- }
- $newXsize = (int) ($realx * $factor);
- $newYsize = (int) ($realy * $factor);
- $image->preciseResize($newXsize, $newYsize);
- if($newXsize === $x && $newYsize === $y) {
- $this->preview = $image;
- return;
- }
- if($newXsize >= $x && $newYsize >= $y) {
- $cropX = floor(abs($x - $newXsize) * 0.5);
- //don't crop previews on the Y axis, this sucks if it's a document.
- //$cropY = floor(abs($y - $newYsize) * 0.5);
- $cropY = 0;
- $image->crop($cropX, $cropY, $x, $y);
- $this->preview = $image;
- return;
- }
- if($newXsize < $x || $newYsize < $y) {
- if($newXsize > $x) {
- $cropX = floor(($newXsize - $x) * 0.5);
- $image->crop($cropX, 0, $x, $newYsize);
- }
- if($newYsize > $y) {
- $cropY = floor(($newYsize - $y) * 0.5);
- $image->crop(0, $cropY, $newXsize, $y);
- }
- $newXsize = (int) $image->width();
- $newYsize = (int) $image->height();
- //create transparent background layer
- $backgroundlayer = imagecreatetruecolor($x, $y);
- $white = imagecolorallocate($backgroundlayer, 255, 255, 255);
- imagefill($backgroundlayer, 0, 0, $white);
- $image = $image->resource();
- $mergeX = floor(abs($x - $newXsize) * 0.5);
- $mergeY = floor(abs($y - $newYsize) * 0.5);
- imagecopy($backgroundlayer, $image, $mergeX, $mergeY, 0, 0, $newXsize, $newYsize);
- //$black = imagecolorallocate(0,0,0);
- //imagecolortransparent($transparentlayer, $black);
- $image = new \OC_Image($backgroundlayer);
- $this->preview = $image;
- return;
- }
- }
- /**
- * @brief register a new preview provider to be used
- * @param string $provider class name of a Preview_Provider
- * @param array $options
- * @return void
- */
- public static function registerProvider($class, $options=array()) {
- self::$registeredProviders[]=array('class'=>$class, 'options'=>$options);
- }
- /**
- * @brief create instances of all the registered preview providers
- * @return void
- */
- private static function initProviders() {
- if(!\OC_Config::getValue('enable_previews', true)) {
- $provider = new Preview\Unknown(array());
- self::$providers = array($provider->getMimeType() => $provider);
- return;
- }
- if(count(self::$providers)>0) {
- return;
- }
- foreach(self::$registeredProviders as $provider) {
- $class=$provider['class'];
- $options=$provider['options'];
- $object = new $class($options);
- self::$providers[$object->getMimeType()] = $object;
- }
- $keys = array_map('strlen', array_keys(self::$providers));
- array_multisort($keys, SORT_DESC, self::$providers);
- }
- public static function post_write($args) {
- self::post_delete($args);
- }
- public static function post_delete($args) {
- $path = $args['path'];
- if(substr($path, 0, 1) === '/') {
- $path = substr($path, 1);
- }
- $preview = new Preview(\OC_User::getUser(), 'files/', $path);
- $preview->deleteAllPreviews();
- }
- public static function isMimeSupported($mimetype) {
- if(!\OC_Config::getValue('enable_previews', true)) {
- return false;
- }
- //check if there are preview backends
- if(empty(self::$providers)) {
- self::initProviders();
- }
- //remove last element because it has the mimetype *
- $providers = array_slice(self::$providers, 0, -1);
- foreach($providers as $supportedMimetype => $provider) {
- if(preg_match($supportedMimetype, $mimetype)) {
- return true;
- }
- }
- return false;
- }
- }
|