BxDolTranscoderVideo.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
  4. * CC-BY License - http://creativecommons.org/licenses/by/3.0/
  5. *
  6. * @defgroup DolphinCore Dolphin Core
  7. * @{
  8. */
  9. bx_import('BxDolTranscoder');
  10. bx_import('BxDolTranscoderVideoQuery');
  11. /**
  12. * @page objects
  13. * @section transcoder_videos Videos Transcoder
  14. * @ref BxDolTranscoderVideo
  15. */
  16. class BxDolTranscoderVideo extends BxDolTranscoder implements iBxDolFactoryObject
  17. {
  18. protected function __construct($aObject, $oStorage)
  19. {
  20. parent::__construct($aObject, $oStorage);
  21. $this->_sQueueStorage = ''; // TODO:
  22. $this->_oDb = new BxDolTranscoderVideoQuery($aObject);
  23. $this->_sQueueTable = $this->_oDb->getQueueTable();
  24. }
  25. public function getDevicePixelRatioHandlerSuffix ()
  26. {
  27. return '';
  28. }
  29. /**
  30. * If video not processed yet then empty string is returned for video, and predefined image is returned for video poster
  31. */
  32. public function getFileUrlNotReady($mixedHandler)
  33. {
  34. return $this->_isPosterFilter() ? BxDolTemplate::getInstance()->getImageUrl('video-na.png') : '';
  35. }
  36. /**
  37. * Convert video to .jpg format - video poster
  38. */
  39. protected function applyFilter_Poster ($sFile, $aParams)
  40. {
  41. bx_import('BxDolImageResize');
  42. $oImage = BxDolImageResize::getInstance();
  43. $sFileOut = $this->getTmpFilename('.jpg');
  44. $bRet = false;
  45. $aSeconds = array (0, 3, 5, 0);
  46. $sSize = $this->_getOptionSizeForVideo ($sFile, $aParams);
  47. foreach ($aSeconds as $iSecond) {
  48. $bRet = $this->_convertVideo($sFile, $sFileOut, '.jpg', $aParams, array (
  49. 's' => $sSize,
  50. 'ss' => $iSecond,
  51. 'vframes' => 1,
  52. 'f' => 'image2',
  53. 'an' => ' ',
  54. ));
  55. if (!$bRet)
  56. continue;
  57. $aRgb = $oImage->getAverageColor($sFileOut);
  58. $fRgb = ($aRgb['r'] + $aRgb['g'] + $aRgb['b']) / 3;
  59. if ($fRgb > 32 && $fRgb < 224)
  60. break;
  61. }
  62. if (!$bRet) {
  63. unlink($sFileOut);
  64. return false;
  65. }
  66. return rename($sFileOut, $sFile);
  67. }
  68. /**
  69. * Convert video to .mp4 format
  70. */
  71. protected function applyFilter_Mp4 ($sFile, $aParams)
  72. {
  73. return $this->_convertVideo($sFile, $sFile, '.mp4', $aParams, array (
  74. 'strict' => 'experimental',
  75. 'vcodec' => 'libx264',
  76. 's' => $this->_getOptionSizeForVideo ($sFile, $aParams),
  77. 'b:v' => isset($aParams['video_bitrate']) ? $aParams['video_bitrate'] . 'k' : '',
  78. 'acodec' => 'aac',
  79. 'ar' => '44100',
  80. 'b:a' => isset($aParams['audio_bitrate']) ? $aParams['audio_bitrate'] . 'k' : '128k',
  81. ));
  82. }
  83. /**
  84. * Convert video to .webm format
  85. */
  86. protected function applyFilter_Webm ($sFile, $aParams)
  87. {
  88. return $this->_convertVideo($sFile, $sFile, '.webm', $aParams, array (
  89. 's' => $this->_getOptionSizeForVideo ($sFile, $aParams),
  90. 'b:v' => isset($aParams['video_bitrate']) ? $aParams['video_bitrate'] . 'k' : '',
  91. 'acodec' => 'libvorbis',
  92. 'ar' => '44100',
  93. 'b:a' => isset($aParams['audio_bitrate']) ? $aParams['audio_bitrate'] . 'k' : '128k',
  94. ));
  95. }
  96. /**
  97. * Convert video using ffmpeg binary. Video conversion otput is written to the log variable.
  98. * @param $sFile input file for conversion
  99. * @param $sFileOut output file for conversion, can be the same as input file
  100. * @param $sExt output file extension, in the format '.ext'
  101. * @param $aParams filter params
  102. * @param $aOptions ffmpeg options as array
  103. * @return false on error, or true on success
  104. */
  105. protected function _convertVideo ($sFile, $sFileOut, $sExt, $aParams, $aOptions)
  106. {
  107. if (!empty($aParams['ffmpeg_options']) && is_array($aParams['ffmpeg_options']))
  108. $aOptions = array_merge($aOptions, $aParams['ffmpeg_options']);
  109. $sOptions = '';
  110. foreach ($aOptions as $k => $v)
  111. if ($v)
  112. $sOptions .= "-{$k} {$v} ";
  113. $bRename = false;
  114. if ($sFileOut == $sFile) { // if output file is the same as input - generate new tmp file
  115. $sFileOut = $this->getTmpFilename($sExt);
  116. $bRename = true;
  117. }
  118. elseif (file_exists($sFileOut)) {
  119. @unlink($sFileOut);
  120. }
  121. $sCommand = BX_SYSTEM_FFMPEG . ' -y -i "' . escapeshellcmd($sFile) . '" ' . $sOptions . ' ' . $sFileOut;
  122. $sOutput = `$sCommand`;
  123. $this->addToLog("\n---\n{$sCommand}\n{$sOutput}\n");
  124. if (!file_exists($sFileOut) || 0 == filesize($sFileOut))
  125. return false;
  126. if ($bRename && !rename($sFileOut, $sFile)) // rename tmp file, if tmp file was generated
  127. return false;
  128. return true;
  129. }
  130. /**
  131. * Get video size.
  132. * It generated image from the video and returns size of this image.
  133. * @param $sFile path to video file
  134. * @return array where 'w' is width and 'h' is height
  135. */
  136. protected function _getVideoSize ($sFile)
  137. {
  138. $sFileOut = $this->getTmpFilename('.jpg');
  139. $bRet = $this->_convertVideo($sFile, $sFileOut, '.jpg', array(), array (
  140. 'ss' => 0,
  141. 'vframes' => 1,
  142. 'f' => 'image2',
  143. 'an' => ' ',
  144. ));
  145. if (!$bRet)
  146. return false;
  147. bx_import('BxDolImageResize');
  148. $oImage = BxDolImageResize::getInstance();
  149. $aSize = $oImage->getImageSize($sFileOut);
  150. @unlink($sFileOut);
  151. return $aSize;
  152. }
  153. /**
  154. * Get output video size as 'WxH' string.
  155. * It check filter params for 'w' and 'h' options and original video ratio and return the resulted video size.
  156. * - If only height specified in params (desired behavior) or no width or height is specified (default height is 360),
  157. * then width is automatically calculated using original video size ratio.
  158. * - If only width specified in params, then height is automatically calculated using original video size ratio.
  159. * - If width and height specified in params then these values are returned, but the resulted video size can be unproportionate.
  160. * @param $sFile original video file path (to check original video size ratio)
  161. * @param $aParams filter params
  162. * @return WxH string
  163. */
  164. protected function _getOptionSizeForVideo ($sFile, $aParams)
  165. {
  166. if (isset($aParams['w']) && isset($aParams['h']))
  167. return $aParams['w'] . 'x' . $aParams['h'];
  168. if (!isset($aParams['w']) && !isset($aParams['h']))
  169. $aParams['h'] = '360';
  170. $fRatio = 16/9;
  171. $aSize = $this->_getVideoSize($sFile);
  172. if ($aSize)
  173. $fRatio = $aSize['w'] / $aSize['h'];
  174. if (isset($aParams['h']))
  175. return round($fRatio * $aParams['h'] / 2) * 2 . 'x' . $aParams['h'];
  176. if (isset($aParams['w']))
  177. return $aParams['w'] . 'x' . round($aParams['w'] / $fRatio / 2) * 2;
  178. return '640x360'; // should never happen
  179. }
  180. /**
  181. * Checks if 'Poster' filter is enabled for curent transcoder
  182. */
  183. protected function _isPosterFilter ()
  184. {
  185. $this->initFilters ();
  186. foreach ($this->_aObject['filters'] as $aParams)
  187. if ('Poster' == $aParams['filter'])
  188. return true;
  189. return false;
  190. }
  191. }
  192. /** @} */