Source for file Id.php
Documentation is available at Id.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This code is released under the GNU LGPL Go read it over here: |
// | http://www.gnu.org/copyleft/lesser.html |
// +----------------------------------------------------------------------+
// | Authors: Sandy McArthur Jr. <Leknor@Leknor.com> |
// +----------------------------------------------------------------------+
// $Id: Id.php 315617 2011-08-27 14:36:36Z alexmerz $
// Uncomment the folling define if you want the class to automatically
// read the MPEG frame info to get bitrate, mpeg version, layer, etc.
// NOTE: This is needed to maintain pre-version 1.0 behavior which maybe
// needed if you are using info that is from the mpeg frame. This includes
// the length of the song.
// This is discouraged because it will siginfincantly lengthen script
// execution time if all you need is the ID3 tag info.
// define('ID3_AUTO_STUDY', true);
// Uncomment the following define if you want tons of debgging info.
// Tip: make sure you use a <PRE> block so the print_r's are readable.
// define('ID3_SHOW_DEBUG', true);
require_once "PEAR.php" ;
* File is not a MP3 file (corrupted?)
* @const PEAR_MP3_ID_NOMP3
define('PEAR_MP3_ID_NOMP3', 4 );
* A Class for reading/writing MP3 ID3 tags
* Note: This code doesn't try to deal with corrupt mp3s. So if you get
* incorrect length times or something else it may be your mp3. To fix just
* re-enocde from the CD. :~)
* require_once("MP3/Id.php");
* $file = "Some Song.mp3";
* echo $id3->getTag('artists');
* $id3->comment = "Be gentle with that file.";
* @author Sandy McArthur Jr. <Leknor@Leknor.com>
* @version $Id: Id.php 315617 2011-08-27 14:36:36Z alexmerz $
* ID3 v1 tag found? (also true if v1.1 found)
* ID3 v2 tag found? (not used yet)
* Was the file studied to learn more info?
* Frames are crc protected?
* encoding type (CBR or VBR)
* number of samples per MPEG audio frame
* Bytes in file without tag overhead
* number of MPEG audio frames
* quality indicator (0% - 100%)
* On Original Media? (never used)
* Emphasis (also never used)
* Byte at which the first mpeg header was found
* length of mp3 format hh:mm:ss
* length of mp3 format mm:ss
* length of mp3 in seconds
* if any errors they will be here
var $debugbeg = '<DIV STYLE="margin: 0.5 em; padding: 0.5 em; border-width: thin; border-color: black; border-style: solid">';
* creates a new id3 object
* and loads a tag from a file.
* @param string $study study the mpeg frame to get extra info like bitrate and frequency
* You should advoid studing alot of files as it will siginficantly
function MP3_Id($study = false ) {
$this->study= ($study || defined('ID3_AUTO_STUDY'));
* reads the given file and parse it
* @param string $file the name of the file to parse
* @return mixed PEAR_Error on error
function read( $file= "") {
if(!empty ($file))$this->file = $file;
return $this->_read_v1 ();
* possible names of tags are:
* artists - Name of band or artist
* album - Name of the album
* year - publishing year of the album or song
* track - the number of the track
* genre - genre of the song
* genreno - Number of the genre
* @param mixed $name Name of the tag to set or hash with the key as fieldname
* @param mixed $value the value to set
function setTag($name, $value) {
foreach( $name as $n => $v) {
$this -> $name = $value ;
* @param string $name the name of the field to get
* @param mixed $default returned if the field not exists
* @return mixed The value of the field
function getTag($name, $default = 0 ) {
if(empty ($this -> $name)) {
* update the id3v1 tags on the file.
* Note: If/when ID3v2 is implemented this method will probably get another
* @param boolean $v1 if true update/create an id3v1 tag on the file. (defaults to true)
function write($v1 = true ) {
* study() - does extra work to get the MPEG frame info.
* copy($from) - set's the ID3 fields to the same as the fields in $from
* @param string $from fields to copy
$this->name = $from->name;
$this->album = $from->album;
$this->year = $from->year;
$this->track = $from->track;
$this->genre = $from->genre;
* remove - removes the id3 tag(s) from a file.
* @param boolean $id3v1 true to remove the tag
* @param boolean $id3v2 true to remove the tag (Not yet implemented)
function remove($id3v1 = true , $id3v2 = true ) {
// TODO: write ID3v2 code
* read a ID3 v1 or v1.1 tag from a file
* $file should be the path to the mp3 to look for a tag.
* When in doubt use the full path.
* @return mixed PEAR_Error if fails
if (fseek($f, -128 , SEEK_END ) == -1 ) {
$id3tag = $this->_decode_v1 ($r);
if(!PEAR ::isError ( $id3tag)) {
$tmp = explode(Chr (0 ), $id3tag['NAME']);
$tmp = explode(Chr (0 ), $id3tag['ARTISTS']);
$tmp = explode(Chr (0 ), $id3tag['ALBUM']);
$tmp = explode(Chr (0 ), $id3tag['YEAR']);
$tmp = explode(Chr (0 ), $id3tag['COMMENT']);
if (isset ($id3tag['TRACK'])) {
$this->track = $id3tag['TRACK'];
$this->genreno = $id3tag['GENRENO'];
$this->genre = $id3tag['GENRE'];
* decodes that ID3v1 or ID3v1.1 tag
* false will be returned if there was an error decoding the tag
* else an array will be returned
* @param string $rawtag tag to decode
* @return string decoded tag
function _decode_v1 ($rawtag) {
if ($this->debug) print ($this->debugbeg . "_decode_v1(\$rawtag)<HR>\n");
if ($rawtag[125 ] == Chr (0 ) and $rawtag[126 ] != Chr (0 )) {
$format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a28COMMENT/x1/C1TRACK/C1GENRENO';
$format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a30COMMENT/C1GENRENO';
$id3tag = unpack($format, $rawtag);
if ($id3tag['TAG'] == 'TAG') {
$id3tag['GENRE'] = $this->getgenre ($id3tag['GENRENO']);
* writes a ID3 v1 or v1.1 tag to a file
* @return mixed returns PEAR_Error when fails
if (! ($f = @fopen($file, 'r+b')) ) {
if (fseek($f, -128 , SEEK_END ) == -1 ) {
// $this->error = 'Unable to see to end - 128 of ' . $file;
return PEAR ::raiseError ( "Unable to see to end - 128 of " . $file, PEAR_MP3_ID_RE);
$newtag = $this->_encode_v1 ();
if ( !PEAR ::isError ( $this->_decode_v1 ($r))) {
if (fseek($f, -128 , SEEK_END ) == -1 ) {
// $this->error = 'Unable to see to end - 128 of ' . $file;
return PEAR ::raiseError ( "Unable to see to end - 128 of " . $file, PEAR_MP3_ID_RE);
if (fseek($f, 0 , SEEK_END ) == -1 ) {
// $this->error = 'Unable to see to end of ' . $file;
return PEAR ::raiseError ( "Unable to see to end of " . $file, PEAR_MP3_ID_RE);
* the newly built tag will be returned
* @return string the new tag
$id3pack = 'a3a30a30a30a4a28x1C1C1';
$id3pack = 'a3a30a30a30a4a30C1';
print ('id3pack: ' . $id3pack . "\n");
$unp = unpack('H*new', $newtag);
* if exists it removes an ID3v1 or v1.1 tag
* returns true if the tag was removed or none was found
* else false if there was an error
* @return boolean true, if the tag was removed
if (! ($f = fopen($file, 'r+b')) ) {
if (fseek($f, -128 , SEEK_END ) == -1 ) {
return PEAR ::raiseError ( 'Unable to see to end - 128 of ' . $file, PEAR_MP3_ID_RE);
if ( !PEAR ::isError ( $this->_decode_v1 ($r))) {
* reads a frame from the file
* @return mixed PEAR_Error when fails
if (! ($f = fopen($file, 'rb')) ) {
while (fread($f,1 ) != Chr (255 )) { // Find the first frame
if ($this->debug) echo "Find...\n";
$frameoffset = ftell($f);
// Binary to Hex to a binary sting. ugly but best I can think of.
// $bits = unpack('H*bits', $r);
// $bits = base_convert($bits['bits'],16,2);
} while (!$bits[8 ] and !$bits[9 ] and !$bits[10 ]); // 1st 8 bits true from the while
if ($this->debug) print ('Bits: ' . $bits . "\n");
if (($bits[24 ] == 1 ) && ($bits[25 ] == 1 )) {
$vbroffset = 9; // MPEG 2.5 Mono
$vbroffset = 17; // MPEG 2.5 Stereo
} else if ($bits[12 ] == 0 ) {
if (($bits[24 ] == 1 ) && ($bits[25 ] == 1 )) {
$vbroffset = 9; // MPEG 2 Mono
$vbroffset = 17; // MPEG 2 Stereo
if (($bits[24 ] == 1 ) && ($bits[25 ] == 1 )) {
$vbroffset = 17; // MPEG 1 Mono
$vbroffset = 32; // MPEG 1 Stereo
// Extract info from Xing header
if ($this->debug) print ('Encoding Header: ' . $r . "\n");
if ($this->debug) print ('XING Header Bits: ' . $vbrbits . "\n");
// Next 4 bytes contain number of frames
// Next 4 bytes contain number of bytes
// Next 100 bytes contain TOC entries, skip
// Next 4 bytes contain Quality Indicator
// VBRI Header is fixed after 32 bytes, so maybe we are looking at the wrong place.
if ($this->debug) print ('Encoding Header: ' . $r . "\n");
// Next 2 bytes contain Version ID, skip
// Next 2 bytes contain Delay, skip
// Next 2 bytes contain Quality Indicator
// Next 4 bytes contain number of bytes
// Next 4 bytes contain number of frames
'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 ),
} else if ($bits[12 ] == 0 ) {
'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 ),
'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 ),
$this->layer = $layer[$bits[13 ]][$bits[14 ]];
if ($this->debug) print ('layer: ' . $this->layer . "\n");
// It's backwards, if the bit is not set then it is protected.
if ($this->debug) print ("protected (crc)\n");
if ($bits[16 ] == 1 ) $bitrate += 8;
if ($bits[17 ] == 1 ) $bitrate += 4;
if ($bits[18 ] == 1 ) $bitrate += 2;
if ($bits[19 ] == 1 ) $bitrate += 1;
'0' => array (44100 , 48000 ),
'0' => array (22050 , 24000 ),
'0' => array (11025 , 12000 ),
array ('Stereo', 'Joint Stereo'),
array ('Dual Channel', 'Mono'),
$this->mode = $mode[$bits[24 ]][$bits[25 ]];
// XXX: I dunno what the mode extension is for bits 26,27
array ('none', '50/15ms'),
$this->emphasis = $emphasis[$bits[30 ]][$bits[31 ]];
$samplesperframe = array (
* getGenre - return the name of a genre number
* if no genre number is specified the genre number from
* $this->genreno will be used.
* the genre is returned or false if an error or not found
* no error message is ever returned
* @param integer $genreno Number of the genre
* @return mixed false, if no genre found, else string
if ($this->debug) print ($this->debugbeg . " getgenre($genreno)<HR>\n" );
if (isset ($genres[$genreno])) {
$genre = $genres[$genreno];
if ($this->debug) print ($genre . "\n");
* getGenreNo - return the number of the genre name
* the genre number is returned or 0xff (255) if a match is not found
* you can specify the default genreno to use if one is not found
* no error message is ever returned
* @param string $genre Name of the genre
* @param integer $default Genre number in case of genre not found
if ($this->debug) print ($this->debugbeg . " getgenreno('$genre',$default)<HR>\n" );
foreach ($genres as $no => $name) {
if ($this->debug) print (" $no:'$name' == '$genre'" );
if ($genreno === false ) $genreno = $default;
} // getGenreNo($genre, $default = 0xff)
* genres - returns an array of the ID3v1 genres
40 => 'Alternative Rock',
46 => 'Instrumental Pop',
47 => 'Instrumental Rock',
51 => 'Techno-Industrial',
92 => 'Progressive Rock',
93 => 'Psychedelic Rock',
136 => 'Christian Gangsta Rap',
140 => 'Contemporary Christian',
Documentation generated on Mon, 11 Mar 2019 15:44:35 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|