½âÎöÓÃPHP¶ÁдÒôƵÎļþÐÅÏ¢µÄÏê½â(Ö§³ÖWMAºÍMP3)

¸´ÖÆ´úÂë ´úÂëÈçÏÂ:

¡¡¡¡<?php

¡¡¡¡// AudioExif.class.php

¡¡¡¡// ÓÃPHP½øÐÐÒôƵÎļþÍ·²¿ÐÅÏ¢µÄ¶ÁÈ¡ÓëдÈë

¡¡¡¡// Ŀǰֻ֧³Ö WMA ºÍ MP3 Á½ÖÖ¸ñʽ, Ö»Ö§³Ö³£ÓõöÍ·²¿ÐÅÏ¢

¡¡¡¡//

¡¡¡¡// дÈëÐÅÏ¢Ö§³Ö: Title(Ãû³Æ), Artist(ÒÕÊõ¼Ò), Copyright(°æÈ¨), Description (ÃèÊö)

¡¡¡¡//               Year(Äê´ú),  Genre (Á÷ÅÉ),   AlbumTitle (ר¼­±êÌâ)

¡¡¡¡// ÆäÖÐ mp3 ºÍ wma ÂÔÓв»Í¬, ¾ßÌå·µ»ØµÄÐÅÏ¢»¹¿ÉÄܸü¶à, µ«Ö»ÓÐÒÔÉÏÐÅÏ¢¿ÉÒÔ±»Ð´Èë

¡¡¡¡// mp3 »¹Ö§³Ö Track (ÇúÄ¿±àºÅдÈë)

¡¡¡¡// ¶ÔÓÚ MP3 ÎļþÖ§³Ö ID3v1Ò²Ö§³ÖID3v2, ¶ÁȡʱÓÅÏÈ v2, дÈëʱ×ÜÊÇ»áдÈëv1, ±ØÒªÊ±Ð´Èëv2

¡¡¡¡//

¡¡¡¡// Ó÷¨ËµÃ÷: (ÓÉÓÚ wma ʹÓà Unicode ´æÈ¡, ¹Ê»¹ÐèÒª mb_convert_encoding() À©Õ¹

¡¡¡¡//           ·µ»ØÊý¾Ý¼°Ð´ÈëÊý¾Ý¾ùΪ ANSI ±àÂë, ¼´´æÊ²Ã´¾ÍÏÔʾʲô (ÖÐÎÄ_GB2312)

¡¡¡¡//

¡¡¡¡// require ('AudioExif.class.php');

¡¡¡¡// $AE = new AudioExif;

¡¡¡¡// $file = '/path/to/test.mp3';

¡¡¡¡//

¡¡¡¡// 1. ¼ì²éÎļþÊÇ·ñÍêÕû (only for wma, mp3ʼÖÕ·µ»Ø true)

¡¡¡¡//

¡¡¡¡// $AE->CheckSize($file);

¡¡¡¡//

¡¡¡¡// 2. ¶ÁÈ¡ÐÅÏ¢, ·µ»ØÖµÓÉÐÅÏ¢×é³ÉµÄÊý×é, ¼üÃû½âÊͲμûÉÏ·½

¡¡¡¡//

¡¡¡¡// print_r($AE->GetInfo($file));

¡¡¡¡//

¡¡¡¡// 3. дÈëÐÅÏ¢, µÚ¶þ²ÎÊýÊÇÒ»¸ö¹þÏ£Êý×é, ¼ü->Öµ, Ö§³ÖµÄ²Î¼ûÉÏ·½µÄ, mp3Ò²Ö§³Ö Track

¡¡¡¡//    ÒªÇóµÚÒ»²ÎÊýµÄÎļþ·¾¶¿ÉÓɱ¾³ÌÐòдÈë

¡¡¡¡// $pa = array('Title' => 'бêÌâ', 'AlbumTitle' => 'еÄר¼­Ãû³Æ');

¡¡¡¡// $AE->SetInfo($file, $pa);

¡¡¡¡//

¡¡¡¡//

¡¡¡¡// ÆäËü: ¸Ã²å¼þ»¨Á˲»ÉÙʱ¼äËѼ¯²éÕÒ wma¼°mp3 µÄÎļþ¸ñʽ˵Ã÷ÎĵµÓëÍøÒ³, Ï£Íû¶Ô´ó¼ÒÓÐÓÃ.

¡¡¡¡//       ÆäÊµÍøÉÏÒѾ­Óв»ÉÙÀàËÆµÄ³ÌÐò, µ«¶Ô wma ʵÔÚÌ«ÉÙÁË, Ö»ÄÜÔÚ win ƽ̨ÏÂͨ¹ý M$ µÄ

¡¡¡¡//       API À´²Ù×÷, ¶ø MP3 Ò²ºÜÉÙÓпÉÒÔÔÚ unix/linux ÃüÁîÐвÙ×÷µÄ, ËùÒÔÌØÒâдÁËÕâ¸öÄ£¿é

¡¡¡¡//

¡¡¡¡// Èç¹û·¢ÏÖ bug »òÌá½» patch, »ò¼ÓÒԸĽøÊ¹Ëü¸ü¼Ó½¡×³, Çë¸æËßÎÒ.

¡¡¡¡// (¹ØÓÚ ID3ºÍWmaµÄÎļþ¸ñʽ¼°½á¹¹ ÔÚÍøÉÏÓ¦¸Ã¶¼¿ÉÒÔÕÒµ½²Î¿¼×ÊÁÏ)

