<?php
if
(!
extension_loaded
('mbstring')){
trigger_error('PHP Extension module `mbstring` is required
for
AudioExif', E_USER_WARNING);
return
true;
}
class
AudioExif{
var
$_wma
= false;
var
$_mp3
= false;
function
AudioExif() {
}
function
CheckSize(
$file
) {
$handler
= &
$this
->_get_handler(
$file
);
if
(!
$handler
)
return
false;
return
$handler
->check_size(
$file
);
}
function
GetInfo(
$file
) {
$handler
= &
$this
->_get_handler(
$file
);
if
(!
$handler
)
return
false;
return
$handler
->get_info(
$file
);
}
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
);
}
function
&_get_handler(
$file
) {
$ext
=
strtolower
(
strrchr
(
$file
, '.'));
$ret
= false;
if
(
$ext
== '.mp3') {
$ret
= &
$this
->_mp3;
if
(!
$ret
)
$ret
=
new
_Mp3Exif();
}
else
if
(
$ext
== '.wma')
{
$ret
= &
$this
->_wma;
if
(!
$ret
)
$ret
=
new
_WmaExif();
}
else
{
trigger_error('AudioExif not supported `' .
$ext
. '` file.', E_USER_WARNING);
}
return
$ret
;
}
}
function
dbcs_gbk(
$str
)
{
$str
=
substr
(
$str
, 0, -2);
return
mb_convert_encoding(
$str
, 'GBK', 'UCS-2LE');
}
function
gbk_dbcs(
$str
)
{
$str
= mb_convert_encoding(
$str
, 'UCS-2LE', 'GBK');
$str
.=
"\0\0"
;
return
$str
;
}
class
_AudioExif
{
var
$fd
;
var
$head
;
var
$head_off
;
var
$head_buf
;
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;
}
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
;
}
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));
}
function
_file_save(
$head
,
$olen
,
$nlen
= 0)
{
if
(
$nlen
== 0)
$nlen
=
strlen
(
$head
);
if
(
$nlen
==
$olen
)
{
flock
(
$this
->fd, LOCK_EX);
fseek
(
$this
->fd, 0, SEEK_SET);
fwrite(
$this
->fd,
$head
,
$nlen
);
flock
(
$this
->fd, LOCK_UN);
}
else
{
$stat
=
fstat
(
$this
->fd);
$fsize
=
$stat
['size'];
$woff
= 0;
$roff
=
$olen
;
flock
(
$this
->fd, LOCK_EX);
fseek
(
$this
->fd,
$roff
, SEEK_SET);
$buf
=
fread
(
$this
->fd, 4096);
fseek
(
$this
->fd,
$woff
, SEEK_SET);
fwrite(
$this
->fd,
$head
,
$nlen
);
$woff
+=
$nlen
;
do
{
$buf2
=
$buf
;
$roff
+= 4096;
if
(
$roff
<
$fsize
)
{
fseek
(
$this
->fd,
$roff
, SEEK_SET);
$buf
=
fread
(
$this
->fd, 4096);
}
$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);
}
}
function
_file_deinit()
{
if
(
$this
->fd)
{
fclose(
$this
->fd);
$this
->fd = false;
}
}
}
class
_WmaExif
extends
_AudioExif
{
var
$items1
=
array
('Title', 'Artist', 'Copyright', 'Description', 'Reserved');
var
$items2
=
array
('Year', 'Genre', 'AlbumTitle');
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
;
}
function
set_info(
$file
,
$pa
)
{
settype(
$pa
, '
array
');
if
(!
$this
->_file_init(
$file
, true))
return
false;
if
(!
$this
->_init_header())
{
$this
->_file_deinit();
return
false;
}
$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')
{
$st_found
= true;
$st_body1
=
$st_body2
= '';
$lenx
= unpack('v5',
$this
->_read_head_buf(10));
$tmp
['len'] -= 34;
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
));
}
if
(
$tmp
['len'] > 0)
$st_body2
.=
$this
->_read_head_buf(
$tmp
['len']);
$head_body
.= pack('H32VH8',
$tmp
['id'],
strlen
(
$st_body1
.
$st_body2
)+24,
$tmp
['unused']);
$head_body
.=
$st_body1
.
$st_body2
;
$st_body2
.=
$data
;
}
$head_body
.= pack('H32Va4', '3326b2758e66cf11a6d900aa0062ce6c',
strlen
(
$st_body1
.
$st_body2
)+24, '');
$head_body
.=
$st_body1
.
$st_body2
;
$this
->head['num']++;
}
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']++;
}
$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
);
$this
->_file_deinit();
return
true;
}
function
get_info(
$file
)
{
$ret
=
array
();
if
(!
$this
->_file_init(
$file
))
return
false;
if
(!
$this
->_init_header())
{
$this
->_file_deinit();
return
false;
}
$head_num
=
$this
->head['num'];
while
((
$tmp
=
$this
->_get_head_frame()) &&
$head_num
> 0)
{
$head_num
--;
if
(
$tmp
['id'] == '3326b2758e66cf11a6d900aa0062ce6c')
{
$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')
{
$inum
=
$this
->_read_head_short();
$tmp
['len'] -= 26;
while
(
$inum
> 0 &&
$tmp
['len'] > 0)
{
$nlen
=
$this
->_read_head_short();
$nbuf
=
$this
->_read_head_buf(
$nlen
);
$flag
=
$this
->_read_head_short();
$vlen
=
$this
->_read_head_short();
$vbuf
=
$this
->_read_head_buf(
$vlen
);
$tmp
['len'] -= (6 +
$nlen
+
$vlen
);
$inum
--;
$name
= dbcs_gbk(
$nbuf
);
$k
=
substr
(
$name
, 3);
if
(in_array(
$k
,
$this
->items2))
{
$ret
[
$k
] = dbcs_gbk(
$vbuf
);
}
}
}
else
{
if
(
$tmp
['len'] > 24)
$this
->head_off += (
$tmp
['len'] - 24);
}
}
$this
->_file_deinit();
return
$ret
;
}
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;
}
function
_get_head_frame()
{
$buf
=
$this
->_read_head_buf(24);
if
(
strlen
(
$buf
) != 24)
return
false;
$tmp
= unpack('H32id/Vlen/H8unused',
$buf
);
return
$tmp
;
}
}
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');
function
check_size(
$file
)
{
return
true;
}
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
;
}
function
set_info(
$file
,
$pa
)
{
if
(!
$this
->_file_init(
$file
, true))
return
false;
if
(
$this
->_init_header())
{
$this
->_set_v1_info(
$pa
);
$this
->_set_v2_info(
$pa
);
}
$this
->_file_deinit();
return
true;
}
function
_init_header()
{
$this
->head1 = false;
$this
->head = false;
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
;
}
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);
}
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
],
"\0"
))
$ret
[
$tmp
] =
substr
(
$ret
[
$tmp
], 0,
$pos
);
}
if
(
$this
->head1['Reserved'] == 0)
$ret
['Track'] =
$this
->head1['Track'];
else
$ret
['Description'] .=
chr
(
$ret
['Reserved']) .
chr
(
$ret
['Track']);
$g
=
$this
->head1['Genre'];
if
(!isset(
$this
->genres[
$g
]))
$ret
['Genre'] = 'Unknown';
else
$ret
['Genre'] =
$this
->genres[
$g
];
$ret
['ID3v1'] = 'yes';
return
$ret
;
}
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']);
if
(
$tmp
['flag'] & 0x6000)
continue
;
if
(
$k
=
$items
[
$tmp
['fid']])
{
if
(
substr
(
$tmp
['dat'], 0, 1) ==
"\0"
)
$tmp
['dat'] =
substr
(
$tmp
['dat'], 1);
$ret
[
$k
] =
$tmp
['dat'];
}
}
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
;
}
function
_get_meta_info()
{
$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;
$meta
=
array
();
$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
=
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
];
$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';
$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
;
}
function
_set_v1_info(
$pa
)
{
$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;
}
$items
=
array
('Title', 'Artist', 'Copyright', 'Description', 'Year', 'AlbumTitle');
foreach
(
$items
as
$k
)
{
if
(isset(
$pa
[
$k
]))
$tmp
[
$k
] =
$pa
[
$k
];
}
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']);
$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);
}
function
_set_v2_info(
$pa
)
{
if
(!
$this
->head)
{
return
;
}
$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
]))
{
$data
=
"\0"
.
$pa
[
$k
];
unset(
$pa
[
$k
]);
}
$head_body
.= pack('a4Nn',
$tmp
['fid'],
strlen
(
$data
),
$tmp
['flag']) .
$data
;
}
$items
=
array_flip
(
$items
);
foreach
(
$pa
as
$k
=>
$v
)
{
if
(
$fid
=
$items
[
$k
])
{
$head_body
.= pack('a4Nn',
$fid
,
strlen
(
$v
) + 1, 0) .
"\0"
.
$v
;
}
}
$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
;
}
$size
=
array
();
$nlen
=
$new_len
- 10;
for
(
$i
= 4;
$i
> 0;
$i
--)
{
$size
[
$i
] = (
$nlen
& 0x7f);
$nlen
>>= 7;
}
$tmp
=
$this
->head;
$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
);
}