PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/MP3/Id.php

https://github.com/chregu/fluxcms
PHP | 1290 lines | 757 code | 143 blank | 390 comment | 138 complexity | d24150e30324e96e75874c95f6394407 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This code is released under the GNU LGPL Go read it over here: |
  9. // | http://www.gnu.org/copyleft/lesser.html |
  10. // +----------------------------------------------------------------------+
  11. // | Authors: Sandy McArthur Jr. <Leknor@Leknor.com> |
  12. // +----------------------------------------------------------------------+
  13. //
  14. // $Id$
  15. //
  16. // Uncomment the folling define if you want the class to automatically
  17. // read the MPEG frame info to get bitrate, mpeg version, layer, etc.
  18. //
  19. // NOTE: This is needed to maintain pre-version 1.0 behavior which maybe
  20. // needed if you are using info that is from the mpeg frame. This includes
  21. // the length of the song.
  22. //
  23. // This is discouraged because it will siginfincantly lengthen script
  24. // execution time if all you need is the ID3 tag info.
  25. // define('ID3_AUTO_STUDY', true);
  26. // Uncomment the following define if you want tons of debgging info.
  27. // Tip: make sure you use a <PRE> block so the print_r's are readable.
  28. // define('ID3_SHOW_DEBUG', true);
  29. require_once "PEAR.php" ;
  30. /**
  31. * File not opened
  32. * @const PEAR_MP3_ID_FNO
  33. */
  34. define('PEAR_MP3_ID_FNO', 1);
  35. /**
  36. * Read error
  37. * @const PEAR_MP3_ID_RE
  38. */
  39. define('PEAR_MP3_ID_RE', 2);
  40. /**
  41. * Tag not found
  42. * @const PEAR_MP3_ID_TNF
  43. */
  44. define('PEAR_MP3_ID_TNF', 3);
  45. /**
  46. * File is not a MP3 file (corrupted?)
  47. * @const PEAR_MP3_ID_NOMP3
  48. */
  49. define('PEAR_MP3_ID_NOMP3', 4);
  50. /**
  51. * A Class for reading/writing MP3 ID3 tags
  52. *
  53. * Note: This code doesn't try to deal with corrupt mp3s. So if you get
  54. * incorrect length times or something else it may be your mp3. To fix just
  55. * re-enocde from the CD. :~)
  56. *
  57. * eg:
  58. * require_once("MP3/Id.php");
  59. * $file = "Some Song.mp3";
  60. *
  61. * $id3 = &new MP3_Id();
  62. * $id3->read($file);
  63. * print_r($id3);
  64. *
  65. * echo $id3->getTag('artists');
  66. *
  67. * $id3->comment = "Be gentle with that file.";
  68. * $id3->write();
  69. * $id3->read($file);
  70. * print_r($id3 );
  71. *
  72. * @package MP3_Id
  73. * @author Sandy McArthur Jr. <Leknor@Leknor.com>
  74. * @version $Version$
  75. */
  76. class MP3_Id {
  77. /**
  78. * mp3/mpeg file name
  79. * @var boolean
  80. */
  81. var $file = false;
  82. /**
  83. * ID3 v1 tag found? (also true if v1.1 found)
  84. * @var boolean
  85. */
  86. var $id3v1 = false;
  87. /**
  88. * ID3 v1.1 tag found?
  89. * @var boolean
  90. */
  91. var $id3v11 = false;
  92. /**
  93. * ID3 v2 tag found? (not used yet)
  94. * @var boolean
  95. */
  96. var $id3v2 = false;
  97. // ID3v1.1 Fields:
  98. /**
  99. * trackname
  100. * @var string
  101. */
  102. var $name = '';
  103. /**
  104. * artists
  105. * @var string
  106. */
  107. var $artists = '';
  108. /**
  109. * album
  110. * @var string
  111. */
  112. var $album = '';
  113. /**
  114. * year
  115. * @var string
  116. */
  117. var $year = '';
  118. /**
  119. * comment
  120. * @var string
  121. */
  122. var $comment = '';
  123. /**
  124. * track number
  125. * @var integer
  126. */
  127. var $track = 0;
  128. /**
  129. * genre name
  130. * @var string
  131. */
  132. var $genre = '';
  133. /**
  134. * genre number
  135. * @var integer
  136. */
  137. var $genreno = 255;
  138. // MP3 Frame Stuff
  139. /**
  140. * Was the file studied to learn more info?
  141. * @var boolean
  142. */
  143. var $studied = false;
  144. /**
  145. * version of mpeg
  146. * @var integer
  147. */
  148. var $mpeg_ver = 0;
  149. /**
  150. * version of layer
  151. * @var integer
  152. */
  153. var $layer = 0;
  154. /**
  155. * version of bitrate
  156. * @var integer
  157. */
  158. var $bitrate = 0;
  159. /**
  160. * Frames are crc protected?
  161. * @var boolean
  162. */
  163. var $crc = false;
  164. /**
  165. * frequency
  166. * @var integer
  167. */
  168. var $frequency = 0;
  169. /**
  170. * Frames padded
  171. * @var boolean
  172. */
  173. var $padding = false;
  174. /**
  175. * private bit set
  176. * @var boolean
  177. */
  178. var $private = false;
  179. /**
  180. * Mode (Stero etc)
  181. * @var string
  182. */
  183. var $mode = '';
  184. /**
  185. * Copyrighted
  186. * @var string
  187. */
  188. var $copyright = false;
  189. /**
  190. * On Original Media? (never used)
  191. * @var boolean
  192. */
  193. var $original = false;
  194. /**
  195. * Emphasis (also never used)
  196. * @var boolean
  197. */
  198. var $emphasis = '';
  199. /**
  200. * Bytes in file
  201. * @var integer
  202. */
  203. var $filesize = -1;
  204. /**
  205. * Byte at which the first mpeg header was found
  206. * @var integer
  207. */
  208. var $frameoffset = -1;
  209. /**
  210. * length of mp3 format hh:ss
  211. * @var string
  212. */
  213. var $length = false;
  214. /**
  215. * length of mp3 in seconds
  216. * @var string
  217. */
  218. var $lengths = false;
  219. /**
  220. * if any errors they will be here
  221. * @var string
  222. */
  223. var $error = false;
  224. /**
  225. * print debugging info?
  226. * @var boolean
  227. */
  228. var $debug = false;
  229. /**
  230. * print debugg
  231. * @var string
  232. */
  233. var $debugbeg = '<DIV STYLE="margin: 0.5 em; padding: 0.5 em; border-width: thin; border-color: black; border-style: solid">';
  234. /**
  235. * print debugg
  236. * @var string
  237. */
  238. var $debugend = '</DIV>';
  239. var $mp3fmode = 'w';
  240. /*
  241. * creates a new id3 object
  242. * and loads a tag from a file.
  243. *
  244. * @param string $study study the mpeg frame to get extra info like bitrate and frequency
  245. * You should advoid studing alot of files as it will siginficantly
  246. * slow this down.
  247. * @access public
  248. */
  249. function MP3_Id($study = false) {
  250. if(defined('ID3_SHOW_DEBUG')) $this->debug = true;
  251. $this->study=($study || defined('ID3_AUTO_STUDY'));
  252. } // id3()
  253. /**
  254. * reads the given file and parse it
  255. *
  256. * @param string $file the name of the file to parse
  257. * @return mixed PEAR_Error on error
  258. * @access public
  259. */
  260. function read( $file="") {
  261. if ($this->debug) print($this->debugbeg . "id3('$file')<HR>\n");
  262. if(!empty($file))$this->file = $file;
  263. if ($this->debug) print($this->debugend);
  264. if (file_exists($file)) {
  265. $this->mp3infile = $file;
  266. $this->_getTagVersion($this->mp3infile);
  267. if ($this->id3v2) {
  268. return $this->_read_v2();
  269. }
  270. }
  271. return $this->_read_v1();
  272. }
  273. function _getTagVersion($mp3file) {
  274. if (file_exists($mp3file)) {
  275. $fp = fopen($mp3file, 'rb');
  276. // check for ID3v2
  277. if (fread($fp, 3) == "ID3") {
  278. $this->id3v2 = True;
  279. }
  280. fclose($fp);
  281. }
  282. }
  283. /**
  284. * sets a field
  285. *
  286. * possible names of tags are:
  287. * artists - Name of band or artist
  288. * album - Name of the album
  289. * year - publishing year of the album or song
  290. * comment - song comment
  291. * track - the number of the track
  292. * genre - genre of the song
  293. * genreno - Number of the genre
  294. *
  295. * @param mixed $name Name of the tag to set or hash with the key as fieldname
  296. * @param mixed $value the value to set
  297. *
  298. * @access public
  299. */
  300. function setTag($name, $value) {
  301. if( is_array($name)) {
  302. foreach( $name as $n => $v) {
  303. $this -> $n = $v ;
  304. }
  305. } else {
  306. $this -> $name = $value ;
  307. }
  308. }
  309. /**
  310. * get the value of a tag
  311. *
  312. * @param string $name the name of the field to get
  313. * @param mixed $default returned if the field not exists
  314. *
  315. * @return mixed The value of the field
  316. * @access public
  317. * @see setTag
  318. */
  319. function getTag($name, $default = 0) {
  320. if(empty($this -> $name)) {
  321. return $default ;
  322. } else {
  323. return $this -> $name ;
  324. }
  325. }
  326. /**
  327. * update the id3v1 tags on the file.
  328. * Note: If/when ID3v2 is implemented this method will probably get another
  329. * parameters.
  330. *
  331. * @param boolean $v1 if true update/create an id3v1 tag on the file. (defaults to true)
  332. *
  333. * @access public
  334. */
  335. function write($v1 = true) {
  336. if ($this->debug) print($this->debugbeg . "write()<HR>\n");
  337. if ($v1) {
  338. $this->_write_v1();
  339. }
  340. if ($this->debug) print($this->debugend);
  341. } // write()
  342. /**
  343. * study() - does extra work to get the MPEG frame info.
  344. *
  345. * @access public
  346. */
  347. function study() {
  348. $this->studied = true;
  349. $this->_readframe();
  350. } // study()
  351. /**
  352. * copy($from) - set's the ID3 fields to the same as the fields in $from
  353. *
  354. * @param string $from fields to copy
  355. * @access public
  356. */
  357. function copy($from) {
  358. if ($this->debug) print($this->debugbeg . "copy(\$from)<HR>\n");
  359. $this->name = $from->name;
  360. $this->artists = $from->artists;
  361. $this->album = $from->album;
  362. $this->year = $from->year;
  363. $this->comment = $from->comment;
  364. $this->track = $from->track;
  365. $this->genre = $from->genre;
  366. $this->genreno = $from->genreno;
  367. if ($this->debug) print($this->debugend);
  368. } // copy($from)
  369. /**
  370. * remove - removes the id3 tag(s) from a file.
  371. *
  372. * @param boolean $id3v1 true to remove the tag
  373. * @param boolean $id3v2 true to remove the tag (Not yet implemented)
  374. *
  375. * @access public
  376. */
  377. function remove($id3v1 = true, $id3v2 = true) {
  378. if ($this->debug) print($this->debugbeg . "remove()<HR>\n");
  379. if ($id3v1) {
  380. $this->_remove_v1();
  381. }
  382. if ($id3v2) {
  383. // TODO: write ID3v2 code
  384. }
  385. if ($this->debug) print($this->debugend);
  386. } // remove
  387. /**
  388. * read a ID3 v1 or v1.1 tag from a file
  389. *
  390. * $file should be the path to the mp3 to look for a tag.
  391. * When in doubt use the full path.
  392. *
  393. * @return mixed PEAR_Error if fails
  394. * @access private
  395. */
  396. function _read_v1() {
  397. if ($this->debug) print($this->debugbeg . "_read_v1()<HR>\n");
  398. if (! ($f = @fopen($this->file, 'rb')) ) {
  399. return PEAR::raiseError( "Unable to open " . $this->file, PEAR_MP3_ID_FNO);
  400. }
  401. if (fseek($f, -128, SEEK_END) == -1) {
  402. return PEAR::raiseError( 'Unable to see to end - 128 of ' . $this->file, PEAR_MP3_ID_RE);
  403. }
  404. $r = fread($f, 128);
  405. fclose($f);
  406. if ($this->debug) {
  407. $unp = unpack('H*raw', $r);
  408. print_r($unp);
  409. }
  410. $id3tag = $this->_decode_v1($r);
  411. if(!PEAR::isError( $id3tag)) {
  412. $this->id3v1 = true;
  413. $tmp = explode(Chr(0), $id3tag['NAME']);
  414. $this->name = $tmp[0];
  415. $tmp = explode(Chr(0), $id3tag['ARTISTS']);
  416. $this->artists = $tmp[0];
  417. $tmp = explode(Chr(0), $id3tag['ALBUM']);
  418. $this->album = $tmp[0];
  419. $tmp = explode(Chr(0), $id3tag['YEAR']);
  420. $this->year = $tmp[0];
  421. $tmp = explode(Chr(0), $id3tag['COMMENT']);
  422. $this->comment = $tmp[0];
  423. if (isset($id3tag['TRACK'])) {
  424. $this->id3v11 = true;
  425. $this->track = $id3tag['TRACK'];
  426. }
  427. $this->genreno = $id3tag['GENRENO'];
  428. $this->genre = $id3tag['GENRE'];
  429. } else {
  430. return $id3tag ;
  431. }
  432. if ($this->debug) print($this->debugend);
  433. } // _read_v1()
  434. /**
  435. * decodes that ID3v1 or ID3v1.1 tag
  436. *
  437. * false will be returned if there was an error decoding the tag
  438. * else an array will be returned
  439. *
  440. * @param string $rawtag tag to decode
  441. * @return string decoded tag
  442. * @access private
  443. */
  444. function _decode_v1($rawtag) {
  445. if ($this->debug) print($this->debugbeg . "_decode_v1(\$rawtag)<HR>\n");
  446. if ($rawtag[125] == Chr(0) and $rawtag[126] != Chr(0)) {
  447. // ID3 v1.1
  448. $format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a28COMMENT/x1/C1TRACK/C1GENRENO';
  449. } else {
  450. // ID3 v1
  451. $format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a30COMMENT/C1GENRENO';
  452. }
  453. $id3tag = unpack($format, $rawtag);
  454. if ($this->debug) print_r($id3tag);
  455. if ($id3tag['TAG'] == 'TAG') {
  456. $id3tag['GENRE'] = $this->getgenre($id3tag['GENRENO']);
  457. } else {
  458. $id3tag = PEAR::raiseError( 'TAG not found', PEAR_MP3_ID_TNF);
  459. }
  460. if ($this->debug) print($this->debugend);
  461. return $id3tag;
  462. } // _decode_v1()
  463. /**
  464. * writes a ID3 v1 or v1.1 tag to a file
  465. *
  466. * @return mixed returns PEAR_Error when fails
  467. * @access private
  468. */
  469. function _write_v1() {
  470. if ($this->debug) print($this->debugbeg . "_write_v1()<HR>\n");
  471. $file = $this->file;
  472. if (! ($f = @fopen($file, 'r+b')) ) {
  473. return PEAR::raiseError( "Unable to open " . $file, PEAR_MP3_ID_FNO);
  474. }
  475. if (fseek($f, -128, SEEK_END) == -1) {
  476. // $this->error = 'Unable to see to end - 128 of ' . $file;
  477. return PEAR::raiseError( "Unable to see to end - 128 of " . $file, PEAR_MP3_ID_RE);
  478. }
  479. $this->genreno = $this->getgenreno($this->genre, $this->genreno);
  480. $newtag = $this->_encode_v1();
  481. $r = fread($f, 128);
  482. if ( !PEAR::isError( $this->_decode_v1($r))) {
  483. if (fseek($f, -128, SEEK_END) == -1) {
  484. // $this->error = 'Unable to see to end - 128 of ' . $file;
  485. return PEAR::raiseError( "Unable to see to end - 128 of " . $file, PEAR_MP3_ID_RE);
  486. }
  487. fwrite($f, $newtag);
  488. } else {
  489. if (fseek($f, 0, SEEK_END) == -1) {
  490. // $this->error = 'Unable to see to end of ' . $file;
  491. return PEAR::raiseError( "Unable to see to end of " . $file, PEAR_MP3_ID_RE);
  492. }
  493. fwrite($f, $newtag);
  494. }
  495. fclose($f);
  496. if ($this->debug) print($this->debugend);
  497. } // _write_v1()
  498. /*
  499. * encode the ID3 tag
  500. *
  501. * the newly built tag will be returned
  502. *
  503. * @return string the new tag
  504. * @access private
  505. */
  506. function _encode_v1() {
  507. if ($this->debug) print($this->debugbeg . "_encode_v1()<HR>\n");
  508. if ($this->track) {
  509. // ID3 v1.1
  510. $id3pack = 'a3a30a30a30a4a28x1C1C1';
  511. $newtag = pack($id3pack,
  512. 'TAG',
  513. $this->name,
  514. $this->artists,
  515. $this->album,
  516. $this->year,
  517. $this->comment,
  518. $this->track,
  519. $this->genreno
  520. );
  521. } else {
  522. // ID3 v1
  523. $id3pack = 'a3a30a30a30a4a30C1';
  524. $newtag = pack($id3pack,
  525. 'TAG',
  526. $this->name,
  527. $this->artists,
  528. $this->album,
  529. $this->year,
  530. $this->comment,
  531. $this->genreno
  532. );
  533. }
  534. if ($this->debug) {
  535. print('id3pack: ' . $id3pack . "\n");
  536. $unp = unpack('H*new', $newtag);
  537. print_r($unp);
  538. }
  539. if ($this->debug) print($this->debugend);
  540. return $newtag;
  541. } // _encode_v1()
  542. /**
  543. * if exists it removes an ID3v1 or v1.1 tag
  544. *
  545. * returns true if the tag was removed or none was found
  546. * else false if there was an error
  547. *
  548. * @return boolean true, if the tag was removed
  549. * @access private
  550. */
  551. function _remove_v1() {
  552. if ($this->debug) print($this->debugbeg . "_remove_v1()<HR>\n");
  553. $file = $this->file;
  554. if (! ($f = fopen($file, 'r+b')) ) {
  555. return PEAR::raiseError( "Unable to open " . $file, PEAR_MP3_ID_FNO);
  556. }
  557. if (fseek($f, -128, SEEK_END) == -1) {
  558. return PEAR::raiseError( 'Unable to see to end - 128 of ' . $file, PEAR_MP3_ID_RE);
  559. }
  560. $r = fread($f, 128);
  561. $success = false;
  562. if ( !PEAR::isError( $this->_decode_v1($r))) {
  563. $size = filesize($this->file) - 128;
  564. if ($this->debug) print('size: old: ' . filesize($this->file));
  565. $success = ftruncate($f, $size);
  566. clearstatcache();
  567. if ($this->debug) print(' new: ' . filesize($this->file));
  568. }
  569. fclose($f);
  570. if ($this->debug) print($this->debugend);
  571. return $success;
  572. } // _remove_v1()
  573. /**
  574. * reads a frame from the file
  575. *
  576. * @return mixed PEAR_Error when fails
  577. * @access private
  578. */
  579. function _readframe() {
  580. if ($this->debug) print($this->debugbeg . "_readframe()<HR>\n");
  581. $file = $this->file;
  582. if (! ($f = fopen($file, 'rb')) ) {
  583. if ($this->debug) print($this->debugend);
  584. return PEAR::raiseError( "Unable to open " . $file, PEAR_MP3_ID_FNO) ;
  585. }
  586. $this->filesize = filesize($file);
  587. do {
  588. while (fread($f,1) != Chr(255)) { // Find the first frame
  589. if ($this->debug) echo "Find...\n";
  590. if (feof($f)) {
  591. if ($this->debug) print($this->debugend);
  592. return PEAR::raiseError( "No mpeg frame found", PEAR_MP3_ID_NOMP3) ;
  593. }
  594. }
  595. fseek($f, ftell($f) - 1); // back up one byte
  596. $frameoffset = ftell($f);
  597. $r = fread($f, 4);
  598. // Binary to Hex to a binary sting. ugly but best I can think of.
  599. $bits = unpack('H*bits', $r);
  600. $bits = base_convert($bits['bits'],16,2);
  601. } while (!$bits[8] and !$bits[9] and !$bits[10]); // 1st 8 bits true from the while
  602. if ($this->debug) print('Bits: ' . $bits . "\n");
  603. $this->frameoffset = $frameoffset;
  604. fclose($f);
  605. if ($bits[11] == 0) {
  606. $this->mpeg_ver = "2.5";
  607. $bitrates = array(
  608. '1' => array(0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0),
  609. '2' => array(0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0),
  610. '3' => array(0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0),
  611. );
  612. } else if ($bits[12] == 0) {
  613. $this->mpeg_ver = "2";
  614. $bitrates = array(
  615. '1' => array(0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0),
  616. '2' => array(0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0),
  617. '3' => array(0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0),
  618. );
  619. } else {
  620. $this->mpeg_ver = "1";
  621. $bitrates = array(
  622. '1' => array(0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0),
  623. '2' => array(0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0),
  624. '3' => array(0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0),
  625. );
  626. }
  627. if ($this->debug) print('MPEG' . $this->mpeg_ver . "\n");
  628. $layer = array(
  629. array(0,3),
  630. array(2,1),
  631. );
  632. $this->layer = $layer[$bits[13]][$bits[14]];
  633. if ($this->debug) print('layer: ' . $this->layer . "\n");
  634. if ($bits[15] == 0) {
  635. // It's backwards, if the bit is not set then it is protected.
  636. if ($this->debug) print("protected (crc)\n");
  637. $this->crc = true;
  638. }
  639. $bitrate = 0;
  640. if ($bits[16] == 1) $bitrate += 8;
  641. if ($bits[17] == 1) $bitrate += 4;
  642. if ($bits[18] == 1) $bitrate += 2;
  643. if ($bits[19] == 1) $bitrate += 1;
  644. $this->bitrate = $bitrates[$this->layer][$bitrate];
  645. $frequency = array(
  646. '1' => array(
  647. '0' => array(44100, 48000),
  648. '1' => array(32000, 0),
  649. ),
  650. '2' => array(
  651. '0' => array(22050, 24000),
  652. '1' => array(16000, 0),
  653. ),
  654. '2.5' => array(
  655. '0' => array(11025, 12000),
  656. '1' => array(8000, 0),
  657. ),
  658. );
  659. $this->frequency = $frequency[$this->mpeg_ver][$bits[20]][$bits[21]];
  660. $this->padding = $bits[22];
  661. $this->private = $bits[23];
  662. $mode = array(
  663. array('Stereo', 'Joint Stereo'),
  664. array('Dual Channel', 'Mono'),
  665. );
  666. $this->mode = $mode[$bits[24]][$bits[25]];
  667. // XXX: I dunno what the mode extension is for bits 26,27
  668. $this->copyright = $bits[28];
  669. $this->original = $bits[29];
  670. $emphasis = array(
  671. array('none', '50/15ms'),
  672. array('', 'CCITT j.17'),
  673. );
  674. $this->emphasis = $emphasis[$bits[30]][$bits[31]];
  675. if ($this->bitrate == 0) {
  676. $s = -1;
  677. } else {
  678. $s = ((8*filesize($this->file))/1000) / $this->bitrate;
  679. }
  680. $this->length = sprintf('%02d:%02d',floor($s/60),floor($s-(floor($s/60)*60)));
  681. $this->lengths = (int)$s;
  682. if ($this->debug) print($this->debugend);
  683. } // _readframe()
  684. /**
  685. * read ID3v2 tag from a file
  686. *
  687. * @access private
  688. */
  689. function _read_v2() {
  690. if (!file_exists($this->mp3infile)) {
  691. return 0;
  692. }
  693. $tagOffset =0;
  694. $frames = array();
  695. // Bad Frames - (courtesy of eyeD3 http://eyed3.nicfit.net/ )
  696. $knownBadFrames = array("\x00\x00MP",
  697. "\x00MP3",
  698. " MP3",
  699. "MP3e",
  700. "\x00MP",
  701. " MP",
  702. "MP3",
  703. "COM ",
  704. "TCP ", // iTunes
  705. "CM1 " // Script kiddie
  706. );
  707. $fp = fopen($this->mp3infile, 'rb');
  708. if (!$fp) {
  709. return PEAR::raiseError( "Unable to open " . $this->mp3infile, PEAR_MP3_ID_FNO);
  710. }
  711. // skip first 3 bytes
  712. fseek($fp, 3);
  713. // get version/revision numbers
  714. $major = 2;
  715. $minor = ord(fread($fp, 1));
  716. $rev = ord(fread($fp, 1));
  717. if ($this->debug) print "found tag version ".$major.".".$minor.".".$rev."\n";
  718. // flags (3 bits for 2.3.0 and 4 for 2.4.0 respectively)
  719. $flags = array();
  720. $flagbyte = ord(fread($fp, 1));
  721. $flags['unsync'] = (bool) ($flagbyte & 64);
  722. $flags['extended'] = (bool) ($flagbyte & 32);
  723. $flags['experimental'] = (bool) ($flagbyte & 16);
  724. $flags['footer'] = (bool) ($flagbyte & 8);
  725. // 4 bytes synchsafe int for tagsize
  726. // (extended header, frames and padding)
  727. $tagSize = $this->_BigEnd2Int(fread($fp, 4), 1) + 10;
  728. if ($this->debug) print "tag size: ".$tagSize."\n";
  729. // parse extended header
  730. $extHeaderLen = 0;
  731. $extHeaders = array();
  732. if ($flags['extended']==True) {
  733. // FIXME: we should parse ext. header but for now we just skip ...
  734. switch($minor) {
  735. case 3:
  736. $extHeaderLen = $this->_BigEnd2Int(fread($fp, 1));
  737. $extHeaderBytes = fread($fp, $extHeaderLen -1);
  738. break;
  739. case 4:
  740. $extHeaderLen = $this->_BigEnd2Int(fread($fp, 4), 1);
  741. $extHeaderBytes = fread($extHeaderLen -4);
  742. break;
  743. default:
  744. break;
  745. }
  746. }
  747. if ($this->debug) print "external headersize: ".$extHeaderLen."\n";
  748. $frameLen = $tagSize - 10 - $extHeaderlen;
  749. if ($flags['footer']==True) {
  750. $frameLen -= 10;
  751. }
  752. // read framedata
  753. // FIXME: check whether pointer is on right position
  754. $framedata = fread($fp, $frameLen);
  755. if (($flags['unsync']==True) && ($minor <= 3)) {
  756. $framedata = $this->_DeUnsynchronise($framedata);
  757. }
  758. while(isset($framedata) && strlen($framedata) > 0) {
  759. if (strlen($framedata) <= 10) {
  760. // Out of range .. padding .. whatever
  761. break;
  762. }
  763. if ($minor > 2) {
  764. // Frame ID $xx xx xx xx (four characters)
  765. // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
  766. // Flags $xx xx
  767. $frameHeader = substr($framedata, 0, 10);
  768. $framedata = substr($framedata, 10);
  769. $frameName = substr($frameHeader, 0, 4);
  770. $frameSizeBytes = substr($frameHeader, 4, 4);
  771. if ($minor == 3) {
  772. // 2.3
  773. $frameSize = $this->_BigEnd2Int($frameSizeBytes);
  774. } else {
  775. // 2.4
  776. $frameSize = $this->_BigEnd2Int($frameSizeBytes, 1);
  777. }
  778. if ($frameName == "\x00\x00\x00" || $frameName== "\x00\x00\x00\x00") {
  779. // padding
  780. break;
  781. }
  782. if (!$this->_isValidID3v2FrameName($frameName, $minor) ||
  783. in_array($frameName, $knownBadFrames)) {
  784. continue;
  785. }
  786. $frameInfo = $this->_v2LookupFrame($frameName, $minor);
  787. if ($frameInfo) {
  788. $data = unpack("a*", substr($framedata, 0, $frameSize));
  789. $frameInfo['data'] = trim(str_replace("\x00","", $data[1]));
  790. $frames[$frameName] = $frameInfo;
  791. }
  792. $framedata = substr($framedata, $frameSize);
  793. } else {
  794. break;
  795. }
  796. }
  797. $this->_map2V1($frames);
  798. fclose($fp);
  799. }
  800. function _map2V1($frames) {
  801. $conv = array('name' => 'TIT2', 'artists'=>'TOPE','album'=>'TALB',
  802. 'year' => 'TYER', 'comment'=>'COMM','track'=>'TRCK',
  803. 'genre'=> 'TCON');
  804. foreach($conv as $v1name=>$v2name) {
  805. if (isset($frames[$v2name])) {
  806. $this->$v1name = $frames[$v2name]['data'];
  807. }
  808. }
  809. }
  810. /**
  811. *
  812. * @access private
  813. */
  814. function _BigEnd2Int($bytestr, $synchsafe=False) {
  815. $intv = 0;
  816. $bytestrlen = strlen($bytestr);
  817. for($i=0; $i<$bytestrlen; $i++) {
  818. if ($synchsafe) {
  819. $intv = $intv | (ord($bytestr{$i}) & 127) << (($bytestrlen - 1 - $i) * 7);
  820. } else {
  821. $intv += ord($bytestr{$i}) * pow(256, ($bytestrlen - 1 - $i));
  822. }
  823. }
  824. return $intv;
  825. }
  826. function _DeUnsynchronise($data) {
  827. return str_replace("\xFF\x00", "\xFF", $data);
  828. }
  829. function _isValidID3v2FrameName($name, $minor) {
  830. switch($minor) {
  831. case 2:
  832. return preg_match("#^[A-Z][A-Z0-9]{2}#", $name);
  833. break;
  834. case 3:
  835. case 4:
  836. return preg_match("#^[A-Z][A-Z0-9]{3}#", $name);
  837. break;
  838. }
  839. return False;
  840. }
  841. function _v2LookupFrame($frameName, $minor) {
  842. // acc. to http://www.id3.org/id3v2.4.0-frames.txt
  843. $default = array('AENC' => array('desc'=>'Audio encryption'),
  844. 'APIC' => array('desc'=>'Attached picture'),
  845. 'ASPI' => array('desc'=>'Audio seek point index'),
  846. 'COMM' => array('desc'=>'Comments'),
  847. 'COMR' => array('desc'=>'Commercial frame'),
  848. 'ENCR' => array('desc'=>'Encryption method registration'),
  849. 'EQU2' => array('desc'=>'Equalisation (2)'),
  850. 'ETCO' => array('desc'=>'Event timing codes'),
  851. 'GEOB' => array('desc'=>'General encapsulated object'),
  852. 'GRID' => array('desc'=>'Group identification registration'),
  853. 'LINK' => array('desc'=>'Linked information'),
  854. 'MCDI' => array('desc'=>'Music CD identifier'),
  855. 'MLLT' => array('desc'=>'MPEG location lookup table'),
  856. 'OWNE' => array('desc'=>'Ownership frame'),
  857. 'PRIV' => array('desc'=>'Private Frame'),
  858. 'PCNT' => array('desc'=>'Play counter'),
  859. 'POPM' => array('desc'=>'Popularimeter'),
  860. 'POSS' => array('desc'=>'Position synchronisation frame'),
  861. 'RBUF' => array('desc'=>'Recommended buffer size'),
  862. 'RVA2' => array('desc'=>'Relative volume adjustment (2)'),
  863. 'RVRB' => array('desc'=>'Reverb'),
  864. 'SEEK' => array('desc'=>'Seek frame'),
  865. 'SIGN' => array('desc'=>'Signature frame'),
  866. 'SYLT' => array('desc'=>'Synchronized lyric/text'),
  867. 'SYTC' => array('desc'=>'Synchronized tempo codes'),
  868. 'TALB' => array('desc'=>'Album/Movie/Show title'),
  869. 'TBPM' => array('desc'=>'BPM (Beats per minute)'),
  870. 'TCOM' => array('desc'=>'Composer'),
  871. 'TCON' => array('desc'=>'Content type'),
  872. 'TCOP' => array('desc'=>'Copyright message'),
  873. 'TDEN' => array('desc'=>'Encoding time'),
  874. 'TDLY' => array('desc'=>'Playlist delay'),
  875. 'TDOR' => array('desc'=>'Original release time'),
  876. 'TDRC' => array('desc'=>'Recording time'),
  877. 'TDRL' => array('desc'=>'Release time'),
  878. 'TDTG' => array('desc'=>'Tagging time'),
  879. 'TENC' => array('desc'=>'Encoded by'),
  880. 'TEXT' => array('desc'=>'Lyricist/Text writer'),
  881. 'TFLT' => array('desc'=>'File type'),
  882. 'TIPL' => array('desc'=>'Involved people list'),
  883. 'TIT1' => array('desc'=>'Content group description'),
  884. 'TIT2' => array('desc'=>'Title/songname/content description'),
  885. 'TIT3' => array('desc'=>'Subtitle/Description refinement'),
  886. 'TKEY' => array('desc'=>'Initial key'),
  887. 'TLAN' => array('desc'=>'Language(s)'),
  888. 'TLEN' => array('desc'=>'Length'),
  889. 'TMCL' => array('desc'=>'Musician credit list'),
  890. 'TMED' => array('desc'=>'Media type'),
  891. 'TMOO' => array('desc'=>'Mood'),
  892. 'TOAL' => array('desc'=>'Original album/movie/show title'),
  893. 'TOFN' => array('desc'=>'Original filename'),
  894. 'TOLY' => array('desc'=>'Original lyricist(s)/text writer(s)'),
  895. 'TOPE' => array('desc'=>'Original artist(s)/ performer(s)'),
  896. 'TOWN' => array('desc'=>'File owner/licensee'),
  897. 'TPE1' => array('desc'=>'Lead performer(s)/Soloist(s)'),
  898. 'TPE2' => array('desc'=>'Band/orchestra/accompainment'),
  899. 'TPE3' => array('desc'=>'Conductor/performer refinement'),
  900. 'TPE4' => array('desc'=>'Interpreted, remixed or otherwise modified by'),
  901. 'TPOS' => array('desc'=>'Part of a set'),
  902. 'TPRO' => array('desc'=>'Produced notice'),
  903. 'TPUB' => array('desc'=>'Publisher'),
  904. 'TRCK' => array('desc'=>'Track number/Position in set'),
  905. 'TRSN' => array('desc'=>'Internet radio station name'),
  906. 'TRSO' => array('desc'=>'Internet radio station owner'),
  907. 'TSOA' => array('desc'=>'Album sort order'),
  908. 'TSOP' => array('desc'=>'Performer sort order'),
  909. 'TSOT' => array('desc'=>'Title sort order'),
  910. 'TSRC' => array('desc'=>'ISRC (international standard recording code)'),
  911. 'TSSE' => array('desc'=>'Software/Hardware and settings used for encoding'),
  912. 'TSST' => array('desc'=>'Set subtitle'),
  913. 'TYER' => array('desc'=>'Year'), // 2.3
  914. 'TXXX' => array('desc'=>'User defined Text information frame'),
  915. 'UFID' => array('desc'=>'Unique file identifier'),
  916. 'USER' => array('desc'=>'Terms of use'),
  917. 'USLT' => array('desc'=>'Unsynchronized lyric/text transcription'),
  918. 'WCOM' => array('desc'=>'Commercial information'),
  919. 'WCOP' => array('desc'=>'Copyright/Legal information'),
  920. 'WOAF' => array('desc'=>'Official audio file webpage'),
  921. 'WOAR' => array('desc'=>'Official artist/performer website'),
  922. 'WOAS' => array('desc'=>'Official audio source webpage'),
  923. 'WORS' => array('desc'=>'Official internet radio station homepage'),
  924. 'WPAY' => array('desc'=>'Payment'),
  925. 'WPUB' => array('desc'=>'Publishers official webpage'),
  926. 'WXXX' => array('desc'=>'User defined URL link frame')
  927. );
  928. if (array_key_exists($frameName, $default)) {
  929. return $default[$frameName];
  930. }
  931. return False;
  932. }
  933. /**
  934. * getGenre - return the name of a genre number
  935. *
  936. * if no genre number is specified the genre number from
  937. * $this->genreno will be used.
  938. *
  939. * the genre is returned or false if an error or not found
  940. * no error message is ever returned
  941. *
  942. * @param integer $genreno Number of the genre
  943. * @return mixed false, if no genre found, else string
  944. *
  945. * @access public
  946. */
  947. function getGenre($genreno) {
  948. if ($this->debug) print($this->debugbeg . "getgenre($genreno)<HR>\n");
  949. $genres = $this->genres();
  950. if (isset($genres[$genreno])) {
  951. $genre = $genres[$genreno];
  952. if ($this->debug) print($genre . "\n");
  953. } else {
  954. $genre = '';
  955. }
  956. if ($this->debug) print($this->debugend);
  957. return $genre;
  958. } // getGenre($genreno)
  959. /*
  960. * getGenreNo - return the number of the genre name
  961. *
  962. * the genre number is returned or 0xff (255) if a match is not found
  963. * you can specify the default genreno to use if one is not found
  964. * no error message is ever returned
  965. *
  966. * @param string $genre Name of the genre
  967. * @param integer $default Genre number in case of genre not found
  968. *
  969. * @access public
  970. */
  971. function getGenreNo($genre, $default = 0xff) {
  972. if ($this->debug) print($this->debugbeg . "getgenreno('$genre',$default)<HR>\n");
  973. $genres = $this->genres();
  974. $genreno = false;
  975. if ($genre) {
  976. foreach ($genres as $no => $name) {
  977. if (strtolower($genre) == strtolower($name)) {
  978. if ($this->debug) print("$no:'$name' == '$genre'");
  979. $genreno = $no;
  980. }
  981. }
  982. }
  983. if ($genreno === false) $genreno = $default;
  984. if ($this->debug) print($this->debugend);
  985. return $genreno;
  986. } // getGenreNo($genre, $default = 0xff)
  987. /*
  988. * genres - returns an array of the ID3v1 genres
  989. *
  990. * @return array
  991. *
  992. * @access public
  993. */
  994. function genres() {
  995. return array(
  996. 0 => 'Blues',
  997. 1 => 'Classic Rock',
  998. 2 => 'Country',
  999. 3 => 'Dance',
  1000. 4 => 'Disco',
  1001. 5 => 'Funk',
  1002. 6 => 'Grunge',
  1003. 7 => 'Hip-Hop',
  1004. 8 => 'Jazz',
  1005. 9 => 'Metal',
  1006. 10 => 'New Age',
  1007. 11 => 'Oldies',
  1008. 12 => 'Other',
  1009. 13 => 'Pop',
  1010. 14 => 'R&B',
  1011. 15 => 'Rap',
  1012. 16 => 'Reggae',
  1013. 17 => 'Rock',
  1014. 18 => 'Techno',
  1015. 19 => 'Industrial',
  1016. 20 => 'Alternative',
  1017. 21 => 'Ska',
  1018. 22 => 'Death Metal',
  1019. 23 => 'Pranks',
  1020. 24 => 'Soundtrack',
  1021. 25 => 'Euro-Techno',
  1022. 26 => 'Ambient',
  1023. 27 => 'Trip-Hop',
  1024. 28 => 'Vocal',
  1025. 29 => 'Jazz+Funk',
  1026. 30 => 'Fusion',
  1027. 31 => 'Trance',
  1028. 32 => 'Classical',
  1029. 33 => 'Instrumental',
  1030. 34 => 'Acid',
  1031. 35 => 'House',
  1032. 36 => 'Game',
  1033. 37 => 'Sound Clip',
  1034. 38 => 'Gospel',
  1035. 39 => 'Noise',
  1036. 40 => 'Alternative Rock',
  1037. 41 => 'Bass',
  1038. 42 => 'Soul',
  1039. 43 => 'Punk',
  1040. 44 => 'Space',
  1041. 45 => 'Meditative',
  1042. 46 => 'Instrumental Pop',
  1043. 47 => 'Instrumental Rock',
  1044. 48 => 'Ethnic',
  1045. 49 => 'Gothic',
  1046. 50 => 'Darkwave',
  1047. 51 => 'Techno-Industrial',
  1048. 52 => 'Electronic',
  1049. 53 => 'Pop-Folk',
  1050. 54 => 'Eurodance',
  1051. 55 => 'Dream',
  1052. 56 => 'Southern Rock',
  1053. 57 => 'Comedy',
  1054. 58 => 'Cult',
  1055. 59 => 'Gangsta',
  1056. 60 => 'Top 40',
  1057. 61 => 'Christian Rap',
  1058. 62 => 'Pop/Funk',
  1059. 63 => 'Jungle',
  1060. 64 => 'Native US',
  1061. 65 => 'Cabaret',
  1062. 66 => 'New Wave',
  1063. 67 => 'Psychadelic',
  1064. 68 => 'Rave',
  1065. 69 => 'Showtunes',
  1066. 70 => 'Trailer',
  1067. 71 => 'Lo-Fi',
  1068. 72 => 'Tribal',
  1069. 73 => 'Acid Punk',
  1070. 74 => 'Acid Jazz',
  1071. 75 => 'Polka',
  1072. 76 => 'Retro',
  1073. 77 => 'Musical',
  1074. 78 => 'Rock & Roll',
  1075. 79 => 'Hard Rock',
  1076. 80 => 'Folk',
  1077. 81 => 'Folk-Rock',
  1078. 82 => 'National Folk',
  1079. 83 => 'Swing',
  1080. 84 => 'Fast Fusion',
  1081. 85 => 'Bebob',
  1082. 86 => 'Latin',
  1083. 87 => 'Revival',
  1084. 88 => 'Celtic',
  1085. 89 => 'Bluegrass',
  1086. 90 => 'Avantgarde',
  1087. 91 => 'Gothic Rock',
  1088. 92 => 'Progressive Rock',
  1089. 93 => 'Psychedelic Rock',
  1090. 94 => 'Symphonic Rock',
  1091. 95 => 'Slow Rock',
  1092. 96 => 'Big Band',
  1093. 97 => 'Chorus',
  1094. 98 => 'Easy Listening',
  1095. 99 => 'Acoustic',
  1096. 100 => 'Humour',
  1097. 101 => 'Speech',
  1098. 102 => 'Chanson',
  1099. 103 => 'Opera',
  1100. 104 => 'Chamber Music',
  1101. 105 => 'Sonata',
  1102. 106 => 'Symphony',
  1103. 107 => 'Booty Bass',
  1104. 108 => 'Primus',
  1105. 109 => 'Porn Groove',
  1106. 110 => 'Satire',
  1107. 111 => 'Slow Jam',
  1108. 112 => 'Club',
  1109. 113 => 'Tango',
  1110. 114 => 'Samba',
  1111. 115 => 'Folklore',
  1112. 116 => 'Ballad',
  1113. 117 => 'Power Ballad',
  1114. 118 => 'Rhytmic Soul',
  1115. 119 => 'Freestyle',
  1116. 120 => 'Duet',
  1117. 121 => 'Punk Rock',
  1118. 122 => 'Drum Solo',
  1119. 123 => 'Acapella',
  1120. 124 => 'Euro-House',
  1121. 125 => 'Dance Hall',
  1122. 126 => 'Goa',
  1123. 127 => 'Drum & Bass',
  1124. 128 => 'Club-House',
  1125. 129 => 'Hardcore',
  1126. 130 => 'Terror',
  1127. 131 => 'Indie',
  1128. 132 => 'BritPop',
  1129. 133 => 'Negerpunk',
  1130. 134 => 'Polsk Punk',
  1131. 135 => 'Beat',
  1132. 136 => 'Christian Gangsta Rap',
  1133. 137 => 'Heavy Metal',
  1134. 138 => 'Black Metal',
  1135. 139 => 'Crossover',
  1136. 140 => 'Contemporary Christian',
  1137. 141 => 'Christian Rock',
  1138. 142 => 'Merengue',
  1139. 143 => 'Salsa',
  1140. 144 => 'Trash Metal',
  1141. 145 => 'Anime',
  1142. 146 => 'Jpop',
  1143. 147 => 'Synthpop'
  1144. );
  1145. } // genres
  1146. } // end of id3
  1147. ?>