Преглед изворни кода

Merge branch 'master' of https://github.com/boonex/dolphin

AntonLV пре 12 година
родитељ
комит
86ed70a6ab
31 измењених фајлова са 1664 додато и 705 уклоњено
  1. 24 0
      inc/classes/BxDolCronTranscoder.php
  2. 1 1
      inc/classes/BxDolDb.php
  3. 1 1
      inc/classes/BxDolEmailTemplates.php
  4. 38 0
      inc/classes/BxDolImageResize.php
  5. 5 3
      inc/classes/BxDolStorage.php
  6. 1 5
      inc/classes/BxDolStorageLocal.php
  7. 1 1
      inc/classes/BxDolStorageQuery.php
  8. 693 0
      inc/classes/BxDolTranscoder.php
  9. 10 511
      inc/classes/BxDolTranscoderImage.php
  10. 7 150
      inc/classes/BxDolTranscoderImageQuery.php
  11. 247 0
      inc/classes/BxDolTranscoderQuery.php
  12. 273 0
      inc/classes/BxDolTranscoderVideo.php
  13. 26 0
      inc/classes/BxDolTranscoderVideoQuery.php
  14. 77 0
      inc/utils.inc.php
  15. 1 0
      install/classes/BxDolInstallSiteConfig.php
  16. 1 0
      install/patterns/header.inc.php
  17. 42 11
      install/sql/system.sql
  18. 2 2
      modules/boonex/convos/install/sql/install.sql
  19. 2 2
      modules/boonex/convos/install/sql/uninstall.sql
  20. 2 2
      modules/boonex/organizations/install/sql/install.sql
  21. 2 2
      modules/boonex/organizations/install/sql/uninstall.sql
  22. 2 2
      modules/boonex/persons/install/sql/install.sql
  23. 2 2
      modules/boonex/persons/install/sql/uninstall.sql
  24. 2 2
      modules/boonex/posts/install/sql/install.sql
  25. 2 2
      modules/boonex/posts/install/sql/uninstall.sql
  26. 2 2
      modules/boonex/timeline/install/sql/install.sql
  27. 2 2
      modules/boonex/timeline/install/sql/uninstall.sql
  28. BIN
      plugins/ffmpeg/ffmpeg.exe
  29. 2 2
      samples/transcoder.php
  30. 194 0
      samples/transcoder_video.php
  31. BIN
      template/images/video-na.png

+ 24 - 0
inc/classes/BxDolCronTranscoder.php

@@ -0,0 +1,24 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolCron');
+
+class BxDolCronTranscoder extends BxDolCron
+{
+    public function processing()
+    {
+        set_time_limit(10800);
+        ignore_user_abort();
+
+        bx_import('BxDolTranscoder');
+        BxDolTranscoder::processQueue();
+    }
+}
+
+/** @} */

+ 1 - 1
inc/classes/BxDolDb.php

