PageRenderTime 60ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/Controller/Component/VideoPreviewComponent.php

https://github.com/xemle/phtagr
PHP | 269 lines | 166 code | 31 blank | 72 comment | 32 complexity | 3fa2f12ea8c3e7515b70f1cfec0ad23f MD5 | raw file
  1. <?php
  2. /**
  3. * PHP versions 5
  4. *
  5. * phTagr : Organize, Browse, and Share Your Photos.
  6. * Copyright 2006-2013, Sebastian Felis (sebastian@phtagr.org)
  7. *
  8. * Licensed under The GPL-2.0 License
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright 2006-2013, Sebastian Felis (sebastian@phtagr.org)
  12. * @link http://www.phtagr.org phTagr
  13. * @package Phtagr
  14. * @since phTagr 2.2b3
  15. * @license GPL-2.0 (http://www.opensource.org/licenses/GPL-2.0)
  16. */
  17. class VideoPreviewComponent extends Component {
  18. var $controller = null;
  19. var $components = array('FileCache', 'FileManager', 'Command');
  20. var $_semaphoreId = false;
  21. var $bin = false;
  22. var $createVideoThumbOption = 'filter.video.createThumb';
  23. var $createVideoThumb = false;
  24. public function initialize(Controller $controller) {
  25. $this->controller = $controller;
  26. $this->bin = $this->controller->getOption('bin.ffmpeg', null);
  27. $this->createVideoThumb = $this->bin && $this->controller->getOption($this->createVideoThumbOption, false);
  28. if (function_exists('sem_get')) {
  29. $this->_semaphoreId = sem_get(4713);
  30. }
  31. }
  32. private function _getDummyPreview() {
  33. return APP . 'webroot' . DS . 'img' . DS . 'dummy_video_preview.jpg';
  34. }
  35. /**
  36. * Finds the video thumb of a video
  37. *
  38. * @param array $video File model data of the video
  39. * @param boolean $insertIfMissing If true, adds the thumb file to the database. Default is true
  40. * @return string Filename of the thumb file. False if no thumb file was found
  41. */
  42. public function findVideoThumb(&$media) {
  43. $thumb = $this->controller->Media->getFile($media, FILE_TYPE_VIDEOTHUMB, false);
  44. if ($thumb) {
  45. return $this->controller->MyFile->getFilename($thumb);
  46. }
  47. $video = $this->controller->Media->getFile($media, FILE_TYPE_VIDEO, false);
  48. $videoFilename = $this->controller->MyFile->getFilename($video);
  49. $path = dirname($videoFilename);
  50. $folder = new Folder($path);
  51. $pattern = basename($videoFilename);
  52. $pattern = substr($pattern, 0, strrpos($pattern, '.') + 1) . '[Tt][Hh][Mm]';
  53. $found = $folder->find($pattern);
  54. if (count($found) && is_readable(Folder::addPathElement($path, $found[0]))) {
  55. $thumbFilename = Folder::addPathElement($path, $found[0]);
  56. $thumb = $this->controller->MyFile->findByFilename($thumbFilename);
  57. if (!$thumb) {
  58. $thumbId = $this->FileManager->add($thumbFilename, $video['File']['user_id']);
  59. CakeLog::debug("Add missing video thumb $thumbFilename to database: $thumbId");
  60. $thumb = $this->controller->MyFile->findById($thumbId);
  61. }
  62. if (!$thumb) {
  63. CakeLog::error("Could not find thumbnail in database");
  64. return false;
  65. }
  66. if ($thumb['File']['media_id'] != $video['File']['media_id'] &&
  67. $this->controller->MyFile->setMedia($thumb, $video['File']['media_id'])) {
  68. CakeLog::debug("Link video thumb {$thumb['File']['id']} to media {$video['File']['media_id']}");
  69. }
  70. return $thumbFilename;
  71. }
  72. return false;
  73. }
  74. /**
  75. * @param string $videoFilename Video filename
  76. * @return string THM video thumbnail filename
  77. */
  78. private function _getVideoThumbFilename($videoFilename) {
  79. return substr($videoFilename, 0, strrpos($videoFilename, '.') + 1) . 'thm';
  80. }
  81. /**
  82. * Creates a video preview image using ffmpeg
  83. *
  84. * @param string $videoFilename Video file
  85. * @param string $thumbFilename Optional filename of the thumbnail image file
  86. * @return string Filename of the video thumbnail. False on failure
  87. */
  88. private function _create($videoFilename, $thumbFilename) {
  89. if (!file_exists($videoFilename) || !is_readable($videoFilename)) {
  90. CakeLog::error("Video file '$videoFilename' does not exists or is readable");
  91. return false;
  92. }
  93. if (!is_writeable(dirname($thumbFilename))) {
  94. CakeLog::error("Could not write video thumb. Path '".dirname($thumbFilename)."' is not writable");
  95. return false;
  96. }
  97. if ($this->_semaphoreId) {
  98. sem_acquire($this->_semaphoreId);
  99. }
  100. $result = $this->Command->run($this->bin, array(
  101. '-i' => $videoFilename,
  102. '-vframes' => 1,
  103. '-f' => 'mjpeg',
  104. '-y', $thumbFilename));
  105. if ($this->_semaphoreId) {
  106. sem_release($this->_semaphoreId);
  107. }
  108. if ($result != 0) {
  109. CakeLog::error("Command '{$this->bin}' returned unexcpected $result");
  110. return false;
  111. } else {
  112. CakeLog::info("Created video thumbnail of '$videoFilename'");
  113. }
  114. return $thumbFilename;
  115. }
  116. /**
  117. * @param array $media Media model data
  118. * @return string Filename of main video
  119. */
  120. private function _getVideoFile(&$media) {
  121. $video = $this->controller->Media->getFile($media, FILE_TYPE_VIDEO, false);
  122. return $this->controller->MyFile->getFilename($video);
  123. }
  124. /**
  125. * Create a THM video thumbnail for given media
  126. *
  127. * @param array $media Media model data
  128. * @return string Filename of video thumbnail. False on error
  129. */
  130. public function createVideoThumb(&$media) {
  131. $video = $this->controller->Media->getFile($media, FILE_TYPE_VIDEO, false);
  132. $videoFile = $this->controller->MyFile->getFilename($video);
  133. $filename = $this->_getVideoThumbFilename($videoFile);
  134. $thumbFilename = $this->_create($videoFile, $filename);
  135. if ($thumbFilename) {
  136. $this->FileManager->add($thumbFilename, $media['Media']['user_id']);
  137. $thumb = $this->controller->MyFile->findByFilename($thumbFilename);
  138. if ($this->controller->MyFile->setMedia($thumb, $video['File']['media_id'])) {
  139. CakeLog::debug("Link thumbnail {$thumb['File']['id']} to media {$video['File']['media_id']}");
  140. }
  141. }
  142. return $thumbFilename;
  143. }
  144. /**
  145. * Add play watermark to the video preview file
  146. *
  147. * @param type $filename
  148. */
  149. private function _addWatermark($filename) {
  150. $watermarkFile = APP . 'webroot' . DS . 'img' . DS . 'play.icon.png';
  151. App::uses('WatermarkCreator', 'Lib');
  152. $watermark = new WatermarkCreator();
  153. $scaleMode = 'inner';
  154. $position = ''; // empty for center
  155. if (!$watermark->create($filename, $watermarkFile, $scaleMode, $position)) {
  156. CakeLog::error(join(', ', $watermark->errors));
  157. }
  158. }
  159. /**
  160. * Returns the preview filename of the internal cache
  161. *
  162. * @param array $media Media model data
  163. * @return string Cached preview filename
  164. */
  165. private function _getPreviewFilenameCache(&$media) {
  166. $path = $this->FileCache->getPath($media);
  167. $file = $this->FileCache->getFilenamePrefix($media['Media']['id']);
  168. $thumbFilename = $path . $file . 'preview.thm';
  169. return $thumbFilename;
  170. }
  171. /**
  172. * Copy given video thumbnail to cached location
  173. *
  174. * @param string $thumbFilename Filename of video thumbnail
  175. * @param string $cacheFilename Filename of cached video thumbnail
  176. * @return string Filename of cache filename. On error returns a dummy preview file
  177. */
  178. private function _createCachedFile($thumbFilename, $cacheFilename) {
  179. if (!is_readable($thumbFilename)) {
  180. CakeLog::error("Thumbnail file is not readable: $thumbFilename");
  181. return $this->_getDummyPreview();
  182. } else if (!is_writable(dirname($cacheFilename))) {
  183. CakeLog::error("Target directory " . dirname($cacheFilename) . " is not writable for copy");
  184. return $this->_getDummyPreview();
  185. }
  186. if ($thumbFilename != $cacheFilename) {
  187. @copy($thumbFilename, $cacheFilename);
  188. }
  189. $this->_addWatermark($cacheFilename);
  190. return $cacheFilename;
  191. }
  192. /**
  193. * Validate if given media is a valid video media
  194. *
  195. * @param array $media Media model data
  196. * @return boolean True if media is valid
  197. */
  198. private function _validateVideoMedia(&$media) {
  199. $video = $this->controller->Media->getFile($media, FILE_TYPE_VIDEO, false);
  200. if (!$video) {
  201. CakeLog::error("Media {$media['Media']['id']} has no attached video file");
  202. return false;
  203. }
  204. $videoFilename = $this->controller->MyFile->getFilename($video);
  205. if (!is_readable($videoFilename)) {
  206. CakeLog::error("Video file of media {$media['Media']['id']} not readable: $videoFilename");
  207. return false;
  208. }
  209. return true;
  210. }
  211. /**
  212. * Gets the thumbnail filename of the a video.
  213. *
  214. * @param image Media model data
  215. */
  216. public function getPreviewFilename($media) {
  217. $cache = $this->_getPreviewFilenameCache($media);
  218. if (is_readable($cache)) {
  219. return $cache;
  220. }
  221. if (!$this->_validateVideoMedia($media)) {
  222. CakeLog::error("Invalid media {$media['Media']['id']}");
  223. return $this->_getDummyPreview();
  224. }
  225. $thumbFilename = $this->findVideoThumb($media);
  226. if ($thumbFilename) {
  227. return $this->_createCachedFile($thumbFilename, $cache);
  228. }
  229. if ($this->createVideoThumb) {
  230. $thumbFilename = $this->createVideoThumb($media);
  231. return $this->_createCachedFile($thumbFilename, $cache);
  232. }
  233. CakeLog::info("Create cached video preview $cache");
  234. $videoFile = $this->_getVideoFile($media);
  235. $this->_create($videoFile, $cache);
  236. return $this->_createCachedFile($cache, $cache);
  237. }
  238. }