// 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);

}

}

?>

php修改音频文件_解析用PHP读写音频文件信息的详解(支持WMA和MP3)相关推荐

  1. sql server修改字段编码格式_原理:一条 sql 的执行过程详解

    思维导航: 写操作执行过程 组件介绍 1.undo log 与 MVCC 2.redo log 与 Buffer Pool 3.bin log(Server 层) 1.连接器 2.缓存(Cache) ...

  2. import引入json文件_关于TypeScript中import JSON的正确姿势详解

    前言 Typescript是微软内部出品的,用actionscript的语法在写js的一门新语言,最近 TypeScript 中毒,想想我一个弱类型出身的人,怎么就喜欢上了类型约束--当然这不是重点, ...

  3. java频繁的读写文件_大量较为频繁读写的文件一般如何进行存储?

    文件内容当然不能存在关系型数据库里.但是你可以把文件的元数据比如原始的文件名,创建者,描述,关键字等,以及文件实际存储信息存在数据库,方便查询. 如果数据量不是很大(G级别以下),文件不是特别零碎,可 ...

  4. python读写ini文件_如何使用Python3读写INI文件?

    这是一个完整的读取,更新和写入示例. 输入文件test.ini [section_a] string_val = hello bool_val = false int_val = 11 pi_val ...

  5. 转:修改ETM,用Ogre实现《天龙八部》地形与部分场景详解

    本文主要讲的是<天龙八部>游戏的地形和一部分场景的具体实现,使用C++, Ogre1.6,我摸索了段时间,可能方法用的并不是最好的,但好歹实现了.文章可能讲得有点罗嗦,很多简单的东西都讲了 ...

  6. 大白话解析Apriori算法python实现(含源代码详解)

    大白话解析Apriori算法python实现(含源代码详解) 一.专业名词解释 二.算法思路 三.python代码实现 四.Aprioir的优点.缺点及改进方法 本文为博主原创文章,转载请注明出处,并 ...

  7. python 命令-python解析命令行参数的三种方法详解

    这篇文章主要介绍了python解析命令行参数的三种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python解析命令行参数主要有三种方法: ...

  8. Unity 分享 功能 用Unity Native Share Plugin 实现链接、图片、视频等文件的分享+ 安卓 Ios 都可以,代码图文详解

    Unity 分享 功能 用Unity Native Share Plugin 实现链接.图片.视频等文件的分享+ 安卓 Ios 都可以,代码图文详解 前言 环境 效果 一.Unity Native S ...

  9. java读取csv合适文件_解析-您可以推荐一个Java库来读取(并可能写入)CSV文件吗?...

    Super CSV是读取/解析,验证和映射CSV文件到POJO的绝佳选择! 我们(Super CSV团队)刚刚发布了一个新版本(您可以从SourceForge或Maven下载它). 读取CSV文件 以 ...

最新文章

  1. 2022-2028年中国数码摄像机市场投资分析及前景预测报告
  2. OSINT系列:威胁信息挖掘ThreatMiner
  3. c# datagridview 相关操作。
  4. java 多线程原理(一)
  5. codeforces1486 F. Pairs of Paths(倍增+树上数数)
  6. 自定义按钮动态变化_新闻价值的变化定义
  7. 计算机办公软件的使用技巧,实用的Word小技巧集锦(1)办公软件知识 -电脑资料
  8. STM32之内部FLASH原理
  9. android 系统(103)---Android 架构师需要掌握的技能
  10. JavaScript学习(四十九)—构造方法、原型、对象图解
  11. 华为云大数据存储的冗余方式是三副本_揭秘!10+位DBA大神测评华为云DDS增强版实感...
  12. python 生成式 生成器
  13. foxmail客户端设置网易邮箱--提示邮箱地址或密码错误
  14. 电气火灾监控系统在地铁供配电系统中的应用
  15. 【SIGIR2017满分论文】IRGAN:大一统信息检索模型的博弈竞争
  16. LeetCode刷题13-简单-罗马数字转整数
  17. JS临时死区(TDZ)
  18. FPGA芯片结构(可编程输入输出单元IOB/可配置逻辑块CLB/数字时钟管理模块DCM/ 嵌入式块RAM(BRAM)/丰富的布线资源/ 底层内嵌功能单元/内嵌专用硬核)
  19. hive中关键字作为列名的方法
  20. vscode代码切换大小写的教程

热门文章

  1. IAR编译的工程无法正常仿真的问题
  2. mysql从入门到转行图片_数据小白转行之路-MYSQL(二)
  3. python 语言教程(1)前言
  4. C++ Primer 5th笔记(chap 16 模板和泛型编程)实例化
  5. 6. Qt 信号与信号槽(8)实例分析
  6. 初等数论--二次剩余与二次同余方程--既约剩余系中二次剩余的个数
  7. 查看安卓模拟器 CPU版本
  8. 【django】配置Jinja2模板引擎【2】
  9. 03-密码学基础-数字摘要hash的介绍
  10. [architecture]-ARMV8的一些总结-一篇就够了