PageRenderTime 168ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/app/vendors/adapters/ffmpeg-php/php-reader/src/ID3v2.php

https://github.com/mariuz/firetube
PHP | 461 lines | 204 code | 43 blank | 214 comment | 49 complexity | 62d7da98de47aba97b5d77437ce810b8 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * PHP Reader Library
  4. *
  5. * Copyright (c) 2008 The PHP Reader Project Workgroup. All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * - Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. * - Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. * - Neither the name of the project workgroup nor the names of its
  16. * contributors may be used to endorse or promote products derived from this
  17. * software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  23. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. * POSSIBILITY OF SUCH DAMAGE.
  30. *
  31. * @package php-reader
  32. * @subpackage ID3
  33. * @copyright Copyright (c) 2008 The PHP Reader Project Workgroup
  34. * @license http://code.google.com/p/php-reader/wiki/License New BSD License
  35. * @version $Id: ID3v2.php 75 2008-04-14 23:57:21Z svollbehr $
  36. */
  37. /**#@+ @ignore */
  38. require_once("Reader.php");
  39. require_once("ID3/Exception.php");
  40. require_once("ID3/Header.php");
  41. require_once("ID3/ExtendedHeader.php");
  42. require_once("ID3/Frame.php");
  43. /**#@-*/
  44. /**
  45. * This class represents a file containing ID3v2 headers as described in
  46. * {@link http://www.id3.org/id3v2.4.0-structure ID3v2 structure document}.
  47. *
  48. * ID3v2 is a general tagging format for audio, which makes it possible to store
  49. * meta data about the audio inside the audio file itself. The ID3 tag is mainly
  50. * targeted at files encoded with MPEG-1/2 layer I, MPEG-1/2 layer II, MPEG-1/2
  51. * layer III and MPEG-2.5, but may work with other types of encoded audio or as
  52. * a stand alone format for audio meta data.
  53. *
  54. * ID3v2 is designed to be as flexible and expandable as possible to meet new
  55. * meta information needs that might arise. To achieve that ID3v2 is constructed
  56. * as a container for several information blocks, called frames, whose format
  57. * need not be known to the software that encounters them. Each frame has an
  58. * unique and predefined identifier which allows software to skip unknown
  59. * frames.
  60. *
  61. * Overall tag structure:
  62. *
  63. * <pre>
  64. * +-----------------------------+
  65. * | Header (10 bytes) |
  66. * +-----------------------------+
  67. * | Extended Header |
  68. * | (variable length, OPTIONAL) |
  69. * +-----------------------------+
  70. * | Frames (variable length) |
  71. * +-----------------------------+
  72. * | Padding |
  73. * | (variable length, OPTIONAL) |
  74. * +-----------------------------+
  75. * | Footer (10 bytes, OPTIONAL) |
  76. * +-----------------------------+
  77. * </pre>
  78. *
  79. * In general, padding and footer are mutually exclusive.
  80. *
  81. * @package php-reader
  82. * @subpackage ID3
  83. * @author Sven Vollbehr <svollbehr@gmail.com>
  84. * @copyright Copyright (c) 2008 The PHP Reader Project Workgroup
  85. * @license http://code.google.com/p/php-reader/wiki/License New BSD License
  86. * @version $Rev: 75 $
  87. */
  88. final class ID3v2
  89. {
  90. /** @var Reader */
  91. private $_reader;
  92. /** @var ID3_Header */
  93. private $_header;
  94. /** @var ID3_ExtendedHeader */
  95. private $_extendedHeader;
  96. /** @var ID3_Header */
  97. private $_footer;
  98. /** @var Array */
  99. private $_frames = array();
  100. /** @var string */
  101. private $_filename;
  102. /** @var Array */
  103. private $_options;
  104. /**
  105. * Constructs the ID3v2 class with given file and options. The options array
  106. * may also be given as the only parameter.
  107. *
  108. * The following options are currently recognized:
  109. * o version -- The ID3v2 tag version to use in write operation. This option
  110. * is automatically set when a tag is read from a file and defaults to
  111. * version 4.0 for tag write.
  112. *
  113. * @todo Only limited subset of flags are processed.
  114. * @todo Utilize the SEEK frame and search for a footer to find the tag
  115. * @todo Utilize the LINK frame to fetch frames from other sources
  116. * @param string $filename The path to the file.
  117. * @param Array $options The options array.
  118. */
  119. public function __construct($filename = false, $options = array())
  120. {
  121. if (is_array($filename)) {
  122. $options = $filename;
  123. $filename = false;
  124. }
  125. $this->_options = &$options;
  126. if (($this->_filename = $filename) === false ||
  127. file_exists($filename) === false) {
  128. $this->_header = new ID3_Header(null, $options);
  129. } else {
  130. $this->_reader = new Reader($filename);
  131. if ($this->_reader->readString8(3) != "ID3")
  132. throw new ID3_Exception
  133. ("File does not contain ID3v2 tag: " . $filename);
  134. $this->_header = new ID3_Header($this->_reader, $options);
  135. if ($this->_header->getVersion() < 3 || $this->_header->getVersion() > 4)
  136. throw new ID3_Exception
  137. ("File does not contain ID3v2 tag of supported version: " . $filename);
  138. if ($this->_header->hasFlag(ID3_Header::EXTENDEDHEADER))
  139. $this->_extendedHeader =
  140. new ID3_ExtendedHeader($this->_reader, $options);
  141. if ($this->_header->hasFlag(ID3_Header::FOOTER))
  142. $this->_footer = &$this->_header; // skip footer, and rather copy header
  143. while (true) {
  144. $offset = $this->_reader->getOffset();
  145. // Jump off the loop if we reached the end of the tag
  146. if ($offset - 10 >= $this->_header->getSize() -
  147. ($this->hasFooter() ? 10 : 0))
  148. break;
  149. // Jump off the loop if we reached the last frame
  150. if ($this->_reader->available() < 4 || Transform::fromUInt32BE
  151. ($identifier = $this->_reader->read(4)) == 0)
  152. break;
  153. $this->_reader->setOffset($offset);
  154. if (@fopen($filename = "ID3/Frame/" .
  155. strtoupper($identifier) . ".php", "r", true) !== false)
  156. require_once($filename);
  157. if (class_exists($classname = "ID3_Frame_" . $identifier))
  158. $frame = new $classname($this->_reader, $options);
  159. else
  160. $frame = new ID3_Frame($this->_reader, $options);
  161. if (!isset($this->_frames[$frame->getIdentifier()]))
  162. $this->_frames[$frame->getIdentifier()] = array();
  163. $this->_frames[$frame->getIdentifier()][] = $frame;
  164. }
  165. }
  166. }
  167. /**
  168. * Returns the header object.
  169. *
  170. * @return ID3_Header
  171. */
  172. public function getHeader() { return $this->_header; }
  173. /**
  174. * Checks whether there is an extended header present in the tag. Returns
  175. * <var>true</var> if the header is present, <var>false</var> otherwise.
  176. *
  177. * @return boolean
  178. */
  179. public function hasExtendedHeader()
  180. {
  181. if ($this->_header)
  182. return $this->_header->hasFlag(ID3_Header::EXTENDEDHEADER);
  183. }
  184. /**
  185. * Returns the extended header object if present, or <var>false</var>
  186. * otherwise.
  187. *
  188. * @return ID3_ExtendedHeader|false
  189. */
  190. public function getExtendedHeader()
  191. {
  192. if ($this->hasExtendedHeader())
  193. return $this->_extendedHeader;
  194. return false;
  195. }
  196. /**
  197. * Sets the extended header object.
  198. *
  199. * @param ID3_ExtendedHeader $extendedHeader The header object
  200. */
  201. public function setExtendedHeader($extendedHeader)
  202. {
  203. if (is_subclass_of($extendedHeader, "ID3_ExtendedHeader")) {
  204. $this->_header->flags =
  205. $this->_header->flags | ID3_Header::EXTENDEDHEADER;
  206. $this->_extendedHeader->setOptions($this->_options);
  207. $this->_extendedHeader = $extendedHeader;
  208. } else throw new ID3_Exception("Invalid argument");
  209. }
  210. /**
  211. * Checks whether there is a frame given as an argument defined in the tag.
  212. * Returns <var>true</var> if one ore more frames are present,
  213. * <var>false</var> otherwise.
  214. *
  215. * @return boolean
  216. */
  217. public function hasFrame($identifier)
  218. {
  219. return isset($this->_frames[$identifier]);
  220. }
  221. /**
  222. * Returns all the frames the tag contains as an associate array. The frame
  223. * identifiers work as keys having an array of frames as associated value.
  224. *
  225. * @return Array
  226. */
  227. public function getFrames()
  228. {
  229. return $this->_frames;
  230. }
  231. /**
  232. * Returns an array of frames matching the given identifier or an empty array
  233. * if no frames matched the identifier.
  234. *
  235. * The identifier may contain wildcard characters "*" and "?". The asterisk
  236. * matches against zero or more characters, and the question mark matches any
  237. * single character.
  238. *
  239. * Please note that one may also use the shorthand $obj->identifier to access
  240. * the first frame with the identifier given. Wildcards cannot be used with
  241. * the shorthand.
  242. *
  243. * @return Array
  244. */
  245. public function getFramesByIdentifier($identifier)
  246. {
  247. $matches = array();
  248. $searchPattern = "/^" .
  249. str_replace(array("*", "?"), array(".*", "."), $identifier) . "$/i";
  250. foreach ($this->_frames as $identifier => $frames)
  251. if (preg_match($searchPattern, $identifier))
  252. foreach ($frames as $frame)
  253. $matches[] = $frame;
  254. return $matches;
  255. }
  256. /**
  257. * Adds a new frame to the tag and returns it.
  258. *
  259. * @param ID3_Frame $frame The frame to add.
  260. * @return ID3_Frame
  261. */
  262. public function addFrame($frame)
  263. {
  264. $frame->setOptions($this->_options);
  265. if (!$this->hasFrame($frame->getIdentifier()))
  266. $this->_frames[$frame->getIdentifier()] = array();
  267. return $this->_frames[$frame->getIdentifier()][] = $frame;
  268. }
  269. /**
  270. * Checks whether there is a footer present in the tag. Returns
  271. * <var>true</var> if the footer is present, <var>false</var> otherwise.
  272. *
  273. * @return boolean
  274. */
  275. public function hasFooter()
  276. {
  277. return $this->_header->hasFlag(ID3_Header::FOOTER);
  278. }
  279. /**
  280. * Returns the footer object if present, or <var>false</var> otherwise.
  281. *
  282. * @return ID3_Header|false
  283. */
  284. public function getFooter()
  285. {
  286. if ($this->hasFooter())
  287. return $this->_footer;
  288. return false;
  289. }
  290. /**
  291. * Sets whether the tag should have a footer defined.
  292. *
  293. * @param boolean $useFooter Whether the tag should have a footer
  294. */
  295. public function setFooter($useFooter)
  296. {
  297. if ($useFooter) {
  298. $this->_header->setFlags
  299. ($this->_header->getFlags() | ID3_Header::FOOTER);
  300. $this->_footer = &$this->_header;
  301. } else {
  302. /* Count footer bytes towards the tag size, so it gets removed or
  303. overridden upon re-write */
  304. if ($this->hasFooter())
  305. $this->_header->setSize($this->_header->getSize() + 10);
  306. $this->_header->setFlags
  307. ($this->_header->getFlags() & ~ID3_Header::FOOTER);
  308. $this->_footer = null;
  309. }
  310. }
  311. /**
  312. * Writes the possibly altered ID3v2 tag back to the file where it was read.
  313. * If the class was constructed without a file name, one can be provided here
  314. * as an argument. Regardless, the write operation will override previous
  315. * tag information, if found.
  316. *
  317. * If write is called without setting any frames to the tag, the tag is
  318. * removed from the file.
  319. *
  320. * @param string $filename The optional path to the file.
  321. */
  322. public function write($filename = false)
  323. {
  324. if (empty($this->_frames))
  325. throw new ID3_Exception("Tag must contain at least one frame");
  326. if ($filename === false && ($filename = $this->_filename) === false)
  327. throw new ID3_Exception("No file given to write the tag to");
  328. if (($fd = fopen
  329. ($filename, file_exists($filename) ? "r+b" : "wb")) === false)
  330. throw new ID3_Exception("Unable to open file for writing: " . $filename);
  331. $oldTagSize = $this->_header->getSize();
  332. $tag = "" . $this;
  333. $tagSize = strlen($tag);
  334. if ($this->_reader === null || $tagSize - 10 > $oldTagSize) {
  335. fseek($fd, 0, SEEK_END);
  336. $oldFileSize = ftell($fd);
  337. ftruncate($fd, $newFileSize = $tagSize - $oldTagSize + $oldFileSize);
  338. for ($i = 1, $cur = $oldFileSize; $cur > 0; $cur -= 1024, $i++) {
  339. fseek($fd, -(($i * 1024) + ($newFileSize - $oldFileSize)), SEEK_END);
  340. $buffer = fread($fd, 1024);
  341. fseek($fd, -($i * 1024), SEEK_END);
  342. fwrite($fd, $buffer, 1024);
  343. }
  344. }
  345. fseek($fd, 0);
  346. fwrite($fd, $tag);
  347. $this->_filename = $filename;
  348. }
  349. /**
  350. * Magic function so that $obj->value will work. The method will attempt to
  351. * return the first frame that matches the identifier.
  352. *
  353. * If there is no frame or field with given name, the method will attempt to
  354. * create a frame with given identifier.
  355. *
  356. * If none of these work, an exception is thrown.
  357. *
  358. * @param string $name The frame or field name.
  359. * @return mixed
  360. */
  361. public function __get($name) {
  362. if (isset($this->_frames[strtoupper($name)]))
  363. return $this->_frames[strtoupper($name)][0];
  364. if (method_exists($this, "get" . ucfirst($name)))
  365. return call_user_func(array($this, "get" . ucfirst($name)));
  366. if (@fopen($filename =
  367. "ID3/Frame/" . strtoupper($name) . ".php", "r", true) !== false)
  368. require_once($filename);
  369. if (class_exists($classname = "ID3_Frame_" . strtoupper($name)))
  370. return $this->addFrame(new $classname());
  371. throw new ID3_Exception("Unknown frame/field: " . $name);
  372. }
  373. /**
  374. * Returns the tag raw data.
  375. *
  376. * @return string
  377. */
  378. public function __toString()
  379. {
  380. $data = "";
  381. foreach ($this->_frames as $frames)
  382. foreach ($frames as $frame)
  383. $data .= $frame;
  384. $datalen = strlen($data);
  385. $padlen = 0;
  386. /* The tag padding is calculated as follows. If the tag can be written in
  387. the space of the previous tag, the remaining space is used for padding.
  388. If there is no previous tag or the new tag is bigger than the space taken
  389. by the previous tag, the padding is calculated using the following
  390. logaritmic equation: log(0.2(x + 10)), ranging from some 300 bytes to
  391. almost 5000 bytes given the tag length of 0..256M. */
  392. if ($this->hasFooter() === false) {
  393. if ($this->_reader !== null && $datalen < $this->_header->getSize())
  394. $padlen = $this->_header->getSize() - $datalen;
  395. else
  396. $padlen = ceil(log(0.2 * ($datalen / 1024 + 10), 10) * 1024);
  397. }
  398. /* ID3v2.4.0 CRC calculated w/ padding */
  399. if (!isset($this->_options["version"]) || $this->_options["version"] >= 4)
  400. $data = str_pad($data, $datalen + $padlen, "\0");
  401. if ($this->hasExtendedHeader()) {
  402. $this->_extendedHeader->setPadding($padlen);
  403. if ($this->_extendedHeader->hasFlag(ID3_ExtendedHeader::CRC32)) {
  404. $crc = crc32($data);
  405. if ($crc & 0x80000000)
  406. $crc = -(($crc ^ 0xffffffff) + 1);
  407. $this->_extendedHeader->setCrc($crc);
  408. }
  409. $data = $this->getExtendedHeader() . $data;
  410. }
  411. /* ID3v2.3.0 CRC calculated w/o padding */
  412. if (isset($this->_options["version"]) && $this->_options["version"] < 4)
  413. $data = str_pad($data, $datalen + $padlen, "\0");
  414. $this->_header->setSize(strlen($data));
  415. return "ID3" . $this->_header . $data .
  416. ($this->hasFooter() ? "3DI" . $this->getFooter() : "");
  417. }
  418. }