@@ -506,7 +506,7 @@ class BxDolDb extends BxDol implements iBxDolSingleton
             foreach( $aBackTrace as $aCall ) {
             foreach( $aBackTrace as $aCall ) {
 
 
                 // truncating global settings since it repeated many times and output it separately
                 // truncating global settings since it repeated many times and output it separately
-                if (isset($aCall['object']) && property_exists($aCall['object'], '_aParams')) {
+                if (isset($aCall['object']) && is_a($aCall['object'], 'BxDolDb')) {
                     if (false === $sParamsOutput)
                     if (false === $sParamsOutput)
                         $sParamsOutput = var_export($aCall['object']->_aParams, true);
                         $sParamsOutput = var_export($aCall['object']->_aParams, true);
                     $aCall['object']->_aParams = '[truncated]';
                     $aCall['object']->_aParams = '[truncated]';

+ 1 - 1
inc/classes/BxDolEmailTemplates.php

@@ -7,7 +7,7 @@
  * @{
  * @{
  */
  */
 
 
-bx_import('BxDolLanguagesQuery');
+bx_import('BxDolLanguages');
 bx_import('BxDolEmailTemplatesQuery');
 bx_import('BxDolEmailTemplatesQuery');
 
 
 /**
 /**

+ 38 - 0
inc/classes/BxDolImageResize.php

@@ -181,6 +181,11 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
         return array ('w' => $aSize[0], 'h' => $aSize[1]);
         return array ('w' => $aSize[0], 'h' => $aSize[1]);
     }
     }
 
 
+    function getAverageColor($sPath)
+    {
+    	return $this->isUsedGD() ? $this->_getAverageColorGD($sPath) : $this->_getAverageColorImageMagic($sPath);
+    }
+
     // private functions are below -------------------------------
     // private functions are below -------------------------------
 
 
     function _grayscale ($sSrcImage, $sDstImage = '')
     function _grayscale ($sSrcImage, $sDstImage = '')
@@ -557,6 +562,39 @@ class BxDolImageResize extends BxDol implements iBxDolSingleton
 
 
         return $writeResult;
         return $writeResult;
     }
     }
+
+	function _getAverageColorGD($sPath)
+    {
+    	$iError = 0;
+		$aSize = array();
+		$oImgOrig = $this->_createGDImage($sPath, $aSize, $iError);
+		if($iError != IMAGE_ERROR_SUCCESS)
+			return false;
+
+		$oImgTmp = ImageCreateTrueColor(1,1);
+		ImageCopyResampled($oImgTmp, $oImgOrig, 0, 0, 0, 0, 1, 1, $aSize[0],$aSize[1]);
+		$iRgb = ImageColorAt($oImgTmp, 0, 0);
+
+		$iRed = ($iRgb >> 16) & 0xFF;
+		$iGreen = ($iRgb >> 8) & 0xFF;
+		$iBlue = $iRgb & 0xFF;
+
+		return array('r' => $iRed, 'g' => $iGreen, 'b' => $iBlue);
+    }
+
+    function _getAverageColorImageMagic($sPath)
+    {
+    	$sCmd = "{$GLOBALS['CONVERT']} $sPath -filter box -resize 1x1! -format \"%[fx:round(255*u.r)],%[fx:round(255*u.g)],%[fx:round(255*u.b)]\" info:";
+        $sResult = @exec($sCmd);
+        if(empty($sResult))
+        	return false;
+
+        $aResult = explode(',', $sResult);
+		if(count($aResult) != 3)
+			return false;
+
+		return array('r' => $aResult[0], 'g' => $aResult[1], 'b' => $aResult[2]);
+    }
 }
 }
 
 
 /** @} */
 /** @} */

+ 5 - 3
inc/classes/BxDolStorage.php

@@ -356,9 +356,11 @@ abstract class BxDolStorage extends BxDol implements iBxDolFactoryObject
 
 
         // TODO: get and check site quota
         // TODO: get and check site quota
 
 
-        $iUploadMaxFilesize = return_bytes(ini_get('upload_max_filesize'));
-        if ($iUploadMaxFilesize && $iUploadMaxFilesize < $iMin)
-            $iMin = $iUploadMaxFilesize;
+        if (!defined('BX_DOL_CRON_EXECUTE')) {
+            $iUploadMaxFilesize = return_bytes(ini_get('upload_max_filesize'));
+            if ($iUploadMaxFilesize && $iUploadMaxFilesize < $iMin)
+                $iMin = $iUploadMaxFilesize;
+        }
 
 
         return $iMin;
         return $iMin;
     }
     }

+ 1 - 5
inc/classes/BxDolStorageLocal.php

@@ -68,11 +68,7 @@ class BxDolStorageLocal extends BxDolStorage
             return false;
             return false;
         }
         }
 
 
-        header('Content-Type: ' . $aFile['mime_type']);
-        if ($this->_iCacheControl > 0)
-            header('Cache-Control: max-age=' . ($aFile['private'] && $this->_iCacheControl > $this->_aObject['token_life'] ? $this->_aObject['token_life'] : $this->_iCacheControl));
-
-        if (false === readfile($sFileLocation)) {
+        if (!bx_smart_readfile($sFileLocation, $aFile['file_name'], $aFile['mime_type'], $aFile['private'] && $this->_iCacheControl > $this->_aObject['token_life'] ? $this->_aObject['token_life'] : $this->_iCacheControl, $aFile['private'] ? 'private' : 'public')) {
             $this->setErrorCode(BX_DOL_STORAGE_ERR_ENGINE_GET);
             $this->setErrorCode(BX_DOL_STORAGE_ERR_ENGINE_GET);
             return false;
             return false;
         }
         }

+ 1 - 1
inc/classes/BxDolStorageQuery.php

@@ -257,7 +257,7 @@ class BxDolStorageQuery extends BxDolDb
 
 
     public function getFilesAll($iStart, $iPerPage)
     public function getFilesAll($iStart, $iPerPage)
     {
     {
-        $sQuery = $this->prepare("SELECT * FROM `" . $this->_sTableFiles . "` LIMIT ?, ?", (int)$iStart, (int)$iPerPage);
+        $sQuery = $this->prepare("SELECT * FROM " . $this->_sTableFiles . " LIMIT ?, ?", (int)$iStart, (int)$iPerPage);
         return $this->getAll($sQuery);
         return $this->getAll($sQuery);
     }
     }
 
 

+ 693 - 0
inc/classes/BxDolTranscoder.php

@@ -0,0 +1,693 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolStorage');
+bx_import('BxDolTranscoder');
+bx_import('BxDolTranscoderQuery');
+
+define('BX_DOL_QUEUE_PENDING', 'pending'); ///< file is pending for processing
+define('BX_DOL_QUEUE_PROCESSING', 'processing'); ///< file is in the process
+define('BX_DOL_QUEUE_COMPLETE', 'complete'); ///< file is already processed
+define('BX_DOL_QUEUE_FAILED', 'failed'); ///< file processing was failed
+
+/**
+ * Base class for @see BxDolTranscoderImage and @see BxDolTranscoderVideo transcoders
+ */
+class BxDolTranscoder extends BxDol implements iBxDolFactoryObject
+{
+    protected $_sLog = ''; ///< file conversion log
+    protected $_aObject; ///< object properties
+    protected $_oStorage; ///< storage object, transcoded files are stored here
+    protected $_oDb; ///< database queries object
+    protected $_sRetinaSuffix = '@2x'; ///< handler suffix for retina image file
+    protected $_sQueueTable = ''; ///< table with queued files for transcoding
+    protected $_sQueueStorage = ''; ///< storage object to storage temporary files
+
+    /**
+     * constructor
+     */
+    protected function __construct($aObject, $oStorage)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_oStorage = $oStorage;
+        $this->_oDb = null;
+    }
+
+    /**
+     * Get transcode object instance.
+     * @param $sObject - name of transcode object.
+     * @return false on error or instance of transcoder class.
+     */
+    public static function getObjectInstance($sObject)
+    {
+        if (isset($GLOBALS['bxDolClasses'][__CLASS__ . '!' . $sObject]))
+            return $GLOBALS['bxDolClasses'][__CLASS__ . '!'.$sObject];
+
+        // get transcode object
+        $aObject = BxDolTranscoderQuery::getTranscoderObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        if ($aObject['source_params'])
+            $aObject['source_params'] = unserialize($aObject['source_params']);
+
+        $aObject['filters'] = false; // filters are initialized on demand
+
+        // create storage object to store transcoded data
+        $oStorage = BxDolStorage::getObjectInstance($aObject['storage_object']);
+        if (!$oStorage)
+            return false;
+
+        // create instance
+        $sClass = $aObject['override_class_name'] ? $aObject['override_class_name'] : 'BxDolTranscoderImage';
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        else
+            bx_import($sClass);
+
+        $o = new $sClass ($aObject, $oStorage);
+
+        return ($GLOBALS['bxDolClasses'][__CLASS__ . '!' . $sObject] = $o);
+    }
+
+    /**
+     * Delete outdated transcoed data from all transcoeeed objects, by last access time.
+     * It called on cron, usually every day.
+     * @return total number of pruned/deleted files
+     */
+    static public function pruning ()
+    {
+        $iCount = 0;
+        $aObjects = self::getTranscoderObjects();
+        foreach ($aObjects as $aObject) {
+            if (!$aObject['atime_tracking'] || !$aObject['atime_pruning'])
+                continue;
+            $oTranscoder = self::getObjectInstance($aObject['object']);
+            if (!$oTranscoder)
+                continue;
+            $iCount += $oTranscoder->prune();
+        }
+        return $iCount;
+    }
+
+    /**
+     * Get transcoder objects array
+     * @return array
+     */
+    static protected function getTranscoderObjects ()
+    {
+        return BxDolTranscoderQuery::getTranscoderObjects ();
+    }
+
+    /**
+     * process files queued for transcoding
+     */
+    static public function processQueue ($bQueuePruning = true)
+    {
+        if ($bQueuePruning)
+            BxDolTranscoderQuery::queuePruning ();
+        $a = BxDolTranscoderQuery::getNextInQueue (gethostname());
+        foreach ($a as $r) {
+            $o = self::getObjectInstance($r['transcoder_object']);
+            $aQueue = $o->getDb()->getFromQueue($r['file_id_source']);
+            if ($aQueue['status'] != 'pending')
+                continue;
+            
+            $o->transcode($aQueue['file_id_source'], $aQueue['profile_id']);
+        }
+    }
+
+    /**
+     * Register handlers array
+     * It can be called upon module enable event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function registerHandlersArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'registerHandlers');
+    }
+
+    /**
+     * Unregister handlers array
+     * It can be called upon module disbale event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function unregisterHandlersArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'unregisterHandlers');
+    }
+
+    /**
+     * Cleanup (queue for deletion all resized files)
+     * It can be called upon module disbale event.
+     * @param $mixed array of transcoders objects, or just one object
+     */
+    static public function cleanupObjectsArray ($mixed)
+    {
+        self::_callFuncForObjectsArray ($mixed, 'cleanup');
+    }
+
+    /**
+     * Called automatically, upon local(transcoded) file deletetion.
+     */
+    static public function onAlertResponseFileDeleteLocal ($oAlert, $sObject)
+    {
+        $oTranscoder = self::getObjectInstance($sObject);
+        if (!$oTranscoder)
+            return;
+
+        if ($oAlert->sAction != 'file_deleted')
+            return;
+
+        $oTranscoder->onDeleteFileLocal($oAlert->iObject);
+    }
+
+    /**
+     * Called automatically, upon original file deletetion.
+     */
+    static public function onAlertResponseFileDeleteOrig ($oAlert, $sObject)
+    {
+        $oTranscoder = self::getObjectInstance($sObject);
+        if (!$oTranscoder)
+            return;
+
+        if ($oAlert->sAction != 'file_deleted')
+            return;
+
+        $oTranscoder->onDeleteFileOrig($oAlert->iObject);
+    }
+
+    /**
+     * Called automatically, upon local(transcoded) file deletetion.
+     */
+    public function onDeleteFileLocal($iFileId)
+    {
+        return $this->_oDb->deleteFileTraces($iFileId);
+    }
+
+    /**
+     * Called automatically, upon original file deletetion.
+     */
+    public function onDeleteFileOrig($mixedHandler)
+    {
+        // delete main file
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if (!$iFileId)
+            return false;
+
+        if (!$this->_oStorage->deleteFile($iFileId))
+            return false;
+
+        // delete retina file
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler . $this->_sRetinaSuffix);
+        if ($iFileId)
+            $this->_oStorage->deleteFile($iFileId);
+
+        return true;
+    }
+
+    /**
+     * Register necessary alert handlers for automatic deletetion of transcoded data if source file is deleted.
+     * Make sure that you call it once, before first usage, for example upon module installation.
+     */
+    public function registerHandlers ()
+    {
+        if (!$this->_oDb->registerHandlers ())
+            return false;
+        return $this->clearCacheDB();
+    }
+
+    /**
+     * Unregister alert handlers for automatic deletetion of transcoded data if source file is deleted.
+     * Make sure that you call it once, for example upon module uninstallation.
+     */
+    public function unregisterHandlers ()
+    {
+        if (!$this->_oDb->unregisterHandlers ())
+            return false;
+        return $this->clearCacheDB();
+    }
+
+    /**
+     * Delete (queue for deletion) all resized files for the current object
+     */
+    public function cleanup ()
+    {
+        return $this->_oStorage->queueFilesForDeletionFromObject();
+    }
+
+    /**
+     * Get storage object where transcoded data is stored
+     */
+    public function getStorage()
+    {
+        return $this->_oStorage;
+    }
+
+    /**
+     * Get database object instance
+     */
+    public function getDb()
+    {
+        return $this->_oDb;
+    }
+
+    /**
+     * Get transcoded file url.
+     * If transcoded file is ready then direct url to the file is returned.
+     * If there is no transcoded data available, then special url is returned, upon opening this url image is transcoded automatically and redirects to the ready transcoed image.
+     * @params $mixedHandler - file handler
+     * @return file url, or false on error.
+     */
+    public function getFileUrl($mixedHandler)
+    {
+        if ($this->isFileReady($mixedHandler)) {
+
+            $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
+
+            $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+            if (!$iFileId)
+                return false;
+
+            $aFile = $this->_oStorage->getFile($iFileId);
+            if (!$aFile)
+                return false;
+
+            if ($this->_aObject['atime_tracking'])
+                $this->_oDb->updateAccessTime($mixedHandler);
+
+            return $this->_oStorage->getFileUrlById($iFileId);
+        }
+
+        if ($this->_sQueueTable && !$this->addToQueue($mixedHandler)) // add file to queue (when queue is enabled) if it isn't transcoded yet
+            return false;
+
+        return $this->getFileUrlNotReady($mixedHandler);
+    }
+
+    /**
+     * Check if transcoded data is available. No need to call it directly, it is called automatically when it is needed.
+     * @params $mixedHandler - file handler
+     * @params $isCheckOutdated - check if transcoded file is outdated
+     * @return false if there is no ready transcoded file is available or it is outdated, true if file is ready
+     */
+    public function isFileReady ($mixedHandler, $isCheckOutdated = true)
+    {
+        $sMethodFileReady = 'isFileReady_' . $this->_aObject['source_type'];
+        return $this->$sMethodFileReady($mixedHandler, $isCheckOutdated);
+    }
+
+    /**
+     * Transcode file, no need to call it directly, it is called automatically when it is needed.
+     * @params $mixedHandler - file handler
+     * @params $iProfileId - optional profile id, to assign transcoded file ownership to, usually it is NOT assigned to any particular profile, so just leave it default
+     * @return true on success, false on error
+     */
+    public function transcode ($mixedHandler, $iProfileId = 0)
+    {
+        $sExtChange = false;
+
+        $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_PROCESSING, '', gethostname());
+
+        // create tmp file locally
+        $sSuffix = $this->_sQueueStorage ? 'Queue' : $this->_aObject['source_type'];
+        $sMethodStoreFile = 'storeFileLocally_' . $sSuffix;
+        $sTmpFile = $this->$sMethodStoreFile($mixedHandler);
+        if (!$sTmpFile) {
+            $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "store local tmp file failed ($sMethodStoreFile)");
+            return false;
+        }
+
+        // appply filters to tmp file
+        $this->initFilters ();
+        foreach ($this->_aObject['filters'] as $aParams) {
+            $this->clearLog();
+            $sMethodFilter = 'applyFilter_' . $aParams['filter'];
+            if (!method_exists($this, $sMethodFilter))
+                continue;
+            if (!$this->$sMethodFilter($sTmpFile, $aParams['filter_params'])) {
+                unlink($sTmpFile);
+                $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "filter failed: $sMethodFilter\n" . $this->getLog());
+                return false;
+            }
+
+            if (!empty($aParams['filter_params']['force_type'])) {
+                switch ($aParams['filter_params']['force_type']) {
+                    case 'jpeg':
+                    case 'jpg':
+                        $sExtChange = 'jpg';
+                        break;
+                    default:
+                        $sExtChange = $aParams['filter_params']['force_type'];
+                }
+            }
+        }
+
+        if ($sExtChange && false !== ($iDotPos = strrpos($sTmpFile, '.'))) {
+            $sExtOld = substr($sTmpFile, $iDotPos+1);
+            if ($sExtOld != $sExtChange) {
+                $sTmpFileOld = $sTmpFile;
+                $sTmpFile = substr_replace ($sTmpFile, $sExtChange, $iDotPos+1, strlen($sExtOld));
+                if (!rename($sTmpFileOld, $sTmpFile)) {
+                    @unlink($sTmpFileOld);
+                    $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "change file extension failed: $sTmpFileOld => $sTmpFile");
+                    return false;
+                }
+            }
+
+        }
+
+        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
+
+        
+        $bRet = true;
+        if ($this->_sQueueStorage) {
+
+            // TODO: store in tmp storage 
+
+            $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_COMPLETE);
+
+        } else {
+
+            $bRet = $this->storeTranscodedFile($mixedHandler, $sTmpFile, $iProfileId);
+        
+            @unlink($sTmpFile);
+            
+            if ($bRet)
+                $this->_oDb->deleteFromQueue($mixedHandler);
+            else
+                $this->_oDb->updateQueueStatus($mixedHandler, BX_DOL_QUEUE_FAILED, "store file failed:\n" . $this->getLog());
+                
+        }
+
+        return $bRet;
+    }
+
+    /**
+     * Delete outdated files by last access time.
+     * @return number of pruned/deleted files
+     */
+    public function prune ()
+    {
+        $aFiles = $this->_oDb->getFilesForPruning();
+        if (!$aFiles)
+            return false;
+        $iCount = 0;
+        foreach ($aFiles as $r)
+            $iCount += $this->_oStorage->deleteFile($r['file_id']) ? 1 : 0;
+        return $iCount;
+    }
+
+    public function getDevicePixelRatio ()
+    {
+        if (isset($_COOKIE['devicePixelRatio']) && (!isset($this->_aObject['source_params']['disable_retina']) || !$this->_aObject['source_params']['disable_retina'])) {
+            $iDevicePixelRatio = intval($_COOKIE['devicePixelRatio']);
+            if ($iDevicePixelRatio >= 2)
+                return 2;
+        }
+        return 1;
+    }
+
+    public function getOrigFileUrl ($mixedHandler)
+    {
+        $sMethodStoreFile = 'getOrigFileUrl_' . $this->_aObject['source_type'];
+        return $this->$sMethodStoreFile($mixedHandler);
+    }
+
+    /**
+     * Get file url when file isn't transcoded yet
+     */
+    public function getFileUrlNotReady($mixedHandler)
+    {
+        return false;
+    }
+
+    public function clearLog()
+    {
+        $this->_sLog = '';
+    }
+
+    public function addToLog($s)
+    {
+        $this->_sLog .= $s;
+    }
+
+    public function getLog()
+    {
+        return $this->_sLog;
+    }
+
+    protected function storeTranscodedFile ($mixedHandler, $sTmpFile, $iProfileId)
+    {
+        $sMethodIsPrivate = 'isPrivate_' . $this->_aObject['source_type'];
+        $isPrivate = $this->$sMethodIsPrivate($mixedHandler);
+        $iFileId = $this->_oStorage->storeFileFromPath ($sTmpFile, $isPrivate, $iProfileId);
+        if (!$iFileId) {
+            $this->addToLog($this->_oStorage->getErrorString());
+            return false;
+        }
+
+        if (!$this->_oDb->updateHandler($iFileId, $mixedHandler)) {
+            $this->_oStorage->deleteFile($iFileId);
+            return false;
+        }
+
+        $this->_oStorage->afterUploadCleanup($iFileId, $iProfileId);
+
+        return true;
+    }
+
+    protected function addToQueue($mixedHandler, $iProfileId = 0)
+    {
+        if ($this->_oDb->getFromQueue($mixedHandler))
+            return true;
+        $sFileUrl = $this->getOrigFileUrl($mixedHandler);
+        return $this->_oDb->addToQueue ($mixedHandler, $sFileUrl, $iProfileId);
+    }
+
+    protected function isFileReady_Folder ($mixedHandlerOrig, $isCheckOutdated = true)
+    {
+        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
+
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if (!$iFileId)
+            return false;
+
+        $aFile = $this->_oStorage->getFile($iFileId);
+        if (!$aFile)
+            return false;
+
+        if ($isCheckOutdated) { // warning, $isCheckOutdated is partially supported for Folder source type - file modification is not checked
+            if ($this->_aObject['ts'] > $aFile['modified'] || !$this->getFilePath_Folder($mixedHandlerOrig)) { // if we changed transcoder object params or original file is deleted
+                // delete file, so it will be recreated next time
+                if ($this->_oStorage->deleteFile($aFile['id']))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected function isFileReady_Storage ($mixedHandlerOrig, $isCheckOutdated = true)
+    {
+        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
+
+        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
+        if (!$iFileId)
+            return false;
+
+        $aFile = $this->_oStorage->getFile($iFileId);
+        if (!$aFile)
+            return false;
+
+        if ($isCheckOutdated) {
+            $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+            if ($oStorageOriginal) {
+                $aFileOriginal = $oStorageOriginal->getFile($mixedHandlerOrig);
+                if (!$aFileOriginal || $aFileOriginal['modified'] > $aFile['modified'] || $this->_aObject['ts'] > $aFile['modified']) { // if original file was changed OR we changed transcoder object params
+                    // delete file, so it will be recreated next time
+                    if ($this->_oStorage->deleteFile($aFile['id']))
+                        return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    protected function isPrivate_Folder ($mixedHandler)
+    {
+        if ('no' == $this->_aObject['private'])
+            return false;
+        return true;
+    }
+
+    protected function isPrivate_Storage ($mixedHandler)
+    {
+        switch ($this->_aObject['private']) {
+            case 'no':
+                return false;
+            case 'yes':
+                return true;
+            default:
+            case 'auto':
+                $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+                if (!$oStorageOriginal)
+                    return true; // in case of error - make sure that file is not public accidentally
+                $aFile = $oStorageOriginal->getFile($mixedHandler);
+                return $oStorageOriginal->isFilePrivate($aFile['id']);
+        }
+    }
+
+    protected function getFilePath_Folder ($mixedHandler)
+    {
+        $sPath = $this->_aObject['source_params']['path'] . $mixedHandler;
+        if (!file_exists($sPath))
+            $sPath = BX_DIRECTORY_PATH_ROOT . $sPath;
+
+        if (!file_exists($sPath))
+            return false;
+
+        return $sPath;
+    }
+
+    protected function storeFileLocally_Folder ($mixedHandler)
+    {
+        $sPath = $this->getFilePath_Folder($mixedHandler);
+        if (!$sPath)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($mixedHandler);
+        if (!copy($sPath, $sTmpFile))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function storeFileLocally_Storage ($mixedHandler)
+    {
+        $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+        if (!$oStorageOriginal)
+            return false;
+
+        $aFile = $oStorageOriginal->getFile($mixedHandler);
+        if (!$aFile)
+            return false;
+
+        $sUrl = $oStorageOriginal->getFileUrlById($mixedHandler);
+        if (!$sUrl)
+            return false;
+
+        $sFileData = bx_file_get_contents ($sUrl);
+        if (false === $sFileData)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($aFile['file_name']);
+        if (!file_put_contents($sTmpFile, $sFileData))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function storeFileLocally_Queue ($mixedHandler)
+    {
+        $sMethodStoreFile = 'storeFileLocally_' . $this->_aObject['source_type'];
+        $sTmpFile = $this->$sMethodStoreFile($mixedHandler);
+        if ($sTmpFile)
+            return $sTmpFile;
+        
+        $aQueue = $o->getDb->getFromQueue($r['file_id_source']);
+        if (!$aQueue || !$aQueue['file_url_source'])
+            return false;
+
+        $sFileData = bx_file_get_contents ($aQueue['file_url_source']);
+        if (false === $sFileData)
+            return false;
+
+        $sTmpFile = $this->getTmpFilename ($mixedHandler);
+        if (!file_put_contents($sTmpFile, $sFileData))
+            return false;
+
+        return $sTmpFile;
+    }
+
+    protected function getOrigFileUrl_Folder ($mixedHandler)
+    {
+        $sPath = $this->_aObject['source_params']['path'] . $mixedHandler;
+        if (!file_exists(BX_DIRECTORY_PATH_ROOT . $sPath))
+            return false;
+        
+        return BX_DOL_URL_ROOT . $sPath;
+    }
+
+    protected function getOrigFileUrl_Storage ($mixedHandler)
+    {
+        $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
+        if (!$oStorageOriginal)
+            return false;
+
+        return $oStorageOriginal->getFileUrlById($mixedHandler);
+    }
+
+    protected function getTmpFilename ($sOverrideName = false)
+    {
+        if ($sOverrideName)
+            return BX_DIRECTORY_PATH_TMP . rand(10000, 99999) . $sOverrideName;
+        return tempnam(BX_DIRECTORY_PATH_TMP, $this->_aObject['object']);
+    }
+
+    protected function initFilters ()
+    {
+        if (false !== $this->_aObject['filters']) // filters are already initilized
+            return;
+
+        // get transcoder filters
+        $aFilters = $this->_oDb->getTranscoderFilters();
+        $this->_aObject['filters'] = array();
+        foreach ($aFilters as $aFilter) {
+            if ($aFilter['filter_params'])
+                $aFilter['filter_params'] = unserialize($aFilter['filter_params']);
+            $this->_aObject['filters'][] = $aFilter;
+        }
+
+    }
+
+    protected function clearCacheDB()
+    {
+        $oCacheDb = $this->_oDb->getDbCacheObject();
+        return $oCacheDb->removeAllByPrefix('db_');
+    }
+
+    public function getDevicePixelRatioHandlerSuffix ()
+    {
+        $iDevicePixelRatio = $this->getDevicePixelRatio ();
+        if ($iDevicePixelRatio >= 2)
+            return $this->_sRetinaSuffix;
+        return '';
+    }
+
+    protected function processHandlerForRetinaDevice ($mixedHandler)
+    {
+        return '' . $mixedHandler . $this->getDevicePixelRatioHandlerSuffix ();
+    }
+
+    static protected function _callFuncForObjectsArray ($mixed, $sFunc)
+    {
+        if (!is_array($mixed))
+            $mixed = array($mixed);
+        foreach ($mixed as $sObject) {
+            $oTranscoder = self::getObjectInstance($sObject);
+            if ($oTranscoder)
+                $oTranscoder->$sFunc();
+        }
+    }
+}
+
+/** @} */

+ 10 - 511
inc/classes/BxDolTranscoderImage.php

@@ -7,7 +7,7 @@
  * @{
  * @{
  */
  */
 
 
-bx_import('BxDolStorage');
+bx_import('BxDolTranscoder');
 bx_import('BxDolTranscoderImageQuery');
 bx_import('BxDolTranscoderImageQuery');
 
 
 /**
 /**
@@ -21,7 +21,7 @@ bx_import('BxDolTranscoderImageQuery');
  * Transcoded image is saved in the specified storage engine and next time ready image is served.
  * Transcoded image is saved in the specified storage engine and next time ready image is served.
  *
  *
  *
  *
- * To add image transcoder object add record to 'sys_objects_transcoder_images' table:
+ * To add image transcoder object add record to 'sys_objects_transcoder' table:
  * - object - name of the transcoder object, in the format: vendor prefix, underscore, module prefix, underscore, image size name; for example: bx_images_thumb.
  * - object - name of the transcoder object, in the format: vendor prefix, underscore, module prefix, underscore, image size name; for example: bx_images_thumb.
  * - storage_object - name of the storage object to store transcoded data, the specified storage object need to be created too, @see BxDolStorage.
  * - storage_object - name of the storage object to store transcoded data, the specified storage object need to be created too, @see BxDolStorage.
  * - source_type - type of the source, where is source image is taken from, available options 'Storage' and 'Folder' for now.
  * - source_type - type of the source, where is source image is taken from, available options 'Storage' and 'Folder' for now.
@@ -35,7 +35,7 @@ bx_import('BxDolTranscoderImageQuery');
  * - ts - unix timestamp of the last change of transcoder parameters, if transcoded image is older than this value - image is deleted and transcoded again.
  * - ts - unix timestamp of the last change of transcoder parameters, if transcoded image is older than this value - image is deleted and transcoded again.
  *
  *
  *
  *
- * Then you need to add image filters to 'sys_transcoder_images_filters' table:
+ * Then you need to add image filters to 'sys_transcoder_filters' table:
  * - transcoder_object - name of the transcoded object to apply filter to.
  * - transcoder_object - name of the transcoded object to apply filter to.
  * - filter - filter name, please read futher for available filters.
  * - filter - filter name, please read futher for available filters.
  * - filter_params - serialized array of filter params, please read futher for particular filters params.
  * - filter_params - serialized array of filter params, please read futher for particular filters params.
@@ -81,478 +81,23 @@ bx_import('BxDolTranscoderImageQuery');
  * @endcode
  * @endcode
  *
  *
  */
  */
-class BxDolTranscoderImage extends BxDol implements iBxDolFactoryObject
+class BxDolTranscoderImage extends BxDolTranscoder implements iBxDolFactoryObject
 {
 {
-    protected $_aObject; ///< object properties
-    protected $_oStorage; ///< storage object, transcoded files are stored here
-    protected $_oDb; ///< database queries object
-    protected $_sRetinaSuffix = '@2x'; ///< handler suffix for retina image file
-
-    /**
-     * constructor
-     */
-    protected function __construct($aObject, $oStorage, $oDb)
-    {
-        parent::__construct();
-        $this->_aObject = $aObject;
-        $this->_oStorage = $oStorage;
-        $this->_oDb = $oDb;
-    }
-
-    /**
-     * Get transcode object instance.
-     * @param $sObject - name of trancode object.
-     * @return false on error or instance of BxDolTranscoderImage class.
-     */
-    public static function getObjectInstance($sObject)
-    {
-        if (isset($GLOBALS['bxDolClasses']['BxDolTranscoderImage!'.$sObject]))
-            return $GLOBALS['bxDolClasses']['BxDolTranscoderImage!'.$sObject];
-
-        // get transcode object
-        $aObject = BxDolTranscoderImageQuery::getTranscoderObject($sObject);
-        if (!$aObject || !is_array($aObject))
-            return false;
-
-        if ($aObject['source_params'])
-            $aObject['source_params'] = unserialize($aObject['source_params']);
-
-        $aObject['filters'] = false; // filters are initialized on demand
-
-        // create storage object to store transcoded data
-        $oStorage = BxDolStorage::getObjectInstance($aObject['storage_object']);
-        if (!$oStorage)
-            return false;
-
-        // create instance
-        $o = new BxDolTranscoderImage($aObject, $oStorage, new BxDolTranscoderImageQuery($aObject));
-
-        return ($GLOBALS['bxDolClasses']['BxDolTranscoderImage!'.$sObject] = $o);
-    }
-
-    /**
-     * Delete outdated transcoed data from all transcoeeed objects, by last access time.
-     * It called on cron, usually every day.
-     * @return total number of pruned/deleted files
-     */
-    static public function pruning ()
+    protected function __construct($aObject, $oStorage)
     {
     {
-        $iCount = 0;
-        $aObjects = BxDolTranscoderImageQuery::getTranscoderObjects ();
-        foreach ($aObjects as $aObject) {
-            if (!$aObject['atime_tracking'] || !$aObject['atime_pruning'])
-                continue;
-            $oTranscoder = BxDolTranscoderImage::getObjectInstance($aObject['object']);
-            if (!$oTranscoder)
-                continue;
-            $iCount += $oTranscoder->prune();
-        }
-        return $iCount;
+        parent::__construct($aObject, $oStorage);
+        $this->_oDb = new BxDolTranscoderImageQuery($aObject, false);
+        $this->_sQueueTable = $this->_oDb->getQueueTable();
     }
     }
 
 
     /**
     /**
-     * Register handlers array
-     * It can be called upon module enable event.
-     * @param $mixed array of transcoders objects, or just one object
+     * Get file url when file isn't transcoded yet
      */
      */
-    static public function registerHandlersArray ($mixed)
+    public function getFileUrlNotReady($mixedHandler)
     {
     {
-        self::_callFuncForObjectsArray ($mixed, 'registerHandlers');
-    }
-
-    /**
-     * Unregister handlers array
-     * It can be called upon module disbale event.
-     * @param $mixed array of transcoders objects, or just one object
-     */
-    static public function unregisterHandlersArray ($mixed)
-    {
-        self::_callFuncForObjectsArray ($mixed, 'unregisterHandlers');
-    }
-
-    /**
-     * Cleanup (queue for deletion all resized files)
-     * It can be called upon module disbale event.
-     * @param $mixed array of transcoders objects, or just one object
-     */
-    static public function cleanupObjectsArray ($mixed)
-    {
-        self::_callFuncForObjectsArray ($mixed, 'cleanup');
-    }
-
-    /**
-     * Called automatically, upon local(transcoded) file deletetion.
-     */
-    static public function onAlertResponseFileDeleteLocal ($oAlert, $sObject)
-    {
-        $oTranscoder = self::getObjectInstance($sObject);
-        if (!$oTranscoder)
-            return;
-
-        if ($oAlert->sAction != 'file_deleted')
-            return;
-
-        $oTranscoder->onDeleteFileLocal($oAlert->iObject);
-    }
-
-    /**
-     * Called automatically, upon original file deletetion.
-     */
-    static public function onAlertResponseFileDeleteOrig ($oAlert, $sObject)
-    {
-        $oTranscoder = self::getObjectInstance($sObject);
-        if (!$oTranscoder)
-            return;
-
-        if ($oAlert->sAction != 'file_deleted')
-            return;
-
-        $oTranscoder->onDeleteFileOrig($oAlert->iObject);
-    }
-
-    /**
-     * Called automatically, upon local(transcoded) file deletetion.
-     */
-    public function onDeleteFileLocal($iFileId)
-    {
-        return $this->_oDb->deleteFileTraces($iFileId);
-    }
-
-    /**
-     * Called automatically, upon original file deletetion.
-     */
-    public function onDeleteFileOrig($mixedHandler)
-    {
-        // delete main file
-        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
-        if (!$iFileId)
-            return false;
-
-        if (!$this->_oStorage->deleteFile($iFileId))
-            return false;
-
-        // delete retina file
-        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler . $this->_sRetinaSuffix);
-        if ($iFileId)
-            $this->_oStorage->deleteFile($iFileId);
-
-        return true;
-    }
-
-    /**
-     * Register necessary alert handlers for automatic deletetion of transcoded data if source file is deleted.
-     * Make sure that you call it once, before first usage, for example upon module installation.
-     */
-    public function registerHandlers ()
-    {
-        if (!$this->_oDb->registerHandlers ())
-            return false;
-        return $this->clearCacheDB();
-    }
-
-    /**
-     * Unregister alert handlers for automatic deletetion of transcoded data if source file is deleted.
-     * Make sure that you call it once, for example upon module uninstallation.
-     */
-    public function unregisterHandlers ()
-    {
-        if (!$this->_oDb->unregisterHandlers ())
-            return false;
-        return $this->clearCacheDB();
-    }
-
-    /**
-     * Delete (queue for deletion) all resized files for the current object
-     */
-    public function cleanup ()
-    {
-        return $this->_oStorage->queueFilesForDeletionFromObject();
-    }
-
-    /**
-     * Get storage object where transcoded data is stored
-     */
-    public function getStorage()
-    {
-        return $this->_oStorage;
-    }
-
-    /**
-     * Get transcoded file url.
-     * If transcoded file is ready then direct url to the file is returned.
-     * If there is no transcoded data available, then special url is returned, upon opening this url image is transcoded automatically and redirects to the ready transcoed image.
-     * @params $mixedHandler - file handler
-     * @return file url, or false on error.
-     */
-    public function getFileUrl($mixedHandler)
-    {
-        if ($this->isFileReady($mixedHandler)) {
-
-            $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
-
-            $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
-            if (!$iFileId)
-                return false;
-
-            $aFile = $this->_oStorage->getFile($iFileId);
-            if (!$aFile)
-                return false;
-
-            if ($this->_aObject['atime_tracking'])
-                $this->_oDb->updateAccessTime($mixedHandler);
-
-            return $this->_oStorage->getFileUrlById($iFileId);
-        }
-
         return BX_DOL_URL_ROOT . 'image_transcoder.php?o=' . $this->_aObject['object'] . '&h=' . $mixedHandler . '&dpx=' . $this->getDevicePixelRatio() . '&t=' . time();
         return BX_DOL_URL_ROOT . 'image_transcoder.php?o=' . $this->_aObject['object'] . '&h=' . $mixedHandler . '&dpx=' . $this->getDevicePixelRatio() . '&t=' . time();
     }
     }
 
 
-    /**
-     * Check if transcoded data is available. No need to call it directly, it is called automatically when it is needed.
-     * @params $mixedHandler - file handler
-     * @params $isCheckOutdated - check if transcoded file is outdated
-     * @return false if there is no ready transcoded file is available or it is outdated, true if file is ready
-     */
-    public function isFileReady ($mixedHandler, $isCheckOutdated = true)
-    {
-        $sMethodFileReady = 'isFileReady_' . $this->_aObject['source_type'];
-        return $this->$sMethodFileReady($mixedHandler, $isCheckOutdated);
-    }
-
-    /**
-     * Transcode file, no need to call it directly, it is called automatically when it is needed.
-     * @params $mixedHandler - file handler
-     * @params $iProfileId - optional profile id, to assign transcoded file ownership to, usually it is NOT assigned to any particular profile, so just leave it default
-     * @return true on success, false on error
-     */
-    public function transcode ($mixedHandler, $iProfileId = 0)
-    {
-        $sExtChange = false;
-
-        // create tmp file locally
-        $sMethodStoreFile = 'storeFileLocally_' . $this->_aObject['source_type'];
-        $sTmpFile = $this->$sMethodStoreFile($mixedHandler);
-        if (!$sTmpFile)
-            return false;
-
-        // appply filters to tmp file
-        $this->initFilters ();
-        foreach ($this->_aObject['filters'] as $aParams) {
-            $sMethodFilter = 'applyFilter_' . $aParams['filter'];
-            if (!method_exists($this, $sMethodFilter))
-                continue;
-            if (!$this->$sMethodFilter($sTmpFile, $aParams['filter_params'])) {
-                unlink($sTmpFile);
-                return false;
-            }
-
-            if (!empty($aParams['filter_params']['force_type'])) {
-                switch ($aParams['filter_params']['force_type']) {
-                    case 'jpeg':
-                    case 'jpg':
-                        $sExtChange = 'jpg';
-                        break;
-                    case 'png':
-                        $sExtChange = 'png';
-                        break;
-                    case 'gif':
-                        $sExtChange = 'gif';
-                        break;
-                }
-            }
-        }
-
-        if ($sExtChange && false !== ($iDotPos = strrpos($sTmpFile, '.'))) {
-            $sExtOld = substr($sTmpFile, $iDotPos+1);
-            if ($sExtOld != $sExtChange) {
-                $sTmpFileOld = $sTmpFile;
-                $sTmpFile = substr_replace ($sTmpFile, $sExtChange, $iDotPos+1, strlen($sExtOld));
-                if (!rename($sTmpFileOld, $sTmpFile)) {
-                    unlink($sTmpFileOld);
-                    return false;
-                }
-            }
-
-        }
-
-        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandler);
-
-        // store transcoded file in the storage
-        $sMethodIsPrivate = 'isPrivate_' . $this->_aObject['source_type'];
-        $isPrivate = $this->$sMethodIsPrivate($mixedHandler);
-        $iFileId = $this->_oStorage->storeFileFromPath ($sTmpFile, $isPrivate, $iProfileId);
-        @unlink($sTmpFile);
-        if (!$iFileId)
-            return false;
-
-        if (!$this->_oDb->updateHandler($iFileId, $mixedHandler)) {
-            $this->_oStorage->deleteFile($iFileId);
-            return false;
-        }
-
-        $this->_oStorage->afterUploadCleanup($iFileId, $iProfileId);
-
-        return true;
-    }
-
-    /**
-     * Delete outdated files by last access time.
-     * @return number of pruned/deleted files
-     */
-    public function prune ()
-    {
-        $aFiles = $this->_oDb->getFilesForPruning();
-        if (!$aFiles)
-            return false;
-        $iCount = 0;
-        foreach ($aFiles as $r)
-            $iCount += $this->_oStorage->deleteFile($r['file_id']) ? 1 : 0;
-        return $iCount;
-    }
-
-    public function getDevicePixelRatio ()
-    {
-        if (isset($_COOKIE['devicePixelRatio']) && (!isset($this->_aObject['source_params']['disable_retina']) || !$this->_aObject['source_params']['disable_retina'])) {
-            $iDevicePixelRatio = intval($_COOKIE['devicePixelRatio']);
-            if ($iDevicePixelRatio >= 2)
-                return 2;
-        }
-        return 1;
-    }
-
-    // ---------------------------
-
-    protected function isFileReady_Folder ($mixedHandlerOrig, $isCheckOutdated = true)
-    {
-        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
-
-        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
-        if (!$iFileId)
-            return false;
-
-        $aFile = $this->_oStorage->getFile($iFileId);
-        if (!$aFile)
-            return false;
-
-        if ($isCheckOutdated) { // warning, $isCheckOutdated is partially supported for Folder source type - file modification is not checked
-            if ($this->_aObject['ts'] > $aFile['modified'] || !$this->getFilePath_Folder($mixedHandlerOrig)) { // if we changed transcoder object params or original file is deleted
-                // delete file, so it will be recreated next time
-                if ($this->_oStorage->deleteFile($aFile['id']))
-                    return false;
-            }
-        }
-
-        return true;
-    }
-
-    protected function isFileReady_Storage ($mixedHandlerOrig, $isCheckOutdated = true)
-    {
-        $mixedHandler = $this->processHandlerForRetinaDevice($mixedHandlerOrig);
-
-        $iFileId = $this->_oDb->getFileIdByHandler($mixedHandler);
-        if (!$iFileId)
-            return false;
-
-        $aFile = $this->_oStorage->getFile($iFileId);
-        if (!$aFile)
-            return false;
-
-        if ($isCheckOutdated) {
-            $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
-            if ($oStorageOriginal) {
-                $aFileOriginal = $oStorageOriginal->getFile($mixedHandlerOrig);
-                if (!$aFileOriginal || $aFileOriginal['modified'] > $aFile['modified'] || $this->_aObject['ts'] > $aFile['modified']) { // if original file was changed OR we changed transcoder object params
-                    // delete file, so it will be recreated next time
-                    if ($this->_oStorage->deleteFile($aFile['id']))
-                        return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    protected function isPrivate_Folder ($mixedHandler)
-    {
-        if ('no' == $this->_aObject['private'])
-            return false;
-        return true;
-    }
-
-    protected function isPrivate_Storage ($mixedHandler)
-    {
-        switch ($this->_aObject['private']) {
-            case 'no':
-                return false;
-            case 'yes':
-                return true;
-            default:
-            case 'auto':
-                $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
-                if (!$oStorageOriginal)
-                    return true; // in case of error - make sure that file is not public accidentally
-                $aFile = $oStorageOriginal->getFile($mixedHandler);
-                return $oStorageOriginal->isFilePrivate($aFile['id']);
-        }
-    }
-
-    protected function getFilePath_Folder ($mixedHandler)
-    {
-        $sPath = $this->_aObject['source_params']['path'] . $mixedHandler;
-        if (!file_exists($sPath))
-            $sPath = BX_DIRECTORY_PATH_ROOT . $sPath;
-
-        if (!file_exists($sPath))
-            return false;
-
-        return $sPath;
-    }
-
-    protected function storeFileLocally_Folder ($mixedHandler)
-    {
-        $sPath = $this->getFilePath_Folder($mixedHandler);
-        if (!$sPath)
-            return false;
-
-        $sTmpFile = $this->getTmpFilename ($mixedHandler);
-        if (!copy($sPath, $sTmpFile))
-            return false;
-
-        return $sTmpFile;
-    }
-
-    protected function storeFileLocally_Storage ($mixedHandler)
-    {
-        $oStorageOriginal = BxDolStorage::getObjectInstance($this->_aObject['source_params']['object']);
-        if (!$oStorageOriginal)
-            return false;
-
-        $aFile = $oStorageOriginal->getFile($mixedHandler);
-        if (!$aFile)
-            return false;
-
-        $sUrl = $oStorageOriginal->getFileUrlById($mixedHandler);
-        if (!$sUrl)
-            return false;
-
-        $sFileData = bx_file_get_contents ($sUrl);
-        if (false === $sFileData)
-            return false;
-
-        $sTmpFile = $this->getTmpFilename ($aFile['file_name']);
-        if (!file_put_contents($sTmpFile, $sFileData))
-            return false;
-
-        return $sTmpFile;
-    }
-
-    protected function getTmpFilename ($sOverrideName = false)
-    {
-        if ($sOverrideName)
-            return BX_DIRECTORY_PATH_TMP . rand(10000, 99999) . $sOverrideName;
-        return tempnam(BX_DIRECTORY_PATH_TMP, $this->_aObject['object']);
-    }
-
     protected function applyFilter_Grayscale ($sFile, $aParams)
     protected function applyFilter_Grayscale ($sFile, $aParams)
     {
     {
         bx_import ('BxDolImageResize');
         bx_import ('BxDolImageResize');
@@ -613,52 +158,6 @@ class BxDolTranscoderImage extends BxDol implements iBxDolFactoryObject
                 break;
                 break;
         }
         }
     }
     }
-
-    protected function initFilters ()
-    {
-        if (false !== $this->_aObject['filters']) // filters are already initilized
-            return;
-
-        // get transcoder filters
-        $aFilters = $this->_oDb->getTranscoderFilters();
-        $this->_aObject['filters'] = array();
-        foreach ($aFilters as $aFilter) {
-            if ($aFilter['filter_params'])
-                $aFilter['filter_params'] = unserialize($aFilter['filter_params']);
-            $this->_aObject['filters'][] = $aFilter;
-        }
-
-    }
-
-    protected function clearCacheDB()
-    {
-        $oCacheDb = $this->_oDb->getDbCacheObject();
-        return $oCacheDb->removeAllByPrefix('db_');
-    }
-
-    public function getDevicePixelRatioHandlerSuffix ()
-    {
-        $iDevicePixelRatio = $this->getDevicePixelRatio ();
-        if ($iDevicePixelRatio >= 2)
-            return $this->_sRetinaSuffix;
-        return '';
-    }
-
-    protected function processHandlerForRetinaDevice ($mixedHandler)
-    {
-        return '' . $mixedHandler . $this->getDevicePixelRatioHandlerSuffix ();
-    }
-
-    static protected function _callFuncForObjectsArray ($mixed, $sFunc)
-    {
-        if (!is_array($mixed))
-            $mixed = array($mixed);
-        foreach ($mixed as $sObject) {
-            $oTranscoder = self::getObjectInstance($sObject);
-            if ($oTranscoder)
-                $oTranscoder->$sFunc();
-        }
-    }
 }
 }
 
 
 /** @} */
 /** @} */

+ 7 - 150
inc/classes/BxDolTranscoderImageQuery.php

@@ -7,163 +7,20 @@
  * @{
  * @{
  */
  */
 
 
-bx_import('BxDolDb');
+bx_import('BxDolTranscoderQuery');
 
 
 /**
 /**
- * @see BxDolTranscoderImage
+ * @see BxDolTranscoderVideo
  */
  */
-class BxDolTranscoderImageQuery extends BxDolDb
+class BxDolTranscoderImageQuery extends BxDolTranscoderQuery
 {
 {
-    protected $_aObject;
-
     public function __construct($aObject)
     public function __construct($aObject)
     {
     {
-        parent::__construct();
-        $this->_aObject = $aObject;
-    }
-
-    static public function getTranscoderObject ($sObject)
-    {
-        $oDb = BxDolDb::getInstance();
-        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_transcoder_images` WHERE `object` = ?", $sObject);
-        return $oDb->getRow($sQuery);
-    }
-
-    static public function getTranscoderObjects ()
-    {
-        $oDb = BxDolDb::getInstance();
-        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_transcoder_images`");
-        return $oDb->getAll($sQuery);
-    }
-
-    public function getTranscoderFilters ()
-    {
-        $sQuery = $this->prepare("SELECT * FROM `sys_transcoder_images_filters` WHERE `transcoder_object` = ? ORDER BY `order` ASC", $this->_aObject['object']);
-        return $this->getAll($sQuery);
-    }
-
-    public function updateHandler ($iFileId, $mixedHandler)
-    {
-        $sUpdateATime = '';
-        if ($this->_aObject['atime_tracking']) {
-            $iTime = time();
-            $sUpdateATime = $this->prepare(", `atime` = ?", $iTime);
-        }
-        $sQuery = $this->prepare("INSERT INTO `sys_transcoder_images_files` SET `transcoder_object` = ?, `file_id` = ?, `handler` = ? $sUpdateATime
-            ON DUPLICATE KEY UPDATE `file_id` = ? $sUpdateATime", $this->_aObject['object'], $iFileId, $mixedHandler, $iFileId);
-        return $this->res($sQuery);
-    }
-
-    public function getFileIdByHandler ($mixedHandler)
-    {
-        $sQuery = $this->prepare("SELECT `file_id` FROM `sys_transcoder_images_files` WHERE `transcoder_object` = ? AND `handler` = ?", $this->_aObject['object'], $mixedHandler);
-        return $this->getOne($sQuery);
-    }
-
-    public function updateAccessTime($mixedHandler)
-    {
-        $iTime = time();
-        $sQuery = $this->prepare("UPDATE `sys_transcoder_images_files` SET `atime` = ? WHERE `transcoder_object` = ? AND `handler` = ?", $iTime, $this->_aObject['object'], $mixedHandler);
-        return $this->res($sQuery);
-    }
-
-    public function deleteFileTraces($iFileId)
-    {
-        $sQuery = $this->prepare("DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` = ? AND `file_id` = ?", $this->_aObject['object'], $iFileId);
-        return $this->res($sQuery);
-    }
-
-    public function getFilesForPruning ()
-    {
-        if (!$this->_aObject['atime_tracking'] || !$this->_aObject['atime_pruning'])
-            continue;
-
-        $sQuery = $this->prepare("SELECT * FROM `sys_transcoder_images_files` WHERE `transcoder_object` = ? AND `atime` != 0 AND `atime` < ?", $this->_aObject['object'], time() - $this->_aObject['atime_pruning']);
-        return $this->getAll($sQuery);
-    }
-
-    public function registerHandlers ()
-    {
-        if (!$this->registerHandler ('getAlertHandlerNameLocal', 'alert_response_file_delete_local', $this->_aObject['object'], $this->_aObject['storage_object']))
-            return false;
-
-        // add handler for original storage engine
-        if ('Storage' == $this->_aObject['source_type']) // if original storage is "Storage", not "Folder"
-            if (!$this->registerHandler ('getAlertHandlerNameOrig', 'alert_response_file_delete_orig', $this->_aObject['object'], $this->_aObject['source_params']['object']))
-                return false;
-
-        return true;
-    }
-
-    public function unregisterHandlers ()
-    {
-        if (!$this->unregisterHandler('getAlertHandlerNameLocal', $this->_aObject['storage_object']))
-            return false;
-
-        // remove handler for original storage engine
-        if ('Storage' == $this->_aObject['source_type']) // if original storage is "Storage", not "Folder"
-            if (!$this->unregisterHandler('getAlertHandlerNameOrig', $this->_aObject['source_params']['object']))
-                return false;
-
-        return true;
-    }
-
-    protected function registerHandler ($sHandlerNameFunc, $sServiceFunc, $sObject, $sUnit)
-    {
-        $sHandlerName = $this->$sHandlerNameFunc ();
-        $iHandlerId = $this->getAlertHandlerId ($sHandlerName);
-        if ($iHandlerId) // if handler already exists, do nothing
-            return true;
-
-        $sServiceCall = serialize(array(
-            'module' => 'system',
-            'method' => $sServiceFunc,
-            'params' => array($sObject),
-            'class' => 'TemplImageServices'
-        ));
-        $sQuery = $this->prepare("INSERT INTO `sys_alerts_handlers` SET `name` = ?, `service_call` = ?", $sHandlerName, $sServiceCall);
-        if (!$this->query($sQuery))
-            return false;
-        $iHandlerId = $this->lastId();
-        $sQuery = $this->prepare("INSERT INTO `sys_alerts` SET `unit` = ?, `action` = ?, `handler_id` = ?", $sUnit, 'file_deleted', $iHandlerId);
-        if (!$this->query($sQuery)) {
-            $this->unregisterHandlers ();
-            return false;
-        }
-
-        return true;
-    }
-
-    protected function unregisterHandler ($sHandlerNameFunc, $sUnit)
-    {
-        $sHandlerName = $this->$sHandlerNameFunc ();
-        $iHandlerId = $this->getAlertHandlerId ($sHandlerName);
-        if (!$iHandlerId) // if handler is alrady missing, do nothing
-            return true;
-
-        $sQuery = $this->prepare("DELETE FROM `sys_alerts` WHERE `unit` = ? AND `action` = ? AND `handler_id` = ?", $sUnit, 'file_deleted', $iHandlerId);
-        if (!$this->query($sQuery))
-            return false;
-
-        $sQuery = $this->prepare("DELETE FROM `sys_alerts_handlers` WHERE `id` = ?", $iHandlerId);
-        return $this->query($sQuery);
-    }
-
-    protected function getAlertHandlerNameLocal ()
-    {
-        return 'sys_image_transcoder_local_file_delete_' . $this->_aObject['object'];
-    }
-
-    protected function getAlertHandlerNameOrig ()
-    {
-        return 'sys_image_transcoder_orig_file_delete_' . $this->_aObject['object'];
-    }
-
-    protected function getAlertHandlerId ($sHandlerName)
-    {
-        $sQuery = $this->prepare("SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = ?", $sHandlerName);
-        return $this->getOne($sQuery);
+        parent::__construct($aObject, false);
+        $this->_sTableFiles = '`sys_transcoder_images_files`';
+        $this->_sHandlerPrefix = 'sys_image_transcoder_';
     }
     }
 }
 }
 
 
 /** @} */
 /** @} */
+

+ 247 - 0
inc/classes/BxDolTranscoderQuery.php

@@ -0,0 +1,247 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolDb');
+
+define('BX_DOL_QUEUE_PRUNE_FAILED', 604800); ///< timeout in seconds when 'failed' items are deleted
+define('BX_DOL_QUEUE_PRUNE_PROCESSING', 86400); ///< timeout in seconds when 'processing' items are deleted, in case when something went wrong during processing
+
+/**
+ * @see BxDolTranscoder
+ */
+class BxDolTranscoderQuery extends BxDolDb
+{
+    const TABLE_QUEUE = 'sys_transcoder_queue';
+
+    protected $_aObject;
+    protected $_sTableQueue;
+    protected $_sTableFiles;
+    protected $_sTableFilters;
+    protected $_sHandlerPrefix;
+
+    public function __construct($aObject, $bUseQueue = false)
+    {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_sTableQueue = $bUseQueue ? self::TABLE_QUEUE : '';
+        $this->_sTableFiles = '`sys_transcoder_images_files`';
+        $this->_sTableFilters = '`sys_transcoder_filters`';
+        $this->_sHandlerPrefix = 'sys_image_transcoder_';
+    }
+
+    static public function getTranscoderObject ($sObject)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_transcoder` WHERE `object` = ?", $sObject);
+        return $oDb->getRow($sQuery);
+    }
+
+    static public function getTranscoderObjects ()
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_transcoder`");
+        return $oDb->getAll($sQuery);
+    }
+
+    public function getTranscoderFilters ()
+    {
+        $sQuery = $this->prepare("SELECT * FROM {$this->_sTableFilters} WHERE `transcoder_object` = ? ORDER BY `order` ASC", $this->_aObject['object']);
+        return $this->getAll($sQuery);
+    }
+
+    public function updateHandler ($iFileId, $mixedHandler)
+    {
+        $sUpdateATime = '';
+        if ($this->_aObject['atime_tracking']) {
+            $iTime = time();
+            $sUpdateATime = $this->prepare(", `atime` = ?", $iTime);
+        }
+        $sQuery = $this->prepare("INSERT INTO {$this->_sTableFiles} SET `transcoder_object` = ?, `file_id` = ?, `handler` = ? $sUpdateATime
+            ON DUPLICATE KEY UPDATE `file_id` = ? $sUpdateATime", $this->_aObject['object'], $iFileId, $mixedHandler, $iFileId);
+        return $this->res($sQuery);
+    }
+
+    public function getFileIdByHandler ($mixedHandler)
+    {
+        $sQuery = $this->prepare("SELECT `file_id` FROM {$this->_sTableFiles} WHERE `transcoder_object` = ? AND `handler` = ?", $this->_aObject['object'], $mixedHandler);
+        return $this->getOne($sQuery);
+    }
+
+    public function updateAccessTime($mixedHandler)
+    {
+        $iTime = time();
+        $sQuery = $this->prepare("UPDATE {$this->_sTableFiles} SET `atime` = ? WHERE `transcoder_object` = ? AND `handler` = ?", $iTime, $this->_aObject['object'], $mixedHandler);
+        return $this->res($sQuery);
+    }
+
+    public function deleteFileTraces($iFileId)
+    {
+        $sQuery = $this->prepare("DELETE FROM {$this->_sTableFiles} WHERE `transcoder_object` = ? AND `file_id` = ?", $this->_aObject['object'], $iFileId);
+        return $this->res($sQuery);
+    }
+
+    public function getFilesForPruning ()
+    {
+        if (!$this->_aObject['atime_tracking'] || !$this->_aObject['atime_pruning'])
+            continue;
+
+        $sQuery = $this->prepare("SELECT * FROM {$this->_sTableFiles} WHERE `transcoder_object` = ? AND `atime` != 0 AND `atime` < ?", $this->_aObject['object'], time() - $this->_aObject['atime_pruning']);
+        return $this->getAll($sQuery);
+    }
+
+    public function registerHandlers ()
+    {
+        if (!$this->registerHandler ('getAlertHandlerNameLocal', 'alert_response_file_delete_local', $this->_aObject['object'], $this->_aObject['storage_object']))
+            return false;
+
+        // add handler for original storage engine
+        if ('Storage' == $this->_aObject['source_type']) // if original storage is "Storage", not "Folder"
+            if (!$this->registerHandler ('getAlertHandlerNameOrig', 'alert_response_file_delete_orig', $this->_aObject['object'], $this->_aObject['source_params']['object']))
+                return false;
+
+        return true;
+    }
+
+    public function unregisterHandlers ()
+    {
+        if (!$this->unregisterHandler('getAlertHandlerNameLocal', $this->_aObject['storage_object']))
+            return false;
+
+        // remove handler for original storage engine
+        if ('Storage' == $this->_aObject['source_type']) // if original storage is "Storage", not "Folder"
+            if (!$this->unregisterHandler('getAlertHandlerNameOrig', $this->_aObject['source_params']['object']))
+                return false;
+
+        return true;
+    }
+
+    static public function queuePruning ()
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $sQuery = $oDb->prepare("DELETE FROM `" . self::TABLE_QUEUE . "` WHERE `status` = ? AND `changed` < ? - ?", BX_DOL_QUEUE_PROCESSING, time(), BX_DOL_QUEUE_PRUNE_PROCESSING);
+        $oDb->query($sQuery);
+
+        $sQuery = $oDb->prepare("DELETE FROM `" . self::TABLE_QUEUE . "` WHERE `status` = ? AND `changed` < ? - ?", BX_DOL_QUEUE_FAILED, time(), BX_DOL_QUEUE_PRUNE_FAILED);
+        return $oDb->query($sQuery);
+    }
+
+    static public function getNextInQueue ($sServer, $iLimit = 5)
+    {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT COUNT(*) FROM `" . self::TABLE_QUEUE . "` WHERE `server` = ? AND `status` = 'processing'", $sServer);
+        if ($oDb->getOne($sQuery))
+            return array();
+
+        $sQuery = $oDb->prepare("SELECT * FROM `" . self::TABLE_QUEUE . "` WHERE `status` = 'pending' ORDER BY `added` ASC LIMIT ?", $iLimit);
+        return $oDb->getAll($sQuery);
+    }
+
+    public function getQueueTable ()
+    {
+        return $this->_sTableQueue;
+    }
+
+    public function deleteFromQueue ($mixedId)
+    {
+        if (!$this->_sTableQueue)
+            return false;
+
+        $sQuery = $this->prepare("DELETE FROM `" . $this->_sTableQueue . "` WHERE `transcoder_object` = ? AND `file_id_source` = ?", $this->_aObject['object'], $mixedId);
+        return $this->query($sQuery);
+    }
+
+    public function getFromQueue ($mixedId)
+    {
+        if (!$this->_sTableQueue)
+            return false;
+
+        $sQuery = $this->prepare("SELECT * FROM `" . $this->_sTableQueue . "` WHERE `transcoder_object` = ? AND `file_id_source` = ?", $this->_aObject['object'], $mixedId);
+        return $this->getRow($sQuery);
+    }
+
+    public function addToQueue ($mixedId, $sFileUrl, $iProfileId)
+    {
+        if (!$this->_sTableQueue)
+            return false;
+        $sQuery = $this->prepare("INSERT INTO `" . $this->_sTableQueue . "` SET `transcoder_object` = ?, `profile_id` = ?, `file_url_source` = ?, `file_id_source` = ?, `status` = ?, `added` = ?, changed = ?", $this->_aObject['object'], $iProfileId, $sFileUrl, $mixedId, BX_DOL_QUEUE_PENDING, time(), time());
+        return $this->query($sQuery);
+    }
+
+    public function updateQueueStatus ($mixedId, $sStatus, $sLog = '', $sServer = '')
+    {
+        if (!$this->_sTableQueue)
+            return false;
+
+        $sQueryVals = $this->prepare("`status` = ?, changed = ? WHERE `transcoder_object` = ? AND `file_id_source` = ?", $sStatus, time(), $this->_aObject['object'], $mixedId);
+        $sQueryServer = $sServer ? $this->prepare(" `server` = ?, ", $sServer) : '';
+        $sQueryLog = $sLog ? $this->prepare(" `log` = ?, ", $sLog) : '';
+        $sQueryAll = "UPDATE `" . $this->_sTableQueue . "` SET " . $sQueryServer . $sQueryLog . $sQueryVals;
+        return $this->query($sQueryAll);
+    }
+
+    protected function registerHandler ($sHandlerNameFunc, $sServiceFunc, $sObject, $sUnit)
+    {
+        $sHandlerName = $this->$sHandlerNameFunc ();
+        $iHandlerId = $this->getAlertHandlerId ($sHandlerName);
+        if ($iHandlerId) // if handler already exists, do nothing
+            return true;
+
+        $sServiceCall = serialize(array(
+            'module' => 'system',
+            'method' => $sServiceFunc,
+            'params' => array($sObject),
+            'class' => 'TemplImageServices'
+        ));
+        $sQuery = $this->prepare("INSERT INTO `sys_alerts_handlers` SET `name` = ?, `service_call` = ?", $sHandlerName, $sServiceCall);
+        if (!$this->query($sQuery))
+            return false;
+        $iHandlerId = $this->lastId();
+        $sQuery = $this->prepare("INSERT INTO `sys_alerts` SET `unit` = ?, `action` = ?, `handler_id` = ?", $sUnit, 'file_deleted', $iHandlerId);
+        if (!$this->query($sQuery)) {
+            $this->unregisterHandlers ();
+            return false;
+        }
+
+        return true;
+    }
+
+    protected function unregisterHandler ($sHandlerNameFunc, $sUnit)
+    {
+        $sHandlerName = $this->$sHandlerNameFunc ();
+        $iHandlerId = $this->getAlertHandlerId ($sHandlerName);
+        if (!$iHandlerId) // if handler is alrady missing, do nothing
+            return true;
+
+        $sQuery = $this->prepare("DELETE FROM `sys_alerts` WHERE `unit` = ? AND `action` = ? AND `handler_id` = ?", $sUnit, 'file_deleted', $iHandlerId);
+        if (!$this->query($sQuery))
+            return false;
+
+        $sQuery = $this->prepare("DELETE FROM `sys_alerts_handlers` WHERE `id` = ?", $iHandlerId);
+        return $this->query($sQuery);
+    }
+
+    protected function getAlertHandlerNameLocal ()
+    {
+        return $this->_sHandlerPrefix . 'local_file_delete_' . $this->_aObject['object'];
+    }
+
+    protected function getAlertHandlerNameOrig ()
+    {
+        return $this->_sHandlerPrefix . 'orig_file_delete_' . $this->_aObject['object'];
+    }
+
+    protected function getAlertHandlerId ($sHandlerName)
+    {
+        $sQuery = $this->prepare("SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = ?", $sHandlerName);
+        return $this->getOne($sQuery);
+    }
+}
+
+/** @} */

+ 273 - 0
inc/classes/BxDolTranscoderVideo.php

@@ -0,0 +1,273 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolTranscoder');
+bx_import('BxDolTranscoderVideoQuery');
+
+/**
+ * @page objects
+ * @section transcoder_videos Videos Transcoder
+ * @ref BxDolTranscoderVideo
+ */
+
+/**
+ * This class transcodes videos and generates video thumbnails.
+ *
+ * To generate video which plays in all moders browsers along with video poster, 
+ * you need to create 3 different video transcoding objects which will generate .mp4, .webm videos and video poster.
+ * Video is convering upon first access, so it is probably better to force video conversion by calling @see BxDolTranscoderVideo::getFileUrl just after video uploading.
+ * Video for conversion is queued and when cron is run video conversion is performed.
+ * While video is pending for conversion or in the process @see BxDolTranscoderVideo::getFileUrl methods returns empty string for video and predefined image for video poster.
+ *
+ * Transcoder object and other params are the same as in @see BxDolTranscoderImage, but it is highly recommended to disable 'atime_pruning' and 'atime_tracking', or set it to fairly big value, since video transcoding is not performed on the fly and takes some time. 
+ *
+ * Available filters:
+ * - Mp4 - this filter convert video into .mp4 format along with resizing, the parameters are the following:
+ *     - h - height of resulted video (360 by default), for video it is highly recommended to specify only height parameter (no width parameter, )
+ *     - video_bitrate - video bitrate (512k by default)
+ *     - audio_bitrate - video bitrate (128k by default)
+ *     - ffmpeg_options - additional command line options for ffmepeg, as key => value array (empty by default)
+ * - Webm - this filter convert video into .webm format along with resizing, the parameters are the same as for Mp4 filter
+ * - Poster - this filter generates video thumbnail, it tries to get poster at 0, 3 and 5 seconds from the beginning, it gets first not fully blacke/white thumb
+ *
+ *
+ * Example of usage:
+ * @code
+ * bx_import('BxDolTranscoder');
+ *
+ * // transcoder objects which generate .mp4, .webm videos and image poster
+ * $oTranscoderMp4 = BxDolTranscoder::getObjectInstance('bx_video_mp4'); 
+ * $oTranscoderWebm = BxDolTranscoder::getObjectInstance('bx_video_webm');
+ * $oTranscoderPoster = BxDolTranscoder::getObjectInstance('bx_video_poster');
+ *
+ * // make sure to call it only once (for example: during module installation), before the first usage, no need to call it every time
+ * $oTranscoderMp4->registerHandlers(); 
+ * $oTranscoderWebm->registerHandlers(); 
+ * $oTranscoderPoster->registerHandlers(); 
+ *
+ * // get URLs of transcoded videos and video thumbnail, 33 is ID of original video file stored in specified storage object
+ * $sUrlMp4 = $oTranscoder->getFileUrl(33);
+ * $sUrlWebM = $oTranscoder->getFileUrl(33);
+ * $sUrlPoster = $oTranscoder->getFileUrl(33);
+ *
+ * echo 'My cat:
+ *     <video poster="' . $sUrlPoster . '">
+ *         <source type="video/webm; codecs="vp8, vorbis" src="' . $sUrlWebM . '" />
+ *         <source type="video/mp4" src="' . $sUrlMp4 . '" />
+ *     </video>'; 
+ * @endcode
+ *
+ */
+class BxDolTranscoderVideo extends BxDolTranscoder implements iBxDolFactoryObject
+{
+    protected function __construct($aObject, $oStorage)
+    {
+        parent::__construct($aObject, $oStorage);
+        $this->_sQueueStorage = ''; // TODO:
+        $this->_oDb = new BxDolTranscoderVideoQuery($aObject);
+        $this->_sQueueTable = $this->_oDb->getQueueTable();
+    }
+
+    public function getDevicePixelRatioHandlerSuffix ()
+    {
+        return '';
+    }
+
+    /**
+     * If video isn't processed yet then empty string is returned for video, or predefined image is returned for video poster
+     */
+    public function getFileUrlNotReady($mixedHandler)
+    {
+        return $this->_isPosterFilter() ? BxDolTemplate::getInstance()->getImageUrl('video-na.png') : '';
+    }
+
+    /**
+     * Convert video to .jpg format - video poster
+     */
+    protected function applyFilter_Poster ($sFile, $aParams)
+    {
+	    bx_import('BxDolImageResize');
+    	$oImage = BxDolImageResize::getInstance();
+
+        $sFileOut = $this->getTmpFilename('.jpg');
+        $bRet = false;
+        $aSeconds = array (0, 3, 5, 0);
+        $sSize = $this->_getOptionSizeForVideo ($sFile, $aParams);
+        foreach ($aSeconds as $iSecond) {
+            $bRet = $this->_convertVideo($sFile, $sFileOut, '.jpg', $aParams, array (
+                's' => $sSize,
+                'ss' => $iSecond,
+                'vframes' => 1,
+                'f' => 'image2',
+                'an' => ' ',
+            ));
+            if (!$bRet)
+                continue;
+
+    		$aRgb = $oImage->getAverageColor($sFileOut);
+		    $fRgb = ($aRgb['r'] + $aRgb['g'] + $aRgb['b']) / 3;
+	    	if ($fRgb > 32 && $fRgb < 224)
+    			break;
+        }
+
+        if (!$bRet) {
+            unlink($sFileOut);
+            return false;
+        }
+
+        return rename($sFileOut, $sFile);
+    }
+
+    /**
+     * Convert video to .mp4 format
+     */
+    protected function applyFilter_Mp4 ($sFile, $aParams)
+    {
+        return $this->_convertVideo($sFile, $sFile, '.mp4', $aParams, array (
+            'vcodec' => 'libx264',
+            's' => $this->_getOptionSizeForVideo ($sFile, $aParams),
+            'b:v' => isset($aParams['video_bitrate']) ? $aParams['video_bitrate'] . 'k' : '512k',
+            'movflags' => '+faststart',
+            'acodec' => 'libvo_aacenc',
+            'ar' => '44100',
+            'b:a' => isset($aParams['audio_bitrate']) ? $aParams['audio_bitrate'] . 'k' : '128k',
+        ));
+    }
+
+    /**
+     * Convert video to .webm format
+     */
+    protected function applyFilter_Webm ($sFile, $aParams)
+    {
+        return $this->_convertVideo($sFile, $sFile, '.webm', $aParams, array (
+            's' => $this->_getOptionSizeForVideo ($sFile, $aParams),
+            'b:v' => isset($aParams['video_bitrate']) ? $aParams['video_bitrate'] . 'k' : '512k',
+            'acodec' => 'libvorbis',
+            'ar' => '44100',
+            'b:a' => isset($aParams['audio_bitrate']) ? $aParams['audio_bitrate'] . 'k' : '128k',
+        ));
+    }
+
+    /**
+     * Convert video using ffmpeg binary. Video conversion otput is written to the 'log' variable.
+     * @param $sFile input file for conversion
+     * @param $sFileOut output file for conversion, can be the same as input file
+     * @param $sExt output file extension, in the format '.ext'
+     * @param $aParams filter params
+     * @param $aOptions ffmpeg options as array
+     * @return false on error, or true on success
+     */
+    protected function _convertVideo ($sFile, $sFileOut, $sExt, $aParams, $aOptions)
+    {
+        if (!empty($aParams['ffmpeg_options']) && is_array($aParams['ffmpeg_options']))
+            $aOptions = array_merge($aOptions, $aParams['ffmpeg_options']);
+
+        $sOptions = '';
+        foreach ($aOptions as $k => $v)
+            if ($v)
+                $sOptions .= "-{$k} {$v} ";
+
+        $bRename = false;
+        if ($sFileOut == $sFile) { // if output file is the same as input - generate new tmp file
+            $sFileOut = $this->getTmpFilename($sExt);
+            $bRename = true;
+        }
+        elseif (file_exists($sFileOut)) {
+            @unlink($sFileOut);
+        }
+
+        $sCommand = BX_SYSTEM_FFMPEG . ' -y -i "' . escapeshellcmd($sFile) . '" ' . $sOptions . ' ' . $sFileOut . ' 2>&1';
+        $sOutput = `$sCommand`;
+        $this->addToLog("\n---\n{$sCommand}\n{$sOutput}\n");
+
+        if (!file_exists($sFileOut) || 0 == filesize($sFileOut))
+            return false;
+
+        if ($bRename && !rename($sFileOut, $sFile)) // rename tmp file, if tmp file was generated
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Get video size. 
+     * It generated image from the video and returns size of the image.
+     * @param $sFile path to the video file
+     * @return array where 'w' key is width and 'h' key is height
+     */
+    protected function _getVideoSize ($sFile)
+    {
+        $sFileOut = $this->getTmpFilename('.jpg');
+
+        $bRet = $this->_convertVideo($sFile, $sFileOut, '.jpg', array(), array (
+            'ss' => 0,
+            'vframes' => 1,
+            'f' => 'image2',
+            'an' => ' ',
+        ));
+
+        if (!$bRet)        
+            return false;
+
+	    bx_import('BxDolImageResize');
+    	$oImage = BxDolImageResize::getInstance();
+        $aSize = $oImage->getImageSize($sFileOut);
+
+        @unlink($sFileOut);
+
+        return $aSize;
+    }
+
+    /**
+     * Get output video size as 'WxH' string.
+     * It check filter params for 'w' and 'h' options and original video ratio and return the resulted video size.
+     * - If only height specified in params (desired behavior) or no width or height is specified (default height is 360), 
+     * then width is automatically calculated using original video size ratio.
+     * - If only width specified in params, then height is automatically calculated using original video size ratio.
+     * - If width and height specified in params then these values are returned, but the resulted video size can be unproportionate.
+     * @param $sFile original video file path (to check original video size ratio)
+     * @param $aParams filter params
+     * @return WxH string
+     */
+    protected function _getOptionSizeForVideo ($sFile, $aParams) 
+    {
+        if (isset($aParams['w']) && isset($aParams['h']))
+            return $aParams['w'] . 'x' . $aParams['h'];
+
+        if (!isset($aParams['w']) && !isset($aParams['h']))
+            $aParams['h'] = '360';
+
+        $fRatio = 16/9;
+        $aSize = $this->_getVideoSize($sFile);
+        if ($aSize)
+            $fRatio = $aSize['w'] / $aSize['h'];
+
+        if (isset($aParams['h']))
+            return round($fRatio * $aParams['h'] / 2) * 2 . 'x' . $aParams['h'];
+
+        if (isset($aParams['w']))
+            return  $aParams['w'] . 'x' . round($aParams['w'] / $fRatio / 2) * 2;
+
+        return '640x360'; // should never happen
+    }
+
+    /**
+     * Checks if 'Poster' filter is enabled for curent transcoder
+     */
+    protected function _isPosterFilter ()
+    {
+        $this->initFilters ();
+        foreach ($this->_aObject['filters'] as $aParams)
+            if ('Poster' == $aParams['filter'])
+                return true;
+        return false;
+    }
+}
+
+/** @} */

+ 26 - 0
inc/classes/BxDolTranscoderVideoQuery.php

@@ -0,0 +1,26 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolTranscoderQuery');
+
+/**
+ * @see BxDolTranscoderVideo
+ */
+class BxDolTranscoderVideoQuery extends BxDolTranscoderQuery
+{
+    public function __construct($aObject)
+    {
+        parent::__construct($aObject, true);
+        $this->_sTableFiles = '`sys_transcoder_videos_files`';
+        $this->_sHandlerPrefix = 'sys_video_transcoder_';
+    }
+}
+
+/** @} */
+

+ 77 - 0
inc/utils.inc.php

@@ -1198,4 +1198,81 @@ function bx_show_service_unavailable_error_and_exit ($sMsg = false, $iRetryAfter
     exit;
     exit;
 }
 }
 
 
+/**
+ * The function is sumilar to php readfile, but it send all required headers and can send file by chunks and suports file seek
+ * @param $sPath path to file to output to the browser
+ * @param $sFilename filename without path, ig file is saved from browser, then this name is used, not used(empty) by default
+ * @param $sMimeType file mime type, by default 'application/octet-stream'
+ * @param $iCacheAge file cache age, by default 0
+ * @param $sCachePrivacy cache privacy 'public' (default value) or 'private'
+ * @return true on success or false on error
+ */
+function bx_smart_readfile($sPath, $sFilename = '', $sMimeType = 'application/octet-stream', $iCacheAge = 0, $sCachePrivacy = 'public')
+{
+    if (!file_exists($sPath))
+        return  false;
+
+    $fp = @fopen($sPath, 'rb');
+
+    $size   = filesize($sPath);
+    $length = $size;
+    $start  = 0;
+    $end    = $size - 1;
+
+    header('Content-type: ' . $sMimeType);
+    header('Cache-Control: ' . $sCachePrivacy . ', must-revalidate, max-age=' . $iCacheAge);
+    header("Accept-Ranges: 0-$length");
+    if ($sFilename)
+        header('Content-Disposition: inline; filename=' . $sFilename);
+
+    if (isset($_SERVER['HTTP_RANGE'])) {
+
+        $c_start = $start;
+        $c_end   = $end;
+
+        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+        if (strpos($range, ',') !== false) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        if ($range == '-') {
+            $c_start = $size - substr($range, 1);
+        }else{
+            $range  = explode('-', $range);
+            $c_start = $range[0];
+            $c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+        }
+        $c_end = ($c_end > $end) ? $end : $c_end;
+        if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
+            header('HTTP/1.1 416 Requested Range Not Satisfiable');
+            header("Content-Range: bytes $start-$end/$size");
+            return false;
+        }
+        $start  = $c_start;
+        $end    = $c_end;
+        $length = $end - $start + 1;
+        fseek($fp, $start);
+        header('HTTP/1.1 206 Partial Content');
+    }
+    header("Content-Range: bytes $start-$end/$size");
+    header("Content-Length: ".$length);
+
+
+    $buffer = 1024 * 8;
+    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
+
+        if ($p + $buffer > $end) {
+            $buffer = $end - $p + 1;
+        }
+        set_time_limit(0);
+        echo fread($fp, $buffer);
+        flush();
+    }
+
+    fclose($fp);
+
+    return true;
+}
+
 /** @} */
 /** @} */

+ 1 - 0
install/classes/BxDolInstallSiteConfig.php

@@ -458,6 +458,7 @@ EOF;
         $aMarkers['version'] = BX_DOL_VER;
         $aMarkers['version'] = BX_DOL_VER;
         $aMarkers['time'] = time();
         $aMarkers['time'] = time();
         $aMarkers['secret'] = genRndPwd(11);
         $aMarkers['secret'] = genRndPwd(11);
+        $aMarkers['ffmpeg_path'] = $aMarkers['root_dir'] . 'plugins/ffmpeg/ffmpeg.exe';
 
 
         return $aMarkers;
         return $aMarkers;
     }
     }

+ 1 - 0
install/patterns/header.inc.php

@@ -13,6 +13,7 @@ define('BX_DATABASE_USER', '%DB_USER%'); ///< db user
 define('BX_DATABASE_PASS', '%DB_PASSWORD%'); ///< db password
 define('BX_DATABASE_PASS', '%DB_PASSWORD%'); ///< db password
 define('BX_DATABASE_NAME', '%DB_NAME%'); ///< db name
 define('BX_DATABASE_NAME', '%DB_NAME%'); ///< db name
 
 
+define('BX_SYSTEM_FFMPEG', '%FFMPEG_PATH%'); ///< path to ffmpeg binary
 define('BX_SYSTEM_CONVERT', '%CONVERT_PATH%'); ///< path to ImageMagick convert binary
 define('BX_SYSTEM_CONVERT', '%CONVERT_PATH%'); ///< path to ImageMagick convert binary
 define('BX_SYSTEM_COMPOSITE', '%COMPOSITE_PATH%'); ///< path to ImageMagick composite binary
 define('BX_SYSTEM_COMPOSITE', '%COMPOSITE_PATH%'); ///< path to ImageMagick composite binary
 define('BX_DOL_SECRET', '%SECRET%'); ///< secret word
 define('BX_DOL_SECRET', '%SECRET%'); ///< secret word

+ 42 - 11
install/sql/system.sql

@@ -5,7 +5,7 @@
 -- --------------------------------------------------------
 -- --------------------------------------------------------
 
 
 SET NAMES 'utf8';
 SET NAMES 'utf8';
-DROP TABLE IF EXISTS `sys_keys`, `sys_objects_editor`, `sys_objects_captcha`, `sys_objects_social_sharing`, `sys_objects_cmts`, `sys_cmts_images`, `sys_cmts_images_preview`, `sys_cmts_images2entries`, `sys_email_templates`, `sys_options`, sys_options_types, `sys_options_categories`, `sys_localization_categories`, `sys_localization_keys`, `sys_localization_languages`, `sys_localization_strings`, `sys_acl_actions`, `sys_acl_actions_track`, `sys_acl_matrix`, `sys_acl_levels`, `sys_sessions`, `sys_acl_levels_members`, `sys_objects_rss`, `sys_objects_search`, `sys_stat_site`, `sys_alerts`, `sys_alerts_handlers`, `sys_injections`, `sys_injections_admin`, `sys_modules`, `sys_modules_file_tracks`, `sys_modules_relations`, `sys_permalinks`, `sys_objects_privacy`, `sys_privacy_defaults`, `sys_privacy_groups`, `sys_objects_auths`, `sys_objects_vote`, `sys_objects_view`, `sys_cron_jobs`, `sys_objects_storage`, `sys_objects_uploader`, `sys_storage_user_quotas`, `sys_storage_tokens`, `sys_storage_ghosts`, `sys_storage_deletions`, `sys_storage_mime_types`, `sys_objects_transcoder_images`, `sys_transcoder_images_files`, `sys_transcoder_images_filters`, `sys_accounts`, `sys_profiles`, `sys_objects_form`, `sys_form_displays`, `sys_form_inputs`, `sys_form_display_inputs`, `sys_form_pre_lists`, `sys_form_pre_values`, `sys_menu_templates`, `sys_objects_menu`, `sys_menu_sets`, `sys_menu_items`, `sys_objects_grid`, `sys_grid_fields`, `sys_grid_actions`, `sys_objects_connection`, `sys_profiles_conn_subscriptions`, `sys_profiles_conn_friends`, `sys_objects_page`, `sys_pages_layouts`, `sys_pages_design_boxes`, `sys_pages_blocks`, `sys_objects_metatags`, `sys_objects_live_updates`, `sys_images`, `sys_std_pages`, `sys_std_widgets`, `sys_std_pages_widgets`;
+DROP TABLE IF EXISTS `sys_keys`, `sys_objects_editor`, `sys_objects_captcha`, `sys_objects_social_sharing`, `sys_objects_cmts`, `sys_cmts_images`, `sys_cmts_images_preview`, `sys_cmts_images2entries`, `sys_email_templates`, `sys_options`, sys_options_types, `sys_options_categories`, `sys_localization_categories`, `sys_localization_keys`, `sys_localization_languages`, `sys_localization_strings`, `sys_acl_actions`, `sys_acl_actions_track`, `sys_acl_matrix`, `sys_acl_levels`, `sys_sessions`, `sys_acl_levels_members`, `sys_objects_rss`, `sys_objects_search`, `sys_stat_site`, `sys_alerts`, `sys_alerts_handlers`, `sys_injections`, `sys_injections_admin`, `sys_modules`, `sys_modules_file_tracks`, `sys_modules_relations`, `sys_permalinks`, `sys_objects_privacy`, `sys_privacy_defaults`, `sys_privacy_groups`, `sys_objects_auths`, `sys_objects_vote`, `sys_objects_view`, `sys_cron_jobs`, `sys_objects_storage`, `sys_objects_uploader`, `sys_storage_user_quotas`, `sys_storage_tokens`, `sys_storage_ghosts`, `sys_storage_deletions`, `sys_storage_mime_types`, `sys_objects_transcoder`, `sys_transcoder_images_files`, `sys_transcoder_videos_files`, `sys_transcoder_filters`, `sys_transcoder_queue`, `sys_accounts`, `sys_profiles`, `sys_objects_form`, `sys_form_displays`, `sys_form_inputs`, `sys_form_display_inputs`, `sys_form_pre_lists`, `sys_form_pre_values`, `sys_menu_templates`, `sys_objects_menu`, `sys_menu_sets`, `sys_menu_items`, `sys_objects_grid`, `sys_grid_fields`, `sys_grid_actions`, `sys_objects_connection`, `sys_profiles_conn_subscriptions`, `sys_profiles_conn_friends`, `sys_objects_page`, `sys_pages_layouts`, `sys_pages_design_boxes`, `sys_pages_blocks`, `sys_objects_metatags`, `sys_objects_live_updates`, `sys_images`, `sys_std_pages`, `sys_std_widgets`, `sys_std_pages_widgets`;
 ALTER DATABASE DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci';
 ALTER DATABASE DEFAULT CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci';
 
 
 
 
@@ -1009,8 +1009,9 @@ CREATE TABLE `sys_cron_jobs` (
 INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
 INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
 ('pruning', '0 0 * * *', 'BxDolCronPruning', 'inc/classes/BxDolCronPruning.php', ''),
 ('pruning', '0 0 * * *', 'BxDolCronPruning', 'inc/classes/BxDolCronPruning.php', ''),
 ('sys_upgrade', '0 3 * * *', 'BxDolCronUpgradeCheck', 'inc/classes/BxDolCronUpgradeCheck.php', ''),
 ('sys_upgrade', '0 3 * * *', 'BxDolCronUpgradeCheck', 'inc/classes/BxDolCronUpgradeCheck.php', ''),
-('sys_upgrade_modules', '0 6 * * *', 'BxDolCronUpgradeModulesCheck', 'inc/classes/BxDolCronUpgradeModulesCheck.php', ''),
-('sys_storage', '* * * * *', 'BxDolCronStorage', 'inc/classes/BxDolCronStorage.php', '');
+('sys_upgrade_modules', '30 2 * * *', 'BxDolCronUpgradeModulesCheck', 'inc/classes/BxDolCronUpgradeModulesCheck.php', ''),
+('sys_storage', '* * * * *', 'BxDolCronStorage', 'inc/classes/BxDolCronStorage.php', ''),
+('sys_transcoder', '* * * * *', 'BxDolCronTranscoder', 'inc/classes/BxDolCronTranscoder.php', '');
 
 
 -- --------------------------------------------------------
 -- --------------------------------------------------------
 
 
@@ -2961,7 +2962,7 @@ CREATE TABLE IF NOT EXISTS `sys_profiles_conn_friends` (
 -- --------------------------------------------------------
 -- --------------------------------------------------------
 
 
 
 
-CREATE TABLE IF NOT EXISTS `sys_objects_transcoder_images` (
+CREATE TABLE IF NOT EXISTS `sys_objects_transcoder` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `object` varchar(64) NOT NULL,
   `object` varchar(64) NOT NULL,
   `storage_object` varchar(64) NOT NULL,
   `storage_object` varchar(64) NOT NULL,
@@ -2971,15 +2972,17 @@ CREATE TABLE IF NOT EXISTS `sys_objects_transcoder_images` (
   `atime_tracking` int(11) NOT NULL,
   `atime_tracking` int(11) NOT NULL,
   `atime_pruning` int(11) NOT NULL,
   `atime_pruning` int(11) NOT NULL,
   `ts` int(11) NOT NULL DEFAULT '0',
   `ts` int(11) NOT NULL DEFAULT '0',
+  `override_class_name` varchar(255) NOT NULL,
+  `override_class_file` varchar(255) NOT NULL,
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
   UNIQUE KEY `object` (`object`)
   UNIQUE KEY `object` (`object`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
-('sys_icon_apple', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0'),
-('sys_icon_facebook', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0'),
-('sys_icon_favicon', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0'),
-('sys_cmts_images_preview', 'sys_cmts_images_preview', 'Storage', 'a:1:{s:6:"object";s:15:"sys_cmts_images";}', 'no', '1', '2592000', '0');
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`, `override_class_name`, `override_class_file`) VALUES 
+('sys_icon_apple', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0', '', ''),
+('sys_icon_facebook', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0', '', ''),
+('sys_icon_favicon', 'sys_images', 'Storage', 'a:2:{s:6:"object";s:10:"sys_images";s:14:"disable_retina";b:1;}', 'no', '0', '0', '0', '', ''),
+('sys_cmts_images_preview', 'sys_cmts_images_preview', 'Storage', 'a:1:{s:6:"object";s:15:"sys_cmts_images";}', 'no', '1', '2592000', '0', '', '');
 
 
 
 
 CREATE TABLE IF NOT EXISTS `sys_transcoder_images_files` (
 CREATE TABLE IF NOT EXISTS `sys_transcoder_images_files` (
@@ -2993,8 +2996,18 @@ CREATE TABLE IF NOT EXISTS `sys_transcoder_images_files` (
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 
 
 
+CREATE TABLE IF NOT EXISTS `sys_transcoder_videos_files` (
+  `transcoder_object` varchar(64) NOT NULL,
+  `file_id` int(11) NOT NULL,
+  `handler` varchar(255) NOT NULL,
+  `atime` int(11) NOT NULL,
+  UNIQUE KEY `transcoder_object` (`transcoder_object`,`handler`),
+  KEY `atime` (`atime`),
+  KEY `file_id` (`file_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 
-CREATE TABLE IF NOT EXISTS `sys_transcoder_images_filters` (
+
+CREATE TABLE IF NOT EXISTS `sys_transcoder_filters` (
   `transcoder_object` varchar(64) NOT NULL,
   `transcoder_object` varchar(64) NOT NULL,
   `filter` varchar(32) NOT NULL,
   `filter` varchar(32) NOT NULL,
   `filter_params` text NOT NULL,
   `filter_params` text NOT NULL,
@@ -3002,13 +3015,31 @@ CREATE TABLE IF NOT EXISTS `sys_transcoder_images_filters` (
   KEY `transcoder_object` (`transcoder_object`)
   KEY `transcoder_object` (`transcoder_object`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
 ('sys_icon_apple', 'Resize', 'a:4:{s:1:"w";s:3:"152";s:1:"h";s:3:"152";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_icon_apple', 'Resize', 'a:4:{s:1:"w";s:3:"152";s:1:"h";s:3:"152";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_icon_facebook', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_icon_facebook', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_icon_favicon', 'Resize', 'a:4:{s:1:"w";s:2:"16";s:1:"h";s:2:"16";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_icon_favicon', 'Resize', 'a:4:{s:1:"w";s:2:"16";s:1:"h";s:2:"16";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"png";}', '0'),
 ('sys_cmts_images_preview', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 ('sys_cmts_images_preview', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 
 
 
 
+CREATE TABLE IF NOT EXISTS `sys_transcoder_queue` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `transcoder_object` varchar(64) NOT NULL,
+  `profile_id` int(10) unsigned NOT NULL,
+  `file_url_source` varchar(255) NOT NULL,
+  `file_id_source` varchar(255) NOT NULL,
+  `file_url_result` varchar(255) NOT NULL,
+  `file_id_result` int(11) NOT NULL,
+  `server` varchar(255) NOT NULL,
+  `status` enum('pending','processing','complete','failed') NOT NULL,
+  `added` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,
+  `log` text NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `transcoder_object` (`transcoder_object`,`file_id_source`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+
 -- --------------------------------------------------------
 -- --------------------------------------------------------
 
 
 
 

+ 2 - 2
modules/boonex/convos/install/sql/install.sql

@@ -110,10 +110,10 @@ INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `
 ('bx_convos_files', 'Local', '', 360, 2592000, 3, 'bx_convos_files', 'deny-allow', '', 'action,apk,app,bat,bin,cmd,com,command,cpl,csh,exe,gadget,inf,ins,inx,ipa,isu,job,jse,ksh,lnk,msc,msi,msp,mst,osx,out,paf,pif,prg,ps1,reg,rgs,run,sct,shb,shs,u3p,vb,vbe,vbs,vbscript,workflow,ws,wsf', 0, 0, 0, 0, 0, 0),
 ('bx_convos_files', 'Local', '', 360, 2592000, 3, 'bx_convos_files', 'deny-allow', '', 'action,apk,app,bat,bin,cmd,com,command,cpl,csh,exe,gadget,inf,ins,inx,ipa,isu,job,jse,ksh,lnk,msc,msi,msp,mst,osx,out,paf,pif,prg,ps1,reg,rgs,run,sct,shb,shs,u3p,vb,vbe,vbs,vbscript,workflow,ws,wsf', 0, 0, 0, 0, 0, 0),
 ('bx_convos_photos_resized', 'Local', '', 360, 2592000, 3, 'bx_convos_photos_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 ('bx_convos_photos_resized', 'Local', '', 360, 2592000, 3, 'bx_convos_photos_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
 ('bx_convos_preview', 'bx_convos_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_convos_files";}', 'no', '1', '2592000', '0');
 ('bx_convos_preview', 'bx_convos_photos_resized', 'Storage', 'a:1:{s:6:"object";s:15:"bx_convos_files";}', 'no', '1', '2592000', '0');
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
 ('bx_convos_preview', 'Resize', 'a:4:{s:1:"w";s:3:"300";s:1:"h";s:3:"200";s:11:"crop_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 ('bx_convos_preview', 'Resize', 'a:4:{s:1:"w";s:3:"300";s:1:"h";s:3:"200";s:11:"crop_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/convos/install/sql/uninstall.sql

@@ -8,8 +8,8 @@ DROP TABLE IF EXISTS `bx_convos_conversations`, `bx_convos_conv2folder`, `bx_con
 DELETE FROM `sys_objects_storage` WHERE `object` = 'bx_convos_files' OR `object` = 'bx_convos_photos_resized';
 DELETE FROM `sys_objects_storage` WHERE `object` = 'bx_convos_files' OR `object` = 'bx_convos_photos_resized';
 DELETE FROM `sys_storage_tokens` WHERE `object` = 'bx_convos_files' OR `object` = 'bx_convos_photos_resized';
 DELETE FROM `sys_storage_tokens` WHERE `object` = 'bx_convos_files' OR `object` = 'bx_convos_photos_resized';
 
 
-DELETE FROM `sys_objects_transcoder_images` WHERE `object` = 'bx_convos_preview';
-DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` = 'bx_convos_preview';
+DELETE FROM `sys_objects_transcoder` WHERE `object` = 'bx_convos_preview';
+DELETE FROM `sys_transcoder_filters` WHERE `transcoder_object` = 'bx_convos_preview';
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` = 'bx_convos_preview';
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` = 'bx_convos_preview';
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/organizations/install/sql/install.sql

@@ -62,7 +62,7 @@ INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `
 ('bx_organizations_pics', 'Local', '', 360, 2592000, 3, 'bx_organizations_pics', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_organizations_pics', 'Local', '', 360, 2592000, 3, 'bx_organizations_pics', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_organizations_pics_resized', 'Local', '', 360, 2592000, 3, 'bx_organizations_pics_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 ('bx_organizations_pics_resized', 'Local', '', 360, 2592000, 3, 'bx_organizations_pics_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
 ('bx_organizations_icon', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_icon', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_thumb', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_thumb', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_avatar', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_avatar', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
@@ -70,7 +70,7 @@ INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source
 ('bx_organizations_cover', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_cover', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0'),
 ('bx_organizations_cover_thumb', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0');
 ('bx_organizations_cover_thumb', 'bx_organizations_pics_resized', 'Storage', 'a:1:{s:6:"object";s:21:"bx_organizations_pics";}', 'no', '1', '2592000', '0');
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
 ('bx_organizations_icon', 'Resize', 'a:4:{s:1:"w";s:2:"32";s:1:"h";s:2:"32";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_organizations_icon', 'Resize', 'a:4:{s:1:"w";s:2:"32";s:1:"h";s:2:"32";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_organizations_thumb', 'Resize', 'a:4:{s:1:"w";s:2:"48";s:1:"h";s:2:"48";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_organizations_thumb', 'Resize', 'a:4:{s:1:"w";s:2:"48";s:1:"h";s:2:"48";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_organizations_avatar', 'Resize', 'a:4:{s:1:"w";s:2:"96";s:1:"h";s:2:"96";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_organizations_avatar', 'Resize', 'a:4:{s:1:"w";s:2:"96";s:1:"h";s:2:"96";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),

+ 2 - 2
modules/boonex/organizations/install/sql/uninstall.sql

@@ -12,8 +12,8 @@ DELETE FROM sys_profiles WHERE `type` = 'bx_organizations';
 DELETE FROM `sys_objects_storage` WHERE `object` IN('bx_organizations_pics', 'bx_organizations_pics_resized');
 DELETE FROM `sys_objects_storage` WHERE `object` IN('bx_organizations_pics', 'bx_organizations_pics_resized');
 DELETE FROM `sys_storage_tokens` WHERE `object` IN('bx_organizations_pics', 'bx_organizations_pics_resized');
 DELETE FROM `sys_storage_tokens` WHERE `object` IN('bx_organizations_pics', 'bx_organizations_pics_resized');
 
 
-DELETE FROM `sys_objects_transcoder_images` WHERE `object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
-DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
+DELETE FROM `sys_objects_transcoder` WHERE `object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
+DELETE FROM `sys_transcoder_filters` WHERE `transcoder_object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` IN('bx_organizations_icon', 'bx_organizations_thumb', 'bx_organizations_avatar', 'bx_organizations_picture', 'bx_organizations_cover', 'bx_organizations_cover_thumb');
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/persons/install/sql/install.sql

@@ -62,7 +62,7 @@ INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `
 ('bx_persons_pictures', 'Local', '', 360, 2592000, 3, 'bx_persons_pictures', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_persons_pictures', 'Local', '', 360, 2592000, 3, 'bx_persons_pictures', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_persons_pictures_resized', 'Local', '', 360, 2592000, 3, 'bx_persons_pictures_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 ('bx_persons_pictures_resized', 'Local', '', 360, 2592000, 3, 'bx_persons_pictures_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
 ('bx_persons_icon', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_icon', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_thumb', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_thumb', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_avatar', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_avatar', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
@@ -70,7 +70,7 @@ INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source
 ('bx_persons_cover', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_cover', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0'),
 ('bx_persons_cover_thumb', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0');
 ('bx_persons_cover_thumb', 'bx_persons_pictures_resized', 'Storage', 'a:1:{s:6:"object";s:19:"bx_persons_pictures";}', 'no', '1', '2592000', '0');
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
 ('bx_persons_icon', 'Resize', 'a:4:{s:1:"w";s:2:"32";s:1:"h";s:2:"32";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_persons_icon', 'Resize', 'a:4:{s:1:"w";s:2:"32";s:1:"h";s:2:"32";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_persons_thumb', 'Resize', 'a:4:{s:1:"w";s:2:"48";s:1:"h";s:2:"48";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_persons_thumb', 'Resize', 'a:4:{s:1:"w";s:2:"48";s:1:"h";s:2:"48";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_persons_avatar', 'Resize', 'a:4:{s:1:"w";s:2:"96";s:1:"h";s:2:"96";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_persons_avatar', 'Resize', 'a:4:{s:1:"w";s:2:"96";s:1:"h";s:2:"96";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),

+ 2 - 2
modules/boonex/persons/install/sql/uninstall.sql

@@ -12,8 +12,8 @@ DELETE FROM sys_profiles WHERE `type` = 'bx_persons';
 DELETE FROM `sys_objects_storage` WHERE `object` IN('bx_persons_pictures', 'bx_persons_pictures_resized');
 DELETE FROM `sys_objects_storage` WHERE `object` IN('bx_persons_pictures', 'bx_persons_pictures_resized');
 DELETE FROM `sys_storage_tokens` WHERE `object` IN('bx_persons_pictures', 'bx_persons_pictures_resized');
 DELETE FROM `sys_storage_tokens` WHERE `object` IN('bx_persons_pictures', 'bx_persons_pictures_resized');
 
 
-DELETE FROM `sys_objects_transcoder_images` WHERE `object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
-DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
+DELETE FROM `sys_objects_transcoder` WHERE `object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
+DELETE FROM `sys_transcoder_filters` WHERE `transcoder_object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` IN('bx_persons_icon', 'bx_persons_thumb', 'bx_persons_avatar', 'bx_persons_picture', 'bx_persons_cover', 'bx_persons_cover_thumb');
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/posts/install/sql/install.sql

@@ -128,10 +128,10 @@ INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `
 ('bx_posts_files', 'Local', '', 360, 2592000, 3, 'bx_posts_files', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_posts_files', 'Local', '', 360, 2592000, 3, 'bx_posts_files', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_posts_photos_resized', 'Local', '', 360, 2592000, 3, 'bx_posts_photos_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 ('bx_posts_photos_resized', 'Local', '', 360, 2592000, 3, 'bx_posts_photos_resized', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES 
 ('bx_posts_preview', 'bx_posts_photos_resized', 'Storage', 'a:1:{s:6:"object";s:14:"bx_posts_files";}', 'no', '1', '2592000', '0');
 ('bx_posts_preview', 'bx_posts_photos_resized', 'Storage', 'a:1:{s:6:"object";s:14:"bx_posts_files";}', 'no', '1', '2592000', '0');
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES 
 ('bx_posts_preview', 'Resize', 'a:4:{s:1:"w";s:3:"300";s:1:"h";s:3:"200";s:11:"crop_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 ('bx_posts_preview', 'Resize', 'a:4:{s:1:"w";s:3:"300";s:1:"h";s:3:"200";s:11:"crop_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/posts/install/sql/uninstall.sql

@@ -8,8 +8,8 @@ DROP TABLE IF EXISTS `bx_posts_posts`, `bx_posts_files`, `bx_posts_photos_resize
 DELETE FROM `sys_objects_storage` WHERE `object` = 'bx_posts_files' OR `object` = 'bx_posts_photos_resized';
 DELETE FROM `sys_objects_storage` WHERE `object` = 'bx_posts_files' OR `object` = 'bx_posts_photos_resized';
 DELETE FROM `sys_storage_tokens` WHERE `object` = 'bx_posts_files' OR `object` = 'bx_posts_photos_resized';
 DELETE FROM `sys_storage_tokens` WHERE `object` = 'bx_posts_files' OR `object` = 'bx_posts_photos_resized';
 
 
-DELETE FROM `sys_objects_transcoder_images` WHERE `object` = 'bx_posts_preview';
-DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` = 'bx_posts_preview';
+DELETE FROM `sys_objects_transcoder` WHERE `object` = 'bx_posts_preview';
+DELETE FROM `sys_transcoder_filters` WHERE `transcoder_object` = 'bx_posts_preview';
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` = 'bx_posts_preview';
 DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` = 'bx_posts_preview';
 
 
 -- FORMS
 -- FORMS

+ 2 - 2
modules/boonex/timeline/install/sql/install.sql

@@ -166,11 +166,11 @@ INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `
 ('bx_timeline_photos_preview', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos_preview', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_timeline_photos_preview', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos_preview', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
 ('bx_timeline_photos_view', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos_view', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 ('bx_timeline_photos_view', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos_view', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
 
 
-INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES
 ('bx_timeline_photos_preview', 'bx_timeline_photos_preview', 'Storage', 'a:1:{s:6:"object";s:18:"bx_timeline_photos";}', 'no', '1', '2592000', '0'),
 ('bx_timeline_photos_preview', 'bx_timeline_photos_preview', 'Storage', 'a:1:{s:6:"object";s:18:"bx_timeline_photos";}', 'no', '1', '2592000', '0'),
 ('bx_timeline_photos_view', 'bx_timeline_photos_view', 'Storage', 'a:1:{s:6:"object";s:18:"bx_timeline_photos";}', 'no', '1', '2592000', '0');
 ('bx_timeline_photos_view', 'bx_timeline_photos_view', 'Storage', 'a:1:{s:6:"object";s:18:"bx_timeline_photos";}', 'no', '1', '2592000', '0');
 
 
-INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
 ('bx_timeline_photos_preview', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_timeline_photos_preview', 'Resize', 'a:4:{s:1:"w";s:3:"100";s:1:"h";s:3:"100";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0'),
 ('bx_timeline_photos_view', 'Resize', 'a:4:{s:1:"w";s:3:"318";s:1:"h";s:3:"318";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 ('bx_timeline_photos_view', 'Resize', 'a:4:{s:1:"w";s:3:"318";s:1:"h";s:3:"318";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
 
 

+ 2 - 2
modules/boonex/timeline/install/sql/uninstall.sql

@@ -20,8 +20,8 @@ DROP TABLE IF EXISTS `bx_timeline_votes_track`;
 -- STORAGES, TRANSCODERS, UPLOADERS
 -- STORAGES, TRANSCODERS, UPLOADERS
 DELETE FROM `sys_objects_uploader` WHERE `object` = 'bx_timeline_simple';
 DELETE FROM `sys_objects_uploader` WHERE `object` = 'bx_timeline_simple';
 DELETE FROM `sys_objects_storage` WHERE `object` IN ('bx_timeline_photos', 'bx_timeline_photos_preview', 'bx_timeline_photos_view');
 DELETE FROM `sys_objects_storage` WHERE `object` IN ('bx_timeline_photos', 'bx_timeline_photos_preview', 'bx_timeline_photos_view');
-DELETE FROM `sys_objects_transcoder_images` WHERE `object` IN ('bx_timeline_photos_preview', 'bx_timeline_photos_view');
-DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` IN ('bx_timeline_photos_preview', 'bx_timeline_photos_view');
+DELETE FROM `sys_objects_transcoder` WHERE `object` IN ('bx_timeline_photos_preview', 'bx_timeline_photos_view');
+DELETE FROM `sys_transcoder_filters` WHERE `transcoder_object` IN ('bx_timeline_photos_preview', 'bx_timeline_photos_view');
 
 
 
 
 -- Forms All
 -- Forms All

BIN
plugins/ffmpeg/ffmpeg.exe


+ 2 - 2
samples/transcoder.php

@@ -34,10 +34,10 @@
  * INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
  * INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
  * ('resizer', 'Local', '', 360, 84000, 0, 'bx_resizer_files', 'deny-allow', '', 'exe,com,bat,pif,scr', 1000000000, 0, 100, 0, 300000000, 0);
  * ('resizer', 'Local', '', 360, 84000, 0, 'bx_resizer_files', 'deny-allow', '', 'exe,com,bat,pif,scr', 1000000000, 0, 100, 0, 300000000, 0);
  *
  *
- * INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES
+ * INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES
  * ('sample', 'resizer', 'Folder', 'a:1:{s:4:"path";s:12:"samples/img/";}', 'no', 0, 0, 0);
  * ('sample', 'resizer', 'Folder', 'a:1:{s:4:"path";s:12:"samples/img/";}', 'no', 0, 0, 0);
  *
  *
- * INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
+ * INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
  * ('sample', 'Resize', 'a:3:{s:1:"w";i:100;s:1:"h";i:100;s:13:"square_resize";i:0;}', 1),
  * ('sample', 'Resize', 'a:3:{s:1:"w";i:100;s:1:"h";i:100;s:13:"square_resize";i:0;}', 1),
  * ('sample', 'Grayscale', '', 2);
  * ('sample', 'Grayscale', '', 2);
  *
  *

+ 194 - 0
samples/transcoder_video.php

@@ -0,0 +1,194 @@
+<?php
+/**
+ * @package     Dolphin Core
+ * @copyright   Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * @license     CC-BY - http://creativecommons.org/licenses/by/3.0/
+ */
+
+/**
+ * @page samples
+ * @section transcoder_videos Transcoder Videos
+ *
+ * This sample shows how transcoder can be used for video files.
+ *
+ * @code
+
+CREATE TABLE `sample_transcoder_video_orig` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `path` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `file_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `mime_type` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
+  `ext` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
+  `size` int(11) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
+('sample_transcoder_video_orig', 'Local', '', 360, 2592000, 0, 'sample_transcoder_video_orig', 'allow-deny', 'avi,flv,mpg,mpeg,wmv,mp4,m4v,mov,divx,xvid,3gp,webm', '', 0, 0, 0, 0, 0, 0);
+
+CREATE TABLE `sample_transcoder_video` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `path` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `file_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+  `mime_type` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
+  `ext` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
+  `size` int(11) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
+('sample_transcoder_video', 'Local', '', 360, 2592000, 0, 'sample_transcoder_video', 'allow-deny', 'avi,flv,mpg,mpeg,wmv,mp4,m4v,mov,divx,xvid,3gp,webm,jpg', '', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO `sys_objects_transcoder` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`, `override_class_name`, `override_class_file`) VALUES
+('sample_video_poster', 'sample_transcoder_video', 'Storage', 'a:1:{s:6:"object";s:28:"sample_transcoder_video_orig";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('sample_video_mp4', 'sample_transcoder_video', 'Storage', 'a:1:{s:6:"object";s:28:"sample_transcoder_video_orig";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', ''),
+('sample_video_webm', 'sample_transcoder_video', 'Storage', 'a:1:{s:6:"object";s:28:"sample_transcoder_video_orig";}', 'no', 0, 0, 0, 'BxDolTranscoderVideo', '');
+
+INSERT INTO `sys_transcoder_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
+('sample_video_webm', 'Webm', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:4:"webm";}', 0),
+('sample_video_mp4', 'Mp4', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:3:"mp4";}', 0),
+('sample_video_poster', 'Poster', 'a:2:{s:1:"h";s:3:"480";s:10:"force_type";s:3:"jpg";}', 0);
+
+ * @endcode
+ *
+ */
+
+require_once('./../inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLanguages');
+bx_import('BxDolTemplate');
+
+$oTemplate = BxDolTemplate::getInstance();
+$oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+$oTemplate->setPageHeader ("Sample video transcoder");
+$oTemplate->setPageContent ('page_main_code', PageCompMainCode());
+$oTemplate->getPageCode();
+
+/**
+ * page code function
+ */
+function PageCompMainCode()
+{
+    ob_start();
+
+    $sTranscoderObjectPoster = 'sample_video_poster';
+    $sTranscoderObjectMP4 = 'sample_video_mp4';
+    $sTranscoderObjectWebM = 'sample_video_webm';
+    $sStorageObjectOrig = 'sample_transcoder_video_orig';
+    $iProfileId = bx_get_logged_profile_id();
+
+    if (!$iProfileId) {
+        echo "You aren't logged in.";
+        exit;
+    }
+
+    bx_import('BxDolTranscoderVideo');
+    $iPrunedFiles = BxDolTranscoder::pruning();
+    if ($iPrunedFiles) {
+        echo "iPrunedFiles: $iPrunedFiles";
+        exit;
+    }
+    $oTranscoderPoster = BxDolTranscoderVideo::getObjectInstance($sTranscoderObjectPoster);
+    $oTranscoderMP4 = BxDolTranscoderVideo::getObjectInstance($sTranscoderObjectMP4);
+    $oTranscoderWebM = BxDolTranscoderVideo::getObjectInstance($sTranscoderObjectWebM);
+    if (!$oTranscoderPoster || !$oTranscoderMP4 || !$oTranscoderWebM) {
+        echo "Transcoder object is not available: " . $sTranscoderObjectPoster . ', ' . $sTranscoderObjectMP4 . ', ' . $sTranscoderObjectWebM;
+        exit;
+    }
+    echo "registerHandlers poster: [" . $oTranscoderPoster->registerHandlers() . "] <br />\n";
+    echo "registerHandlers mp4: [" . $oTranscoderMP4->registerHandlers() . "] <br />\n";
+    echo "registerHandlers webm: [" . $oTranscoderWebM->registerHandlers() . "] <hr class='bx-def-hr' />\n";
+
+
+    bx_import('BxDolStorage');
+    $oStorageOrig = BxDolStorage::getObjectInstance($sStorageObjectOrig);
+    if (!$oStorageOrig) {
+        echo "Storage object is not available: " . $sStorageObjectOrig;
+        exit;
+    }
+
+    if (isset($_POST['upload'])) {
+    
+        $iId = $oStorageOrig->storeFileFromForm($_FILES['file'], true, $iProfileId);
+        if ($iId) {
+            $iCount = $oStorageOrig->afterUploadCleanup($iId, $iProfileId);
+            echo "<h2>Uploaded file id: " . $iId . "(deleted ghosts:" . $iCount . ") </h2>";
+
+            // force transcode
+            echo "Force transcode: <br />";
+            echo "poster: " . $oTranscoderPoster->getFileUrl($iId) . '<br />';
+            echo "mp4: " . $oTranscoderMP4->getFileUrl($iId) . '<br />';
+            echo "webm: " . $oTranscoderWebM->getFileUrl($iId) . '<hr class="bx-def-hr" />';
+        } else {
+            echo "<h2>Error uploading file: " . $oStorage->getErrorString() . '</h2><hr class="bx-def-hr" />';
+        }
+
+    }
+    elseif (isset($_POST['delete'])) {
+
+        foreach ($_POST['file_id'] as $iFileId) {
+            $bRet = $oStorageOrig->deleteFile($iFileId, $iProfileId);
+            if ($bRet)
+                echo "<h2>Deleted file id: " . $iFileId . '</h2><hr class="bx-def-hr" />';
+            else
+                echo "<h2>File deleting error: " . $oStorageOrig->getErrorString() . '</h2><hr class="bx-def-hr" />';
+        }
+
+    } 
+    else {
+
+        $a = $oStorageOrig->getFilesAll();
+        foreach ($a as $r) {
+            $sUrlPoster = $oTranscoderPoster->getFileUrl($r['id']);
+            $sUrlMP4 = $oTranscoderMP4->getFileUrl($r['id']);
+            $sUrlWebM = $oTranscoderWebM->getFileUrl($r['id']);
+
+            echo '<h3>' . $r['file_name'] . '</h3>
+                <video controls="" preload="none" autobuffer="" poster="' . $sUrlPoster . '" style="width:100%; height:200px;">
+                    ' . ($sUrlWebM ? '<source type="video/webm; codecs="vp8, vorbis" src="' . $sUrlWebM . '" />' : '') . '
+                    ' . ($sUrlMP4  ? '<source type="video/mp4" src="' . $sUrlMP4 . '" />' : '') . '
+                </video>
+                <hr class="bx-def-hr" />';
+        }
+    }
+
+
+    $a = $oStorageOrig->getFilesAll();
+?>
+    <h2>Files List</h2>
+    <form method="POST">
+        <?php foreach ($a as $r): ?>
+            <input type="checkbox" name="file_id[]" value="<?=$r['id'] ?>" />
+            <?=$r['file_name'] ?>
+            <br />
+        <?php endforeach; ?>
+        <input type="submit" name="delete" value="Delete" class="bx-btn bx-btn-small bx-def-margin-sec-top" style="float:none;" />
+    </form>
+    <hr class="bx-def-hr" /> 
+
+
+    <h2>Upload</h2>
+    <form enctype="multipart/form-data" method="POST">
+        <input type="file" name="file" />
+        <br />
+        <input type="submit" name="upload" value="Upload" class="bx-btn bx-btn-small bx-def-margin-sec-top" style="float:none;" />
+    </form>
+<?php
+
+    $s = ob_get_clean();
+    return DesignBoxContent("Sample video transcoder", $s, BX_DB_PADDING_DEF);
+
+}

BIN
template/images/video-na.png