PHP获取音频文件的相关信息
项目需求:现在有一个音频文件上传的功能,在上传后PHP需要获取这个音频文件的相关信息,例如:时长等,由于这个文件是放在买的空间上的,没有像ffmpeg这样的扩展来处理,那么PHP能不能获取到这些信息?
下面是之前在项目中用到的一个用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=newAudioExif; //$file='/path/to/test.mp3'; // //1.检查文件是否完整(onlyforwma,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('PHPExtensionmodule`mbstring`isrequiredforAudioExif',E_USER_WARNING); returntrue; } //theMainClass classAudioExif{ //publicvars var$_wma=false; var$_mp3=false; //Construct functionAudioExif(){ //nothingtodo } //checkthefilesize functionCheckSize($file){ $handler=&$this->_get_handler($file); if(!$handler)returnfalse; return$handler->check_size($file); } //gettheinfomations functionGetInfo($file){ $handler=&$this->_get_handler($file); if(!$handler)returnfalse; return$handler->get_info($file); } //writetheinfomations functionSetInfo($file,$pa){ if(!is_writable($file)){ trigger_error('AudioExif:file`'.$file.'`cannotbeenoverwritten',E_USER_WARNING); returnfalse; } $handler=&$this->_get_handler($file); if(!$handler)returnfalse; return$handler->set_info($file,$pa); } //privatemethods function&_get_handler($file){ $ext=strtolower(strrchr($file,'.')); $ret=false; if($ext=='.mp3'){ //MP3 $ret=&$this->_mp3; if(!$ret)$ret=new_Mp3Exif(); } elseif($ext=='.wma') {//wma $ret=&$this->_wma; if(!$ret)$ret=new_WmaExif(); } else {//unknown trigger_error('AudioExifnotsupported`'.$ext.'`file.',E_USER_WARNING); } return$ret; } } //DBCS=>gb2312 functiondbcs_gbk($str) { //stripthelast"\0\0" $str=substr($str,0,-2); returnmb_convert_encoding($str,'GBK','UCS-2LE'); } //gb2312=>DBCS functiongbk_dbcs($str) { $str=mb_convert_encoding($str,'UCS-2LE','GBK'); $str.="\0\0"; return$str; } //fileexif class_AudioExif { var$fd; var$head; var$head_off; var$head_buf; //initthefilehandler function_file_init($fpath,$write=false) { $mode=($write?'rb+':'rb'); $this->fd=@fopen($fpath,$mode); if(!$this->fd) { trigger_error('AudioExif:`'.$fpath.'`cannotbeopenedwithmode`'.$mode.'`',E_USER_WARNING); returnfalse; } $this->head=false; $this->head_off=0; $this->head_buf=''; returntrue; } //readbufferfromthehead_buf&movetheoffpointer function_read_head_buf($len) { if($len<=0)returnNULL; $buf=substr($this->head_buf,$this->head_off,$len); $this->head_off+=strlen($buf); return$buf; } //readoneshortvalue 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)); } //savethefilehead 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,bufferrequired $stat=fstat($this->fd); $fsize=$stat['size']; //bufrequired(4096?)应该不会nlen-olen>4096吧 $woff=0; $roff=$olen; //readfirstbuffer flock($this->fd,LOCK_EX); fseek($this->fd,$roff,SEEK_SET); $buf=fread($this->fd,4096); //seektostart fseek($this->fd,$woff,SEEK_SET); fwrite($this->fd,$head,$nlen); $woff+=$nlen; //seektowoff&writethedata do { $buf2=$buf; $roff+=4096; if($roff<$fsize) { fseek($this->fd,$roff,SEEK_SET); $buf=fread($this->fd,4096); } //savelastbuffer $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); } } //closethefile function_file_deinit() { if($this->fd) { fclose($this->fd); $this->fd=false; } } } //wmaclass class_WmaExifextends_AudioExif { var$items1=array('Title','Artist','Copyright','Description','Reserved'); var$items2=array('Year','Genre','AlbumTitle'); //checkfilesize(length)maybeinvalidfile functioncheck_size($file) { $ret=false; if(!$this->_file_init($file))returntrue; 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; } //setinfo(savetheinfos) functionset_info($file,$pa) { //checkthepa settype($pa,'array'); if(!$this->_file_init($file,true))returnfalse; if(!$this->_init_header()) { $this->_file_deinit(); returnfalse; } //parsetheoldheader&generatethenewheader $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') {//StandardInfo //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)); } //leftlength if($tmp['len']>0)$st_body2.=$this->_read_head_buf($tmp['len']); //savetohead_body $head_body.=pack('H32VH8',$tmp['id'],strlen($st_body1.$st_body2)+24,$tmp['unused']); $head_body.=$st_body1.$st_body2; $st_body2.=$data; } //savetohead_body $head_body.=pack('H32Va4','3326b2758e66cf11a6d900aa0062ce6c',strlen($st_body1.$st_body2)+24,''); $head_body.=$st_body1.$st_body2; $this->head['num']++; } //exnotfound? if(!$ex_found) { $inum=0; $et_body=''; foreach($this->items2as$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']++; } //aftersave $new_len=strlen($head_body)+30; $old_len=$this->head['len']; if($new_len<$old_len) { $head_body.=str_repeat("\0",$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); //closethefile&return $this->_file_deinit(); returntrue; } //getinfo functionget_info($file) { $ret=array(); if(!$this->_file_init($file))returnfalse; if(!$this->_init_header()) { $this->_file_deinit(); returnfalse; } //getthedatafromhead_buf $head_num=$this->head['num'];//numofhead_frame while(($tmp=$this->_get_head_frame())&&$head_num>0) { $head_num--; if($tmp['id']=='3326b2758e66cf11a6d900aa0062ce6c') {//StandardInfo $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])); } } elseif($tmp['id']=='40a4d0d207e3d21197f000a0c95ea850') {//ExtendedInfo $inum=$this->_read_head_short(); $tmp['len']-=26; while($inum>0&&$tmp['len']>0) { //attributename $nlen=$this->_read_head_short(); $nbuf=$this->_read_head_buf($nlen); //theflag&valuelength $flag=$this->_read_head_short(); $vlen=$this->_read_head_short(); $vbuf=$this->_read_head_buf($vlen); //updatetheXX $tmp['len']-=(6+$nlen+$vlen); $inum--; $name=dbcs_gbk($nbuf); $k=substr($name,3); if(in_array($k,$this->items2)) {//allisstringvalue(refertofalgforothertags) $ret[$k]=dbcs_gbk($vbuf); } } } else {//skiponly if($tmp['len']>24)$this->head_off+=($tmp['len']-24); } } $this->_file_deinit(); return$ret; } //gettheheader? function_init_header() { fseek($this->fd,0,SEEK_SET); $buf=fread($this->fd,30); if(strlen($buf)!=30)returnfalse; $tmp=unpack('H32id/Vlen/Vlen2/Vnum/H4unused',$buf); if($tmp['id']!='3026b2758e66cf11a6d900aa0062ce6c') returnfalse; $this->head_buf=fread($this->fd,$tmp['len']-30); $this->head=$tmp; returntrue; } //_get_head_frame() function_get_head_frame() { $buf=$this->_read_head_buf(24); if(strlen($buf)!=24)returnfalse; $tmp=unpack('H32id/Vlen/H8unused',$buf); return$tmp; } } //mp3class(ifnotIDv2thenselectIDv1) class_Mp3Exifextends_AudioExif { var$head1; var$genres=array('Blues','ClassicRock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal','NewAge','Oldies','Other','Pop','R&B','Rap','Reggae','Rock','Techno','Industrial','Alternative','Ska','DeathMetal','Pranks','Soundtrack','Euro-Techno','Ambient','Trip-Hop','Vocal','Jazz+Funk','Fusion','Trance','Classical','Instrumental','Acid','House','Game','SoundClip','Gospel','Noise','AlternRock','Bass','Soul','Punk','Space','Meditative','InstrumentalPop','InstrumentalRock','Ethnic','Gothic','Darkwave','Techno-Industrial','Electronic','Pop-Folk','Eurodance','Dream','SouthernRock','Comedy','Cult','Gangsta','Top40','ChristianRap','Pop/Funk','Jungle','NativeAmerican','Cabaret','NewWave','Psychadelic','Rave','Showtunes','Trailer','Lo-Fi','Tribal','AcidPunk','AcidJazz','Polka','Retro','Musical','Rock&Roll','HardRock','Unknown'); //MP3alwaysreturntrue functioncheck_size($file) { returntrue; } //getinfo functionget_info($file) { if(!$this->_file_init($file))returnfalse; $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; } //setinfo functionset_info($file,$pa) { if(!$this->_file_init($file,true))returnfalse; if($this->_init_header()) { //alwayssavev1info $this->_set_v1_info($pa); //setv2firstifneed $this->_set_v2_info($pa); } $this->_file_deinit(); returntrue; } //gettheheaderinformation[v1+v2],callafterfile_init function_init_header() { $this->head1=false; $this->head=false; //trytogetID3v1first 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; } //trytogetID3v2 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); } //getv1info function_get_v1_info() { $ret=array(); $tmpa=array('Title','Artist','Copyright','Description','Year','AlbumTitle'); foreach($tmpaas$tmp) { $ret[$tmp]=$this->head1[$tmp]; if($pos=strpos($ret[$tmp],"\0")) $ret[$tmp]=substr($ret[$tmp],0,$pos); } //counttheGenre,[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]; //returnthevalue $ret['ID3v1']='yes'; return$ret; } //getv2info 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(1100000000000000) if($tmp['flag']&0x6000)continue; //mappingthedata if($k=$items[$tmp['fid']]) {//Iffirstcharis"\0",justskip if(substr($tmp['dat'],0,1)=="\0")$tmp['dat']=substr($tmp['dat'],1); $ret[$k]=$tmp['dat']; } } //resetthegenre 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; } //getmetainfoofMP3 function_get_meta_info() { //seektotheleadbuf: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)returnfalse; $buf=fread($this->fd,3); if(strlen($buf)!=3)returnfalse; $tmp=unpack('C3',$buf); if(($tmp[1]&0xf0)!=0xf0)returnfalse; //getthemetainfo $meta=array(); //getmpegversion $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'; //bitrates $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]; //samplerates<采样率> $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','jointstereo','dualchannel','singlechannel'); $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/15microsecs','rreserved','CCITTJ17'); $k=($tmp[3]&0x03); $meta['emphasis']=$emphasis[$k]; return$meta; } //setv1info 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; } //basicitems $items=array('Title','Artist','Copyright','Description','Year','AlbumTitle'); foreach($itemsas$k) { if(isset($pa[$k]))$tmp[$k]=$pa[$k]; } //genreindex if(isset($pa['Genre'])) { $g=0; foreach($this->genresas$gtmp) { if(!strcasecmp($gtmp,$pa['Genre'])) break; $g++; } $tmp['Genre']=$g; } if(isset($pa['Track']))$tmp['Track']=intval($pa['Track']); //packthedata $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); } //setv2info function_set_v2_info($pa) { if(!$this->head) {//insertID3 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])) { //thedatashouldprefixby"\0"[replace] $data="\0".$pa[$k]; unset($pa[$k]); } $head_body.=pack('a4Nn',$tmp['fid'],strlen($data),$tmp['flag']).$data; } //reversetheitems&setthenewtags $items=array_flip($items); foreach($paas$k=>$v) { if($fid=$items[$k]) { $head_body.=pack('a4Nn',$fid,strlen($v)+1,0)."\0".$v; } } //newlength $new_len=strlen($head_body)+10; $old_len=$this->head['size']+10; if($new_len<$old_len) { $head_body.=str_repeat("\0",$old_len-$new_len); $new_len=$old_len; } //countthesize1,2,3,4,noincludetheheader //较为变态的算法...:p(28bytesinteger) $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_lennew_len:$new_len\n"; $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); }
以上所述就是本文的全部内容了,希望大家能够喜欢。