用PHP读写音频文件的信息(支持WMA和MP3)

// 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);
//
// 版本: 0.1
// 作者: hightman
// QQ群: 17708754  (非纯PHP进阶交流群)
// 时间: 2007/01/25
// 其它: 该插件花了不少时间搜集查找 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 );
   }
}
?>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值