PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/library/X/VlcShares/Plugins/Helper/FFMpeg.php

http://vlc-shares.googlecode.com/
PHP | 407 lines | 202 code | 66 blank | 139 comment | 45 complexity | 113aea9ef47d2f2409cb64e89a6b5699 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. require_once ('X/VlcShares/Plugins/Helper/Abstract.php');
  3. require_once ('X/VlcShares/Plugins/Helper/StreaminfoInterface.php');
  4. class X_VlcShares_Plugins_Helper_FFMpeg extends X_VlcShares_Plugins_Helper_Abstract implements X_VlcShares_Plugins_Helper_StreaminfoInterface {
  5. private $_location = null;
  6. private $_fetched = false;
  7. private $formatTests = array();
  8. /**
  9. *
  10. * @var Zend_Config
  11. */
  12. private $options = null;
  13. function __construct(Zend_Config $options) {
  14. $this->options = $options;
  15. //X_Debug::i("Helper options: ".var_export($options->toArray(), true));
  16. $this->formatTests = array(
  17. // audio
  18. X_VlcShares_Plugins_Helper_StreaminfoInterface::ACODEC_AAC => array('aac'),
  19. X_VlcShares_Plugins_Helper_StreaminfoInterface::ACODEC_AC3 => array('ac3'),
  20. X_VlcShares_Plugins_Helper_StreaminfoInterface::ACODEC_MP3 => array('mp3'),
  21. // video
  22. X_VlcShares_Plugins_Helper_StreaminfoInterface::VCODEC_FLV => array('flv'),
  23. X_VlcShares_Plugins_Helper_StreaminfoInterface::VCODEC_H264 => array('h264'),
  24. X_VlcShares_Plugins_Helper_StreaminfoInterface::VCODEC_XVID => array('mpeg4'),
  25. // unknown
  26. X_VlcShares_Plugins_Helper_StreaminfoInterface::AVCODEC_UNKNOWN => array('')
  27. );
  28. }
  29. /**
  30. * Show if helper is enabled and ready to get infos
  31. * @return boolean
  32. */
  33. function isEnabled() {
  34. return ($this->options->get('enabled', false) && file_exists($this->options->get('path', false)));
  35. }
  36. /**
  37. * Set location source
  38. *
  39. * @param $location the source
  40. * @return X_VlcShares_Plugins_Helper_Mediainfo
  41. */
  42. function setLocation($location) {
  43. if ( $this->_location != $location ) {
  44. $this->_location = $location;
  45. $this->_fetched = false;
  46. }
  47. return $this;
  48. }
  49. /**
  50. * Return all infos about the source setted
  51. * with setLocation() in an associative array
  52. * The array has this format:
  53. * array(
  54. * 'source' => $source
  55. * 'videos' => array from getVideosInfo()
  56. * 'audios' => array from getAudiosInfo()
  57. * 'subs' => array from getSubsInfo()
  58. * )
  59. *
  60. * @return array
  61. */
  62. function getInfos() {
  63. $this->fetch();
  64. return $this->_fetched;
  65. }
  66. /**
  67. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getAudioCodecName()
  68. * @param int $index
  69. * @return string
  70. */
  71. public function getAudioCodecName($index = -1) {
  72. $this->fetch();
  73. if ( $index == -1 ) {
  74. reset($this->_fetched['audios']);
  75. $index = key($this->_fetched['audios']);
  76. }
  77. if ( array_key_exists($index, $this->_fetched['audios']) ) {
  78. return $this->_fetched['audios'][$index]['codecName'];
  79. } else {
  80. throw new Exception("There is no stream $index in source {$this->_location}");
  81. }
  82. }
  83. /**
  84. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getAudioCodecType()
  85. * @param int $index
  86. */
  87. public function getAudioCodecType($index = -1) {
  88. $this->fetch();
  89. if ( $index == -1 ) {
  90. reset($this->_fetched['audios']);
  91. $index = key($this->_fetched['audios']);
  92. }
  93. if ( array_key_exists($index, $this->_fetched['audios']) ) {
  94. return $this->_fetched['audios'][$index]['codecType'];
  95. } else {
  96. throw new Exception("There is no stream $index in source {$this->_location}");
  97. }
  98. }
  99. /**
  100. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getAudioInfo()
  101. * @return array
  102. */
  103. public function getAudiosInfo() {
  104. $this->fetch();
  105. // @ prevent error fetched data
  106. return @$this->_fetched['audios'];
  107. }
  108. /**
  109. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getAudioStreamsNumber()
  110. * @return int
  111. */
  112. public function getAudioStreamsNumber() {
  113. $this->fetch();
  114. // @ prevent error fetched data
  115. return count(@$this->_fetched['audios']);
  116. }
  117. /**
  118. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getSubFormat()
  119. * @param int $index
  120. * @return string
  121. */
  122. public function getSubFormat($index = -1) {
  123. $this->fetch();
  124. if ( $index == -1 ) {
  125. reset($this->_fetched['subs']);
  126. $index = key($this->_fetched['subs']);
  127. }
  128. if ( array_key_exists($index, $this->_fetched['subs']) ) {
  129. return $this->_fetched['subs'][$index]['format'];
  130. } else {
  131. throw new Exception("There is no sub $index in source {$this->_location}");
  132. }
  133. }
  134. /**
  135. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getSubLanguage()
  136. * @param index $index
  137. * @return string
  138. */
  139. public function getSubLanguage($index = -1) {
  140. $this->fetch();
  141. if ( $index == -1 ) {
  142. reset($this->_fetched['subs']);
  143. $index = key($this->_fetched['subs']);
  144. }
  145. if ( array_key_exists($index, $this->_fetched['subs']) ) {
  146. return $this->_fetched['subs'][$index]['language'];
  147. } else {
  148. throw new Exception("There is no sub $index in source {$this->_location}");
  149. }
  150. }
  151. /**
  152. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getSubsInfo()
  153. * @return array
  154. */
  155. public function getSubsInfo() {
  156. $this->fetch();
  157. // @ prevent error fetched data
  158. return @$this->_fetched['subs'];
  159. }
  160. /**
  161. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getSubsNumber()
  162. * @return int
  163. */
  164. public function getSubsNumber() {
  165. $this->fetch();
  166. // @ prevent error fetched data
  167. return count(@$this->_fetched['subs']);
  168. }
  169. /**
  170. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getVideoCodecName()
  171. * @param int $index
  172. * @return string
  173. */
  174. public function getVideoCodecName($index = -1) {
  175. $this->fetch();
  176. if ( $index == -1 ) {
  177. reset($this->_fetched['videos']);
  178. $index = key($this->_fetched['videos']);
  179. }
  180. if ( array_key_exists($index, $this->_fetched['videos']) ) {
  181. return $this->_fetched['videos'][$index]['codecName'];
  182. } else {
  183. throw new Exception("There is no stream $index in source {$this->_location}");
  184. }
  185. }
  186. /**
  187. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getAudioCodecType()
  188. * @param int $index
  189. * @return int
  190. */
  191. public function getVideoCodecType($index = -1) {
  192. $this->fetch();
  193. if ( $index == -1 ) {
  194. reset($this->_fetched['videos']);
  195. $index = key($this->_fetched['videos']);
  196. }
  197. if ( array_key_exists($index, $this->_fetched['videos']) ) {
  198. return $this->_fetched['videos'][$index]['codecType'];
  199. } else {
  200. throw new Exception("There is no stream $index in source {$this->_location}");
  201. }
  202. }
  203. /**
  204. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getVideosInfo()
  205. * @return array
  206. */
  207. public function getVideosInfo() {
  208. $this->fetch();
  209. // @ prevent error fetched data
  210. return @$this->_fetched['videos'];
  211. }
  212. /**
  213. * @see X_VlcShares_Plugins_Helper_StreaminfoInterface::getVideoStreamsNumber()
  214. * @return int
  215. */
  216. public function getVideoStreamsNumber() {
  217. $this->fetch();
  218. // @ prevent error fetched data
  219. return count(@$this->_fetched['videos']);
  220. }
  221. /**
  222. * Fetch info about location
  223. */
  224. private function fetch() {
  225. // if $this->_location should be fetched
  226. // $this->_fetched === false is true
  227. // else all datas are in $this->_fetched (array)
  228. if ( $this->_fetched === false ) {
  229. if ( !$this->options->enabled || !file_exists($this->options->path) ) {
  230. X_Debug::e("Helper disabled ({$this->options->enabled}) or wrong path ({$this->options->path})");
  231. $ffmpegOutput = array();
  232. } else {
  233. $ffmpegOutput = $this->_invoke();
  234. }
  235. //$dom = new Zend_Dom_Query($xmlString);
  236. $fetched = array(
  237. 'source' => $this->_location,
  238. //'videos' => array(array('codecName' => 'h264', 'codecType' => X_VlcShares_Plugins_Helper_StreaminfoInterface::VCODEC_H264)),
  239. //'audios' => array(array('codecName' => 'aac', 'codecType' => X_VlcShares_Plugins_Helper_StreaminfoInterface::ACODEC_AAC)),
  240. 'videos' => array(), // should indentify correctly
  241. 'audios' => array(), // should identify correctly
  242. 'subs' => array()
  243. );
  244. //X_Debug::i(var_export($ffmpegOutput, true));
  245. foreach ($ffmpegOutput as $line) {
  246. $line = trim($line);
  247. if ( $line == '' ) continue; // jump away from empty line
  248. // we are looking for line like this:
  249. // Stream #0.0(jpn): Video: h264, yuv420p, 1280x720, PAR 1:1 DAR 16:9, 23.98 tbr, 1k tbn, 47.95 tbc
  250. // Stream #0.1(jpn): Audio: aac, 48000 Hz, stereo, s16
  251. // Stream #0.2(ita): Subtitle: 0x0000
  252. // Stream #0.3: Attachment: 0x0000 <--- MKV Menu
  253. // OR DIFFERENT VERSION:
  254. //Stream #0:0: Video: h264 (High) (H264 / 0x34363248), yuv420p, 640x480 [SAR 1:1 DAR 4:3], 23.98 fps, 23.98 tbr, 1k tbn, 47.95 tbc (default)
  255. //Stream #0:1(eng): Audio: vorbis, 48000 Hz, stereo, s16 (default)
  256. //Stream #0:2(jpn): Audio: vorbis, 48000 Hz, mono, s16
  257. //Stream #0:3(eng): Subtitle: ssa (default)
  258. //if ( !X_Env::startWith($line, 'Stream #0') ) continue;
  259. $matches = array();
  260. $pattern = '/Stream #(?P<mainid>\d+)(.|:)(?P<subid>\d+)(?P<lang>(\(\w+\))?): (?P<type>\w+): (?P<codec>[^,\s]+)(?P<extra>.*)/';
  261. if ( !preg_match($pattern, $line, $matches) ) {
  262. continue;
  263. }
  264. X_Debug::i("Checking line: $line");
  265. $language = @$matches['lang'];
  266. $streamID = $matches['subid'];
  267. $streamType = $matches['type'];
  268. $streamFormat = $matches['codec'];
  269. $streamMore = $matches['extra'];
  270. // it's the line we are looking for
  271. // time to split
  272. //list(, $streamID, $streamType, $streamFormat, $streamMore ) = explode(' ', $line, 5);
  273. /*
  274. X_Debug::i("StreamID (raw): $streamID");
  275. X_Debug::i("StreamType (raw): $streamType");
  276. X_Debug::i("StreamFormat (raw): $streamFormat");
  277. X_Debug::i("StreamMore (raw): $streamMore");
  278. */
  279. // in 0 -> Stream
  280. // in 1 -> #0.StreamID(language): <--- language is present only in mkv files. For avi, no (language)
  281. // in 2 -> StreamType: <---- Video|Audio|Subtitle|Attachment
  282. // in 3 -> StreamFormat, blablabla <--- for audio and video
  283. // OR
  284. // in 3 -> StreamFormat <---- for subtitle and attachment
  285. switch ( $streamType ) {
  286. case 'Video': $streamType = 'videos'; break;
  287. case 'Audio': $streamType = 'audios'; break;
  288. case 'Subtitle': $streamType = 'subs'; break;
  289. default: $streamType = false;
  290. }
  291. if ( !$streamType ) continue; // check for Annotation or unknown type of stream
  292. // time to get the real streamID
  293. //
  294. //@list($streamID, $language) = explode('(', trim($streamID, '):'), 2);
  295. // in $streamID there is : #0.1
  296. // discard the first piece
  297. //list( , $streamID) = explode('.', ltrim($streamID,'#'), 2);
  298. $infoStream = array();
  299. if ( $streamType == 'subs' ) {
  300. // i don't need format decoding for subs
  301. $infoStream['format'] = trim($streamFormat);
  302. $infoStream['language'] = $language;
  303. } else {
  304. $infoStream = array('codecType' => X_VlcShares_Plugins_Helper_StreaminfoInterface::AVCODEC_UNKNOWN,
  305. 'codecName' => X_VlcShares_Plugins_Helper_StreaminfoInterface::AVCODEC_UNKNOWN);
  306. foreach ($this->formatTests as $key => $test) {
  307. $valid = false;
  308. if ( $test[0] == $streamFormat ) {
  309. $valid = true;
  310. if ( count($test) > 1 && !X_Env::startWith(trim($streamMore), $test[1] ) ) {
  311. $valid = false;
  312. }
  313. }
  314. if ( $valid ) {
  315. $infoStream = array('codecType' => $key, 'codecName' => $key);
  316. break;
  317. }
  318. }
  319. }
  320. $infoStream['ID'] = $streamID;
  321. // language if available
  322. if ( $language ) $infoStream['language'] = $language;
  323. //$fetched[$streamType][$streamID] = $infoStream;
  324. // no index as key for use with vlc --sub-track
  325. X_Debug::i("Stream type {{$streamType}} found: ".print_r($infoStream, true));
  326. $fetched[$streamType][] = $infoStream;
  327. }
  328. //X_Debug::i(var_export($fetched, true));
  329. // I use lazy init for info
  330. // and I insert results in cache
  331. $this->_fetched = $fetched;
  332. }
  333. }
  334. private function _invoke() {
  335. $source = $this->_location;
  336. $osTweak = X_Env::isWindows() ? '2>&1' : '2>&1';
  337. $ffmpeg = "\"{$this->options->path}\"";
  338. $str = X_Env::execute("$ffmpeg -i \"$source\" $osTweak", X_Env::EXECUTE_OUT_ARRAY, X_Env::EXECUTE_PS_WAIT );
  339. return $str;
  340. }
  341. }