¡¡¡¡//

¡¡¡¡if (!extension_loaded('mbstring'))

¡¡¡¡{

¡¡¡¡trigger_error('PHP Extension module `mbstring` is required for AudioExif', E_USER_WARNING);

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// the Main Class

¡¡¡¡class AudioExif

¡¡¡¡{

¡¡¡¡// public vars

¡¡¡¡var $_wma = false;

¡¡¡¡var $_mp3 = false;

¡¡¡¡// Construct

¡¡¡¡function AudioExif()

¡¡¡¡{

¡¡¡¡// nothing to do

¡¡¡¡}

¡¡¡¡// check the filesize

¡¡¡¡function CheckSize($file)

¡¡¡¡{

¡¡¡¡$handler = &$this->_get_handler($file);

¡¡¡¡if (!$handler) return false;

¡¡¡¡return $handler->check_size($file);

¡¡¡¡}

¡¡¡¡// get the infomations

¡¡¡¡function GetInfo($file)

¡¡¡¡{

¡¡¡¡$handler = &$this->_get_handler($file);

¡¡¡¡if (!$handler) return false;

¡¡¡¡return $handler->get_info($file);

¡¡¡¡}

¡¡¡¡// write the infomations

¡¡¡¡function SetInfo($file, $pa)

¡¡¡¡{

¡¡¡¡if (!is_writable($file))

¡¡¡¡{

¡¡¡¡trigger_error('AudioExif: file `' . $file . '` can not been overwritten', E_USER_WARNING);

¡¡¡¡return false;

¡¡¡¡}

¡¡¡¡$handler = &$this->_get_handler($file);

¡¡¡¡if (!$handler) return false;

¡¡¡¡return $handler->set_info($file, $pa);

¡¡¡¡}

¡¡¡¡// private methods

¡¡¡¡function &_get_handler($file)

¡¡¡¡{

¡¡¡¡$ext = strtolower(strrchr($file, '.'));

¡¡¡¡$ret = false;

¡¡¡¡if ($ext == '.mp3')

¡¡¡¡{  // MP3

¡¡¡¡$ret = &$this->_mp3;

¡¡¡¡if (!$ret) $ret = new _Mp3Exif();

¡¡¡¡}

¡¡¡¡else if ($ext == '.wma')

¡¡¡¡{  // wma

¡¡¡¡$ret = &$this->_wma;

¡¡¡¡if (!$ret) $ret = new _WmaExif();

¡¡¡¡}

¡¡¡¡else

¡¡¡¡{  // unknown

¡¡¡¡trigger_error('AudioExif not supported `' . $ext . '` file.', E_USER_WARNING);

¡¡¡¡}

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// DBCS => gb2312

¡¡¡¡function dbcs_gbk($str)

¡¡¡¡{

¡¡¡¡// strip the last ""

¡¡¡¡$str = substr($str, 0, -2);

¡¡¡¡return mb_convert_encoding($str, 'GBK', 'UCS-2LE');

¡¡¡¡}

¡¡¡¡// gb2312 => DBCS

¡¡¡¡function gbk_dbcs($str)

¡¡¡¡{

¡¡¡¡$str  = mb_convert_encoding($str, 'UCS-2LE', 'GBK');

¡¡¡¡$str .= "";

¡¡¡¡return $str;

¡¡¡¡}

¡¡¡¡// file exif

¡¡¡¡class _AudioExif

¡¡¡¡{

¡¡¡¡var $fd;

¡¡¡¡var $head;

¡¡¡¡var $head_off;

¡¡¡¡var $head_buf;

¡¡¡¡// init the file handler

¡¡¡¡function _file_init($fpath, $write = false)

¡¡¡¡{

¡¡¡¡$mode = ($write ? 'rb+' : 'rb');

¡¡¡¡$this->fd = @fopen($fpath, $mode);

¡¡¡¡if (!$this->fd)

¡¡¡¡{

¡¡¡¡trigger_error('AudioExif: `' . $fpath . '` can not be opened with mode `' . $mode . '`', E_USER_WARNING);

¡¡¡¡return false;

¡¡¡¡}

¡¡¡¡$this->head = false;

¡¡¡¡$this->head_off = 0;

¡¡¡¡$this->head_buf = '';

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// read buffer from the head_buf & move the off pointer

¡¡¡¡function _read_head_buf($len)

¡¡¡¡{

¡¡¡¡if ($len <= 0) return NULL;

¡¡¡¡$buf = substr($this->head_buf, $this->head_off, $len);

¡¡¡¡$this->head_off += strlen($buf);

¡¡¡¡return $buf;

¡¡¡¡}

¡¡¡¡// read one short value

¡¡¡¡function _read_head_short()

¡¡¡¡{

¡¡¡¡$ord1 = ord(substr($this->head_buf, $this->head_off, 1));

¡¡¡¡$ord2 = ord(substr($this->head_buf, $this->head_off+1, 1));

¡¡¡¡$this->head_off += 2;

¡¡¡¡return ($ord1 + ($ord2<<8));

¡¡¡¡}

¡¡¡¡// save the file head

¡¡¡¡function _file_save($head, $olen, $nlen = 0)

¡¡¡¡{

¡¡¡¡if ($nlen == 0) $nlen = strlen($head);

¡¡¡¡if ($nlen == $olen)

¡¡¡¡{

¡¡¡¡// shorter

¡¡¡¡flock($this->fd, LOCK_EX);

¡¡¡¡fseek($this->fd, 0, SEEK_SET);

¡¡¡¡fwrite($this->fd, $head, $nlen);

¡¡¡¡flock($this->fd, LOCK_UN);

¡¡¡¡}

¡¡¡¡else

¡¡¡¡{

¡¡¡¡// longer, buffer required

¡¡¡¡$stat = fstat($this->fd);

¡¡¡¡$fsize = $stat['size'];

¡¡¡¡// buf required (4096?) Ó¦¸Ã²»»á nlen - olen > 4096 °É

¡¡¡¡$woff = 0;

¡¡¡¡$roff = $olen;

¡¡¡¡// read first buffer

¡¡¡¡flock($this->fd, LOCK_EX);

¡¡¡¡fseek($this->fd, $roff, SEEK_SET);

¡¡¡¡$buf = fread($this->fd, 4096);

¡¡¡¡// seek to start

¡¡¡¡fseek($this->fd, $woff, SEEK_SET);

¡¡¡¡fwrite($this->fd, $head, $nlen);

¡¡¡¡$woff += $nlen;

¡¡¡¡// seek to woff & write the data

¡¡¡¡do

¡¡¡¡{

¡¡¡¡$buf2 = $buf;

¡¡¡¡$roff += 4096;

¡¡¡¡if ($roff < $fsize)

¡¡¡¡{

¡¡¡¡fseek($this->fd, $roff, SEEK_SET);

¡¡¡¡$buf = fread($this->fd, 4096);

¡¡¡¡}

¡¡¡¡// save last buffer

¡¡¡¡$len2 = strlen($buf2);

¡¡¡¡fseek($this->fd, $woff, SEEK_SET);

¡¡¡¡fwrite($this->fd, $buf2, $len2);

¡¡¡¡$woff += $len2;

¡¡¡¡}

¡¡¡¡while ($roff < $fsize);

¡¡¡¡ftruncate($this->fd, $woff);

¡¡¡¡flock($this->fd, LOCK_UN);

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// close the file

¡¡¡¡function _file_deinit()

¡¡¡¡{

¡¡¡¡if ($this->fd)

¡¡¡¡{

¡¡¡¡fclose($this->fd);

¡¡¡¡$this->fd = false;

¡¡¡¡}

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// wma class

¡¡¡¡class _WmaExif extends _AudioExif

¡¡¡¡{

¡¡¡¡var $items1 = array('Title', 'Artist', 'Copyright', 'Description', 'Reserved');

¡¡¡¡var $items2 = array('Year', 'Genre', 'AlbumTitle');

¡¡¡¡// check file size (length) maybe invalid file

¡¡¡¡function check_size($file)

¡¡¡¡{

¡¡¡¡$ret = false;

¡¡¡¡if (!$this->_file_init($file)) return true;

¡¡¡¡if ($this->_init_header())

¡¡¡¡{

¡¡¡¡$buf = fread($this->fd, 24);

¡¡¡¡$tmp = unpack('H32id/Vlen/H8unused', $buf);

¡¡¡¡if ($tmp['id'] == '3626b2758e66cf11a6d900aa0062ce6c')

¡¡¡¡{

¡¡¡¡$stat = fstat($this->fd);

¡¡¡¡$ret = ($stat['size'] == ($this->head['len'] + $tmp['len']));

¡¡¡¡}

¡¡¡¡}

¡¡¡¡$this->_file_deinit();

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡// set info (save the infos)

¡¡¡¡function set_info($file, $pa)

¡¡¡¡{

¡¡¡¡// check the pa

¡¡¡¡settype($pa, 'array');

¡¡¡¡if (!$this->_file_init($file, true)) return false;

¡¡¡¡if (!$this->_init_header())

¡¡¡¡{

¡¡¡¡$this->_file_deinit();

¡¡¡¡return false;

¡¡¡¡}

¡¡¡¡// parse the old header & generate the new header

¡¡¡¡$head_body = '';

¡¡¡¡$st_found = $ex_found = false;

¡¡¡¡$head_num = $this->head['num'];

¡¡¡¡while (($tmp = $this->_get_head_frame()) && ($head_num > 0))

¡¡¡¡{

¡¡¡¡$head_num--;

¡¡¡¡if ($tmp['id'] == '3326b2758e66cf11a6d900aa0062ce6c')

¡¡¡¡{  // Standard Info

¡¡¡¡// 1-4

¡¡¡¡$st_found = true;

¡¡¡¡$st_body1 = $st_body2 = '';

¡¡¡¡$lenx = unpack('v5', $this->_read_head_buf(10));

¡¡¡¡$tmp['len'] -= 34;  // 10 + 24

¡¡¡¡for ($i = 0; $i < count($this->items1); $i++)

¡¡¡¡{

¡¡¡¡$l = $lenx[$i+1];

¡¡¡¡$k = $this->items1[$i];

¡¡¡¡$tmp['len'] -= $l;

¡¡¡¡$data = $this->_read_head_buf($l);

¡¡¡¡if (isset($pa[$k])) $data = gbk_dbcs($pa[$k]);

¡¡¡¡$st_body2 .= $data;

¡¡¡¡$st_body1 .= pack('v', strlen($data));

¡¡¡¡}

¡¡¡¡// left length

¡¡¡¡if ($tmp['len'] > 0) $st_body2 .= $this->_read_head_buf($tmp['len']);

¡¡¡¡// save to head_body

¡¡¡¡$head_body .= pack('H32VH8', $tmp['id'], strlen($st_body1 . $st_body2)+24, $tmp['unused']);

¡¡¡¡$head_body .= $st_body1 . $st_body2;

¡¡¡¡}

¡¡¡¡else if ($tmp['id'] == '40a4d0d207e3d21197f000a0c95ea850')

¡¡¡¡{  // extended info

¡¡¡¡$ex_found = true;

¡¡¡¡$inum = $this->_read_head_short();

¡¡¡¡$inum2 = $inum;

¡¡¡¡$tmp['len'] -= 26;  // 24 + 2

¡¡¡¡$et_body = '';

¡¡¡¡while ($tmp['len'] > 0 && $inum > 0)

¡¡¡¡{

¡¡¡¡// attribute name

¡¡¡¡$nlen = $this->_read_head_short();

¡¡¡¡$nbuf = $this->_read_head_buf($nlen);

¡¡¡¡// the flag & value  length

¡¡¡¡$flag = $this->_read_head_short();

¡¡¡¡$vlen = $this->_read_head_short();

¡¡¡¡$vbuf = $this->_read_head_buf($vlen);

¡¡¡¡// set the length

¡¡¡¡$tmp['len'] -= (6 + $nlen + $vlen);

¡¡¡¡$inum--;

¡¡¡¡// save the data?

¡¡¡¡$name = dbcs_gbk($nbuf);

¡¡¡¡$k = substr($name, 3);

¡¡¡¡if (in_array($k, $this->items2) && isset($pa[$k]))

¡¡¡¡{

¡¡¡¡$vbuf = gbk_dbcs($pa[$k]);

¡¡¡¡$vlen = strlen($vbuf);

¡¡¡¡unset($pa[$k]);

¡¡¡¡}

¡¡¡¡$et_body .= pack('v', $nlen) . $nbuf . pack('vv', $flag, $vlen) . $vbuf;

¡¡¡¡}

¡¡¡¡// new tag insert??

¡¡¡¡foreach ($this->items2 as $k)

¡¡¡¡{

¡¡¡¡if (isset($pa[$k]))

¡¡¡¡{

¡¡¡¡$inum2++;

¡¡¡¡$nbuf = gbk_dbcs('WM/' . $k);

¡¡¡¡$nlen = strlen($nbuf);

¡¡¡¡$vbuf = gbk_dbcs($pa[$k]);

¡¡¡¡$vlen = strlen($vbuf);

¡¡¡¡$et_body .= pack('v', $nlen) . $nbuf . pack('vv', 0, $vlen) . $vbuf;

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// left buf?

¡¡¡¡if ($tmp['len'] > 0) $et_body .= $this->_read_head_buf($tmp['len']);

¡¡¡¡// save to head_body

¡¡¡¡$head_body .= pack('H32VH8v', $tmp['id'], strlen($et_body)+26, $tmp['unused'], $inum2);

¡¡¡¡$head_body .= $et_body;

¡¡¡¡}

¡¡¡¡else

¡¡¡¡{

¡¡¡¡// just keep other head frame

¡¡¡¡$head_body .= pack('H32VH8', $tmp['id'], $tmp['len'], $tmp['unused']);

¡¡¡¡if ($tmp['len'] > 24) $head_body .= $this->_read_head_buf($tmp['len']-24);

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// st not found?

¡¡¡¡if (!$st_found)

¡¡¡¡{

¡¡¡¡$st_body1 = $st_body2 = '';

¡¡¡¡foreach ($this->items1 as $k)

¡¡¡¡{

¡¡¡¡$data = (isset($pa[$k]) ? gbk_dbcs($pa[$k]) : "");

¡¡¡¡$st_body1 .= pack('v', strlen($data));

¡¡¡¡$st_body2 .= $data;

¡¡¡¡}

¡¡¡¡// save to head_body

¡¡¡¡$head_body .= pack('H32Va4', '3326b2758e66cf11a6d900aa0062ce6c', strlen($st_body1 . $st_body2)+24, '');

¡¡¡¡$head_body .= $st_body1 . $st_body2;

¡¡¡¡$this->head['num']++;

¡¡¡¡}

¡¡¡¡// ex not found?

¡¡¡¡if (!$ex_found)

¡¡¡¡{

¡¡¡¡$inum = 0;

¡¡¡¡$et_body = '';

¡¡¡¡foreach ($this->items2 as $k)

¡¡¡¡{

¡¡¡¡$nbuf = gbk_dbcs('WM/' . $k);

¡¡¡¡$vbuf = (isset($pa[$k]) ? gbk_dbcs($pa[$k]) : "");

¡¡¡¡$et_body .= pack('v', strlen($nbuf)) . $nbuf . pack('vv', 0, strlen($vbuf)) . $vbuf;

¡¡¡¡$inum++;

¡¡¡¡}

¡¡¡¡$head_body .= pack('H32Va4v', '40a4d0d207e3d21197f000a0c95ea850', strlen($et_body)+26, '', $inum);

¡¡¡¡$head_body .= $et_body;

¡¡¡¡$this->head['num']++;

¡¡¡¡}

¡¡¡¡// after save

¡¡¡¡$new_len = strlen($head_body) + 30;

¡¡¡¡$old_len = $this->head['len'];

¡¡¡¡if ($new_len < $old_len)

¡¡¡¡{

¡¡¡¡$head_body .= str_repeat("", $old_len - $new_len);

¡¡¡¡$new_len = $old_len;

¡¡¡¡}

¡¡¡¡$tmp = $this->head;

¡¡¡¡$head_buf = pack('H32VVVH4', $tmp['id'], $new_len, $tmp['len2'], $tmp['num'], $tmp['unused']);

¡¡¡¡$head_buf .= $head_body;

¡¡¡¡$this->_file_save($head_buf, $old_len, $new_len);

¡¡¡¡// close the file & return

¡¡¡¡$this->_file_deinit();

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// get info

¡¡¡¡function get_info($file)

¡¡¡¡{

¡¡¡¡$ret = array();

¡¡¡¡if (!$this->_file_init($file)) return false;

¡¡¡¡if (!$this->_init_header())

¡¡¡¡{

¡¡¡¡$this->_file_deinit();

¡¡¡¡return false;

¡¡¡¡}

¡¡¡¡// get the data from head_buf

¡¡¡¡$head_num = $this->head['num'];  // num of head_frame

¡¡¡¡while (($tmp = $this->_get_head_frame()) && $head_num > 0)

¡¡¡¡{

¡¡¡¡$head_num--;

¡¡¡¡if ($tmp['id'] == '3326b2758e66cf11a6d900aa0062ce6c')

¡¡¡¡{  // Standard Info

¡¡¡¡$lenx = unpack('v*', $this->_read_head_buf(10));

¡¡¡¡for ($i = 1; $i <= count($this->items1); $i++)

¡¡¡¡{

¡¡¡¡$k = $this->items1[$i-1];

¡¡¡¡$ret[$k] = dbcs_gbk($this->_read_head_buf($lenx[$i]));

¡¡¡¡}

¡¡¡¡}

¡¡¡¡else if ($tmp['id'] == '40a4d0d207e3d21197f000a0c95ea850')

¡¡¡¡{  // Extended Info

¡¡¡¡$inum = $this->_read_head_short();

¡¡¡¡$tmp['len'] -= 26;

¡¡¡¡while ($inum > 0 && $tmp['len'] > 0)

¡¡¡¡{

¡¡¡¡// attribute name

¡¡¡¡$nlen = $this->_read_head_short();

¡¡¡¡$nbuf = $this->_read_head_buf($nlen);

¡¡¡¡// the flag & value  length

¡¡¡¡$flag = $this->_read_head_short();

¡¡¡¡$vlen = $this->_read_head_short();

¡¡¡¡$vbuf = $this->_read_head_buf($vlen);

¡¡¡¡// update the XX

¡¡¡¡$tmp['len'] -= (6 + $nlen + $vlen);

¡¡¡¡$inum--;

¡¡¡¡$name = dbcs_gbk($nbuf);

¡¡¡¡$k = substr($name, 3);

¡¡¡¡if (in_array($k, $this->items2))

¡¡¡¡{  // all is string value (refer to falg for other tags)

¡¡¡¡$ret[$k] = dbcs_gbk($vbuf);

¡¡¡¡}

¡¡¡¡}

¡¡¡¡}

¡¡¡¡else

¡¡¡¡{  // skip only

¡¡¡¡if ($tmp['len'] > 24) $this->head_off += ($tmp['len'] - 24);

¡¡¡¡}

¡¡¡¡}

¡¡¡¡$this->_file_deinit();

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡// get the header?

¡¡¡¡function _init_header()

¡¡¡¡{

¡¡¡¡fseek($this->fd, 0, SEEK_SET);

¡¡¡¡$buf = fread($this->fd, 30);

¡¡¡¡if (strlen($buf) != 30) return false;

¡¡¡¡$tmp = unpack('H32id/Vlen/Vlen2/Vnum/H4unused', $buf);

¡¡¡¡if ($tmp['id'] != '3026b2758e66cf11a6d900aa0062ce6c')

¡¡¡¡return false;

¡¡¡¡$this->head_buf = fread($this->fd, $tmp['len'] - 30);

¡¡¡¡$this->head = $tmp;

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// _get_head_frame()

¡¡¡¡function _get_head_frame()

¡¡¡¡{

¡¡¡¡$buf = $this->_read_head_buf(24);

¡¡¡¡if (strlen($buf) != 24) return false;

¡¡¡¡$tmp = unpack('H32id/Vlen/H8unused', $buf);

¡¡¡¡return $tmp;

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// mp3 class (if not IDv2 then select IDv1)

¡¡¡¡class _Mp3Exif extends _AudioExif

¡¡¡¡{

¡¡¡¡var $head1;

¡¡¡¡var $genres = array('Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal','New Age','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial','Alternative','Ska','Death Metal','Pranks','Soundtrack','Euro-Techno','Ambient','Trip-Hop','Vocal','Jazz+Funk','Fusion','Trance','Classical','Instrumental','Acid','House','Game','Sound Clip','Gospel','Noise','AlternRock','Bass','Soul','Punk','Space','Meditative','Instrumental Pop','Instrumental Rock','Ethnic','Gothic','Darkwave','Techno-Industrial','Electronic','Pop-Folk','Eurodance','Dream','Southern Rock','Comedy','Cult','Gangsta','Top 40','Christian Rap','Pop/Funk','Jungle','Native American','Cabaret','New Wave','Psychadelic','Rave','Showtunes','Trailer','Lo-Fi','Tribal','Acid Punk','Acid Jazz','Polka','Retro','Musical','Rock & Roll','Hard Rock','Unknown');

¡¡¡¡// MP3 always return true

¡¡¡¡function check_size($file)

¡¡¡¡{

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// get info

¡¡¡¡function get_info($file)

¡¡¡¡{

¡¡¡¡if (!$this->_file_init($file)) return false;

¡¡¡¡$ret = false;

¡¡¡¡if ($this->_init_header())

¡¡¡¡{

¡¡¡¡$ret = ($this->head ? $this->_get_v2_info() : $this->_get_v1_info());

¡¡¡¡$ret['meta'] = $this->_get_meta_info();

¡¡¡¡}

¡¡¡¡$this->_file_deinit();

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡// set info

¡¡¡¡function set_info($file, $pa)

¡¡¡¡{

¡¡¡¡if (!$this->_file_init($file, true)) return false;

¡¡¡¡if ($this->_init_header())

¡¡¡¡{

¡¡¡¡// always save v1 info

¡¡¡¡$this->_set_v1_info($pa);

¡¡¡¡// set v2 first if need

¡¡¡¡$this->_set_v2_info($pa);

¡¡¡¡}

¡¡¡¡$this->_file_deinit();

¡¡¡¡return true;

¡¡¡¡}

¡¡¡¡// get the header information[v1+v2], call after file_init

¡¡¡¡function _init_header()

¡¡¡¡{

¡¡¡¡$this->head1 = false;

¡¡¡¡$this->head = false;

¡¡¡¡// try to get ID3v1 first

¡¡¡¡fseek($this->fd, -128, SEEK_END);

¡¡¡¡$buf = fread($this->fd, 128);

¡¡¡¡if (strlen($buf) == 128 && substr($buf, 0, 3) == 'TAG')

¡¡¡¡{

¡¡¡¡$tmp = unpack('a3id/a30Title/a30Artist/a30AlbumTitle/a4Year/a28Description/CReserved/CTrack/CGenre', $buf);

¡¡¡¡$this->head1 = $tmp;

¡¡¡¡}

¡¡¡¡// try to get ID3v2

¡¡¡¡fseek($this->fd, 0, SEEK_SET);

¡¡¡¡$buf = fread($this->fd, 10);

¡¡¡¡if (strlen($buf) == 10 && substr($buf, 0, 3) == 'ID3')

¡¡¡¡{

¡¡¡¡$tmp = unpack('a3id/Cver/Crev/Cflag/C4size', $buf);

¡¡¡¡$tmp['size'] = ($tmp['size1']<<21)|($tmp['size2']<<14)|($tmp['size3']<<7)|$tmp['size4'];

¡¡¡¡unset($tmp['size1'], $tmp['size2'], $tmp['size3'], $tmp['size4']);

¡¡¡¡$this->head = $tmp;

¡¡¡¡$this->head_buf = fread($this->fd, $tmp['size']);

¡¡¡¡}

¡¡¡¡return ($this->head1 || $this->head);

¡¡¡¡}

¡¡¡¡// get v1 info

¡¡¡¡function _get_v1_info()

¡¡¡¡{

¡¡¡¡$ret = array();

¡¡¡¡$tmpa = array('Title', 'Artist', 'Copyright', 'Description', 'Year', 'AlbumTitle');

¡¡¡¡foreach ($tmpa as $tmp)

¡¡¡¡{

¡¡¡¡$ret[$tmp] = $this->head1[$tmp];

¡¡¡¡if ($pos = strpos($ret[$tmp], ""))

¡¡¡¡$ret[$tmp] = substr($ret[$tmp], 0, $pos);

¡¡¡¡}

¡¡¡¡// count the Genre, [Track]

¡¡¡¡if ($this->head1['Reserved'] == 0) $ret['Track'] = $this->head1['Track'];

¡¡¡¡else $ret['Description'] .= chr($ret['Reserved']) . chr($ret['Track']);

¡¡¡¡// Genre_idx

¡¡¡¡$g = $this->head1['Genre'];

¡¡¡¡if (!isset($this->genres[$g])) $ret['Genre'] = 'Unknown';

¡¡¡¡else $ret['Genre'] = $this->genres[$g];

¡¡¡¡// return the value

¡¡¡¡$ret['ID3v1'] = 'yes';

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡// get v2 info

¡¡¡¡function _get_v2_info()

¡¡¡¡{

¡¡¡¡$ret = array();

¡¡¡¡$items = array(  'TCOP'=>'Copyright', 'TPE1'=>'Artist', 'TIT2'=>'Title', 'TRCK'=> 'Track',

¡¡¡¡'TCON'=>'Genre', 'COMM'=>'Description', 'TYER'=>'Year', 'TALB'=>'AlbumTitle');

¡¡¡¡while (true)

¡¡¡¡{

¡¡¡¡$buf = $this->_read_head_buf(10);

¡¡¡¡if (strlen($buf) != 10) break;

¡¡¡¡$tmp = unpack('a4fid/Nsize/nflag', $buf);

¡¡¡¡if ($tmp['size'] == 0) break;

¡¡¡¡$tmp['dat'] = $this->_read_head_buf($tmp['size']);

¡¡¡¡// 0x6000 (11000000 00000000)

¡¡¡¡if ($tmp['flag'] & 0x6000) continue;

¡¡¡¡// mapping the data

¡¡¡¡if ($k = $items[$tmp['fid']])

¡¡¡¡{  // If first char is "", just skip

¡¡¡¡if (substr($tmp['dat'], 0, 1) == "") $tmp['dat'] = substr($tmp['dat'], 1);

¡¡¡¡$ret[$k] = $tmp['dat'];

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// reset the genre

¡¡¡¡if ($g = $ret['Genre'])

¡¡¡¡{

¡¡¡¡if (substr($g,0,1) == '(' && substr($g,-1,1) == ')') $g = substr($g, 1, -1);

¡¡¡¡if (is_numeric($g))

¡¡¡¡{

¡¡¡¡$g = intval($g);

¡¡¡¡$ret['Genre'] = (isset($this->genres[$g]) ? $this->genres[$g] : 'Unknown');

¡¡¡¡}

¡¡¡¡}

¡¡¡¡$ret['ID3v1'] = 'no';

¡¡¡¡return $ret;

¡¡¡¡}

¡¡¡¡// get meta info of MP3

¡¡¡¡function _get_meta_info()

¡¡¡¡{

¡¡¡¡// seek to the lead buf: 0xff

¡¡¡¡$off = 0;

¡¡¡¡if ($this->head) $off = $this->head['size'] + 10;

¡¡¡¡fseek($this->fd, $off, SEEK_SET);

¡¡¡¡while (!feof($this->fd))

¡¡¡¡{

¡¡¡¡$skip = ord(fread($this->fd, 1));

¡¡¡¡if ($skip == 0xff) break;

¡¡¡¡}

¡¡¡¡if ($skip != 0xff) return false;

¡¡¡¡$buf = fread($this->fd, 3);

¡¡¡¡if (strlen($buf) != 3) return false;

¡¡¡¡$tmp = unpack('C3', $buf);

¡¡¡¡if (($tmp[1] & 0xf0) != 0xf0) return false;

¡¡¡¡// get the meta info

¡¡¡¡$meta = array();

¡¡¡¡// get mpeg version

¡¡¡¡$meta['mpeg']  = ($tmp[1] & 0x08 ? 1 : 2);

¡¡¡¡$meta['layer']  = ($tmp[1] & 0x04) ? (($tmp[1] & 0x02) ? 1 : 2) : (($tmp[1] & 0x02) ? 3 : 0);

¡¡¡¡$meta['epro']  = ($tmp[1] & 0x01) ? 'no' : 'yes';

¡¡¡¡// bit rates

¡¡¡¡$bit_rates = array(

¡¡¡¡1 => array(

¡¡¡¡1 => array(0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0),

¡¡¡¡2 => array(0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0),

¡¡¡¡3 => array(0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0)

¡¡¡¡),

¡¡¡¡2 => array(

¡¡¡¡1 => array(0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0),

¡¡¡¡2 => array(0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0),

¡¡¡¡3 => array(0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0)

¡¡¡¡)

¡¡¡¡);

¡¡¡¡$i = $meta['mpeg'];

¡¡¡¡$j = $meta['layer'];

¡¡¡¡$k = ($tmp[2]>>4);

¡¡¡¡$meta['bitrate'] = $bit_rates[$i][$j][$k];

¡¡¡¡// sample rates <²ÉÑùÂÊ>

¡¡¡¡$sam_rates = array(1=>array(44100,48000,32000,0), 2=>array(22050,24000,16000,0));

¡¡¡¡$meta['samrate'] = $sam_rates[$i][$k];

¡¡¡¡$meta["padding"] = ($tmp[2] & 0x02) ? 'on' : 'off';

¡¡¡¡$meta["private"] = ($tmp[2] & 0x01) ? 'on' : 'off';

¡¡¡¡// mode & mode_ext

¡¡¡¡$k = ($tmp[3]>>6);

¡¡¡¡$channel_modes = array('stereo', 'joint stereo', 'dual channel', 'single channel');

¡¡¡¡$meta['mode'] = $channel_modes[$k];

¡¡¡¡$k = (($tmp[3]>>4) & 0x03);

¡¡¡¡$extend_modes = array('MPG_MD_LR_LR', 'MPG_MD_LR_I', 'MPG_MD_MS_LR', 'MPG_MD_MS_I');

¡¡¡¡$meta['ext_mode'] = $extend_modes[$k];

¡¡¡¡$meta['copyright'] = ($tmp[3] & 0x08) ? 'yes' : 'no';

¡¡¡¡$meta['original'] = ($tmp[3] & 0x04) ? 'yes' : 'no';

¡¡¡¡$emphasis = array('none', '50/15 microsecs', 'rreserved', 'CCITT J 17');

¡¡¡¡$k = ($tmp[3] & 0x03);

¡¡¡¡$meta['emphasis'] = $emphasis[$k];

¡¡¡¡return $meta;

¡¡¡¡}

¡¡¡¡// set v1 info

¡¡¡¡function _set_v1_info($pa)

¡¡¡¡{

¡¡¡¡// ID3v1 (simpled)

¡¡¡¡$off = -128;

¡¡¡¡if (!($tmp = $this->head1))

¡¡¡¡{

¡¡¡¡$off = 0;

¡¡¡¡$tmp['id'] = 'TAG';

¡¡¡¡$tmp['Title'] = $tmp['Artist'] = $tmp['AlbumTitle'] = $tmp['Year'] = $tmp['Description'] = '';

¡¡¡¡$tmp['Reserved'] = $tmp['Track'] = $tmp['Genre'] = 0;

¡¡¡¡}

¡¡¡¡// basic items

¡¡¡¡$items = array('Title', 'Artist', 'Copyright', 'Description', 'Year', 'AlbumTitle');

¡¡¡¡foreach ($items as $k)

¡¡¡¡{

¡¡¡¡if (isset($pa[$k])) $tmp[$k] = $pa[$k];

¡¡¡¡}

¡¡¡¡// genre index

¡¡¡¡if (isset($pa['Genre']))

¡¡¡¡{

¡¡¡¡$g = 0;

¡¡¡¡foreach ($this->genres as $gtmp)

¡¡¡¡{

¡¡¡¡if (!strcasecmp($gtmp, $pa['Genre']))

¡¡¡¡break;

¡¡¡¡$g++;

¡¡¡¡}

¡¡¡¡$tmp['Genre'] = $g;

¡¡¡¡}

¡¡¡¡if (isset($pa['Track'])) $tmp['Track'] = intval($pa['Track']);

¡¡¡¡// pack the data

¡¡¡¡$buf = pack('a3a30a30a30a4a28CCC',  $tmp['id'], $tmp['Title'], $tmp['Artist'], $tmp['AlbumTitle'],

¡¡¡¡$tmp['Year'], $tmp['Description'], 0, $tmp['Track'], $tmp['Genre']);

¡¡¡¡flock($this->fd, LOCK_EX);

¡¡¡¡fseek($this->fd, $off, SEEK_END);

¡¡¡¡fwrite($this->fd, $buf, 128);

¡¡¡¡flock($this->fd, LOCK_UN);

¡¡¡¡}

¡¡¡¡// set v2 info

¡¡¡¡function _set_v2_info($pa)

¡¡¡¡{

¡¡¡¡if (!$this->head)

¡¡¡¡{  // insert ID3

¡¡¡¡return;  // ûÓоÍËãÁË

¡¡¡¡/**

¡¡¡¡$tmp = array('id'=>'ID3','ver'=>3,'rev'=>0,'flag'=>0);

¡¡¡¡$tmp['size'] = -10;  // +10 => 0

¡¡¡¡$this->head = $tmp;

¡¡¡¡$this->head_buf = '';

¡¡¡¡$this->head_off = 0;

¡¡¡¡**/

¡¡¡¡}

¡¡¡¡$items = array(  'TCOP'=>'Copyright', 'TPE1'=>'Artist', 'TIT2'=>'Title', 'TRAC'=>'Track',

¡¡¡¡'TCON'=>'Genre', 'COMM'=>'Description', 'TYER'=>'Year', 'TALB'=>'AlbumTitle');

¡¡¡¡$head_body = '';

¡¡¡¡while (true)

¡¡¡¡{

¡¡¡¡$buf = $this->_read_head_buf(10);

¡¡¡¡if (strlen($buf) != 10) break;

¡¡¡¡$tmp = unpack('a4fid/Nsize/nflag', $buf);

¡¡¡¡if ($tmp['size'] == 0) break;

¡¡¡¡$data = $this->_read_head_buf($tmp['size']);

¡¡¡¡if (($k = $items[$tmp['fid']]) && isset($pa[$k]))

¡¡¡¡{

¡¡¡¡// the data should prefix by "" [replace]

¡¡¡¡$data = "" . $pa[$k];

¡¡¡¡unset($pa[$k]);

¡¡¡¡}

¡¡¡¡$head_body .= pack('a4Nn', $tmp['fid'], strlen($data), $tmp['flag']) . $data;

¡¡¡¡}

¡¡¡¡// reverse the items & set the new tags

¡¡¡¡$items = array_flip($items);

¡¡¡¡foreach ($pa as $k => $v)

¡¡¡¡{

¡¡¡¡if ($fid = $items[$k])

¡¡¡¡{

¡¡¡¡$head_body .= pack('a4Nn', $fid, strlen($v) + 1, 0) . "" . $v;

¡¡¡¡}

¡¡¡¡}

¡¡¡¡// new length

¡¡¡¡$new_len = strlen($head_body) + 10;

¡¡¡¡$old_len = $this->head['size'] + 10;

¡¡¡¡if ($new_len < $old_len)

¡¡¡¡{

¡¡¡¡$head_body .= str_repeat("", $old_len - $new_len);

¡¡¡¡$new_len = $old_len;

¡¡¡¡}

¡¡¡¡// count the size1,2,3,4, no include the header

¡¡¡¡// ½ÏΪ±ä̬µÄËã·¨... :p (28bytes integer)

¡¡¡¡$size = array();

¡¡¡¡$nlen = $new_len - 10;

¡¡¡¡for ($i = 4; $i > 0; $i--)

¡¡¡¡{

¡¡¡¡$size[$i] = ($nlen & 0x7f);

¡¡¡¡$nlen >>= 7;

¡¡¡¡}

¡¡¡¡$tmp = $this->head;

¡¡¡¡//echo "old_len : $old_len new_len: $new_len ";

¡¡¡¡$head_buf = pack('a3CCCCCCC', $tmp['id'], $tmp['ver'], $tmp['rev'], $tmp['flag'],

¡¡¡¡$size[1], $size[2], $size[3], $size[4]);

¡¡¡¡$head_buf .= $head_body;

¡¡¡¡$this->_file_save($head_buf, $old_len, $new_len);

¡¡¡¡}

¡¡¡¡}

¡¡¡¡?>