api.chanshots.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. /**
  3. * Channel screenshots service implementation
  4. */
  5. class ChanShots {
  6. /**
  7. * Contains binpaths.ini config as key=>value
  8. *
  9. * @var array
  10. */
  11. protected $binPaths = array();
  12. /**
  13. * Contains alter.ini config as key=>value
  14. *
  15. * @var array
  16. */
  17. protected $altCfg = array();
  18. /**
  19. * Cameras instance placeholder
  20. *
  21. * @var object
  22. */
  23. protected $cameras = '';
  24. /**
  25. * Recorder instance placeholder
  26. *
  27. * @var object
  28. */
  29. protected $recorder = '';
  30. /**
  31. * Storages instance placeholder.
  32. *
  33. * @var object
  34. */
  35. protected $storages = '';
  36. /**
  37. * Contains full cameras data as
  38. *
  39. * @var array
  40. */
  41. protected $allCamerasData = array();
  42. /**
  43. * Contains camera running recorders state as cameraId=>PID
  44. *
  45. * @var array
  46. */
  47. protected $cameraRecordersRunning = array();
  48. /**
  49. * Contains ffmpeg binary path
  50. *
  51. * @var string
  52. */
  53. protected $ffmpgPath = '';
  54. /**
  55. * Contains basic screenshots path
  56. *
  57. * @var string
  58. */
  59. protected $screenshotsPath = '';
  60. /**
  61. * Default chunk time offset to screenshot
  62. *
  63. * @var string
  64. */
  65. protected $timeOffset = '00:00:01';
  66. /**
  67. * Contains default screenshot options
  68. *
  69. * @var string
  70. */
  71. protected $screenshotOpts = '-loglevel error -frames:v 1 -q:v 15';
  72. /**
  73. * PixelCraft instance for further chanshots processing
  74. *
  75. * @var object
  76. */
  77. protected $pixelCraft = '';
  78. /**
  79. * Channel screenshots validation flag
  80. *
  81. * @var bool
  82. */
  83. protected $shotsValidationFlag = false;
  84. public function __construct() {
  85. $this->loadConfigs();
  86. $this->initPixelCraft();
  87. }
  88. /**
  89. * Some predefined paths here
  90. */
  91. const SHOTS_SUBDIR = 'chanshots/';
  92. const SHOTS_EXT = '.jpg';
  93. const CHANSHOTS_PID = 'CHANSHOTS';
  94. /**
  95. * predefined shots paths
  96. */
  97. const ERR_NOSIG='skins/nosignal.gif';
  98. const ERR_CORRUPT='skins/error.gif';
  99. const ERR_DISABLD='skins/chanblock.gif';
  100. /**
  101. * Loads some required configs
  102. *
  103. * @global $ubillingConfig
  104. *
  105. * @return void
  106. */
  107. protected function loadConfigs() {
  108. global $ubillingConfig;
  109. $this->binPaths = $ubillingConfig->getBinpaths();
  110. $this->altCfg = $ubillingConfig->getAlter();
  111. $this->ffmpgPath = $this->binPaths['FFMPG_PATH'];
  112. $this->screenshotsPath = Storages::PATH_HOWL . self::SHOTS_SUBDIR;
  113. $this->shotsValidationFlag = $ubillingConfig->getAlterParam('CHANSHOTS_VALIDATION');
  114. }
  115. /**
  116. * Inits PixelCraft object instance
  117. *
  118. * @return void
  119. */
  120. protected function initPixelCraft() {
  121. $this->pixelCraft = new PixelCraft();
  122. }
  123. /**
  124. * Inits cameras into protected prop and loads its full data
  125. *
  126. * @return void
  127. */
  128. protected function initCameras() {
  129. $this->cameras = new Cameras();
  130. $this->allCamerasData = $this->cameras->getAllCamerasFullData();
  131. }
  132. /**
  133. * Inits recorder instance for detecting is camera alive or not?
  134. *
  135. * @return void
  136. */
  137. protected function initRecorder() {
  138. $this->recorder = new Recorder();
  139. $this->cameraRecordersRunning = $this->recorder->getRunningRecorders();
  140. }
  141. /**
  142. * Returns screenshots base path
  143. *
  144. * @return string
  145. */
  146. public function getScreenshotsPath() {
  147. return ($this->screenshotsPath);
  148. }
  149. /**
  150. * Checks if a channel screenshot is valid.
  151. *
  152. * @param string $channelScreenshot The path to the channel screenshot file.
  153. * @return bool Returns true if the channel screenshot is valid, false otherwise.
  154. */
  155. public function isChannelScreenshotValid($channelScreenshot) {
  156. $result = true;
  157. if ($this->shotsValidationFlag) {
  158. $result = false;
  159. if (file_exists($channelScreenshot)) {
  160. $imageValid = $this->pixelCraft->isImageValid($channelScreenshot);
  161. if ($imageValid) {
  162. $result = true;
  163. }
  164. }
  165. }
  166. return ($result);
  167. }
  168. /**
  169. * Returns channel latest screenshot path
  170. *
  171. * @param string $channelId
  172. *
  173. * @return string/void
  174. */
  175. public function getChannelScreenShot($channelId) {
  176. $result = '';
  177. $channelId = ubRouting::filters($channelId, 'mres');
  178. if (file_exists($this->screenshotsPath)) {
  179. $screenshotName = $channelId . self::SHOTS_EXT;
  180. $fullPath = $this->screenshotsPath . $screenshotName;
  181. if (file_exists($fullPath)) {
  182. $result = $fullPath;
  183. }
  184. }
  185. return ($result);
  186. }
  187. /**
  188. * Inits storages into protected prop for further usage
  189. *
  190. * @return void
  191. */
  192. protected function initStorages() {
  193. $this->storages = new Storages();
  194. }
  195. /**
  196. * Allocates screenshots path, returns it if its writable
  197. *
  198. * @return string/void on error
  199. */
  200. protected function allocateScreenshotsPath() {
  201. $result = '';
  202. if (!file_exists($this->screenshotsPath)) {
  203. mkdir($this->screenshotsPath, 0777);
  204. chmod($this->screenshotsPath, 0777);
  205. log_register('CHANSHOTS ALLOCATED `' . $this->screenshotsPath . '`');
  206. } else {
  207. $result = $this->screenshotsPath;
  208. }
  209. return ($result);
  210. }
  211. /**
  212. * Cleanups old screenshot if it exists
  213. *
  214. * @param string $channel
  215. *
  216. * @return void
  217. */
  218. protected function flushOldScreenshot($channel) {
  219. if (!empty($channel)) {
  220. $fileName = $channel . self::SHOTS_EXT;
  221. $fullScreenShotPath = $this->screenshotsPath . $fileName;
  222. if (file_exists($fullScreenShotPath)) {
  223. unlink($fullScreenShotPath);
  224. }
  225. }
  226. }
  227. /**
  228. * Tooks screenshot from some channel chunk
  229. *
  230. * @param string $chunkPath
  231. * @param string $channel
  232. *
  233. * @return string
  234. */
  235. protected function takeChunkScreenshot($chunkPath, $channel) {
  236. $result = '';
  237. if (!empty($channel) and file_exists($chunkPath)) {
  238. $fileName = $channel . self::SHOTS_EXT;
  239. $fullScreenShotPath = $this->screenshotsPath . $fileName;
  240. //old screenshot cleanup
  241. $this->flushOldScreenshot($channel);
  242. //taking new screenshot for this channel
  243. $command = $this->ffmpgPath . ' -ss ' . $this->timeOffset . ' -i ' . $chunkPath . ' ' . $this->screenshotOpts . ' ' . $fullScreenShotPath;
  244. $result = shell_exec($command);
  245. }
  246. return ($result);
  247. }
  248. /**
  249. * Performs capturing screeenshots from all active camera channels
  250. *
  251. * @return void
  252. */
  253. public function run() {
  254. $process = new StarDust(self::CHANSHOTS_PID);
  255. if ($process->notRunning()) {
  256. $process->start();
  257. //preload required objects
  258. $this->initStorages();
  259. $this->initCameras();
  260. //any cameras here?
  261. if (!empty($this->allCamerasData)) {
  262. $this->initRecorder();
  263. $this->allocateScreenshotsPath();
  264. foreach ($this->allCamerasData as $eachCamId => $eachCameraData) {
  265. if ($eachCameraData['CAMERA']['active']) {
  266. $channel = $eachCameraData['CAMERA']['channel'];
  267. $storageId = $eachCameraData['CAMERA']['storageid'];
  268. //camera recorder now running?
  269. if (isset($this->cameraRecordersRunning[$eachCamId])) {
  270. $allCameraChunks = $this->storages->getChannelChunks($storageId, $channel);
  271. $chunksCount = sizeof($allCameraChunks);
  272. //dont shot single chunk - it may be unfinished
  273. if ($chunksCount > 1) {
  274. $lastChunk = array_pop($allCameraChunks);
  275. $secondLastChunk = array_pop($allCameraChunks);
  276. //taking screenshot from second last channel chunk
  277. $this->takeChunkScreenshot($secondLastChunk, $channel);
  278. }
  279. } else {
  280. //if no recorder now running just flush channel scrreenshot
  281. $this->flushOldScreenshot($channel);
  282. }
  283. }
  284. }
  285. }
  286. $process->stop();
  287. }
  288. }
  289. /**
  290. * Renders small channel preview for camera lists
  291. *
  292. * @param string $channel
  293. * @param string $screenshot
  294. *
  295. * @return string
  296. */
  297. public function renderListBox($channel, $screenshot) {
  298. $result = wf_tag('style');
  299. $result .= ' .preview' . $channel . ' {
  300. position: relative;
  301. margin-right: 10px;
  302. }
  303. .preview' . $channel . ' img {
  304. object-fit: cover;
  305. width: 124px;
  306. height: 75px;
  307. }
  308. ';
  309. $result .= wf_tag('style', true);
  310. $result .= wf_tag('span', false, 'preview' . $channel);
  311. $result .= wf_tag('img', false, 'preview' . $channel, 'src="' . $screenshot . '" style="width:124px;"');
  312. $result .= wf_tag('span', true);
  313. return ($result);
  314. }
  315. }