PX : code

MP3 Info Function (3) by Mike Billings
Download this code


<?php

// 
// mp3info.php
// 
// Retrieves all mp3 file info and ID3v1.1 tag data.
// 
// This code is loosly based on the version by Chester@hackster.com
// and uses information from http://www.dv.co.yu/mpgscript/mpeghdr.htm
// and mpg123\'s source.
// 
// This code doesn\'t handle VBR very well.  If a VBR file is processed,
// it will simply find the first valid frame and report that bitrate.
// ID3v2 is not handled at all.  Perhaps in the future...
// 
// Written by Mike Billings(mike@carolina.rr.com) - 02/06/2001
// 
// Returns a single demensional array that contains the following:
//     array[\"filename\"]    = Filename
//    array[\"filesize\"]    = File size
//    array[\"seconds\"]    = Length of track in seconds
//    array[\"bitrate\"]    = Bitrate (eg: 192000)
//    array[\"sample\"]        = Sampling frequency (eg: 44100)
//    array[\"cmode\"]        = Channel mode (text)
//    array[\"version\"]    = MPEG version
//    array[\"layer\"]        = Layer description
//    array[\"crc\"]        = Has CRC bit set
//    array[\"copyright\"]    = Has Copyright bit set
//    array[\"original\"]    = Has Original bit set
//    array[\"frames\"]        = Total number of frames
//    array[\"padding\"]    = Are frames padded
//    array[\"hasID3\"]        = Does the track have an ID3
//    array[\"songtitle\"]    = ID3 Title
//    array[\"artist\"]        = ID3 Artist
//    array[\"album\"]        = ID3 Album
//    array[\"year\"]        = ID3 Year
//    array[\"comment\"]    = ID3 Comment
//    array[\"track\"]        = ID3 Track number
//    array[\"genre\"]        = ID3 Genre (lookup value)
// 

// Read a 4 byte header
function readheader($file) {
    if (
strlen($buf=fread($file,4)) != 4)
        return 
"readerror\";
    else
        return    (ord($buf[0]) << 24) |
            (ord($buf[1]) << 16) |
            (ord($buf[2]) << 8) |
            ord($buf[3]);
}

// Shift a header forward 1 byte in the file
function shiftheader($file, $header) {
    if (strlen($buf=fread($file,1)) != 1)
        return \"readerror\";
    else
        return $header << 8 | ord($buf[0]);
}

// Quick determination if the header is good
function checkheader($header) {
    // I use hexdec() here because \'&\' doesn\'t behave
    // correctly with big hex values.  ???
    if (($header & hexdec(\"ffe00000\")) != hexdec(\"ffe00000\"))    // first 11 bits must be set
            return false;
    if (!(($header >> 17)&3))            // only support MPEG Version 1
        return false;
    if ((($header >> 12)&0xf) == 0xf)        // bitrate must not be \"bad\"
        return false;
    if ((($header >> 10)&0x3) == 0x3 )        // sample must not be \"reserved\"
        return false;
    if (($header & hexdec(\"ffff0000\")) == hexdec(\"fffe0000\"))    // ???
        return false;
    
    return true;
}

// Find a valid frame header
function findheader($file) {
    $RIFF = 0x52494646;    // \'RIFF\' in hex - used for detecting RIFF headers
    $DATA = 0x64617461;    // \'data\' in hex
    
    if (!is_long($header=readheader($file))) return 0;
    while (!checkheader($header)) {
        // Check for and skip RIFF header
        if ((ftell($file)-4) == 0 && $header == $RIFF) {
            if (!is_long($header=readheader($file))) return 0;
            while ($header != $DATA) {
                if (!is_long($header=shiftheader($file, $header))) return 0;
            }
            if (!is_long($header=readheader($file))) return 0;
            // echo \"Skipped RIFF header at \" . (ftell($file)-4) . \" bytes.<BR>\\n\";
        } else {
            if (!is_long($header=shiftheader($file, $header))) return 0;
        }
    }
    // echo \"Found good header at \" . (ftell($file)-4) . \" bytes<BR>\\n\";
    return $header;
}

// Find the next header and decode it
function decodeNextHeader($file, $filename) {
    if (($header=findheader($file)) != 0)
        return decodeheader($header, $filename);
    else {
        // echo \"decodeNextHeader(): Couldn\'t find a header<BR>\\n\";
        return 0;
    }
}

function mp3info($filename) {
    if (!$file = fopen($filename,\"rb\")) {
        return 0;
    }
    
    // Find a valid header
    while (($headerArray1=decodeNextHeader($file, $filename)) == 0 && !feof($file));
    
/* This is sorta slow, so don\'t do this
    // Get two consecutive headers to match to avoid errors
    while (($headerArray2=decodeNextHeader($file, $filename)) == 0);
    
    $headerArray1 = $headerArray2;
    do {
        $headerArray1 = $headerArray2;
        
        // Find the next valid frame if there is one
        while (($headerArray2=decodeNextHeader($file, $filename)) == 0 && !feof($file));

    } while (
        $headerArray1[\"version\"] != $headerArray2[\"version\"] &&
        $headerArray1[\"layer\"] != $headerArray2[\"layer\"] &&
        $headerArray1[\"sample\"] != $headerArray2[\"sample\"] &&
        $headerArray1[\"cmode\"] != $headerArray2[\"cmode\"] &&
        !feof($file)
    );
*/
    
    // If the header info wasn\'t found, don\'t bother getting an ID3
    if (is_array($headerArray1)) {
        
        // Get ID3 v1.1 tag info
        // Do this after all the header stuff as it uses an fseek
        $filesize = $headerArray1[\"filesize\"];
        fseek($file, $filesize-128);
        $tag = fread($file, 128);
        
        if (strstr(\"$tag\", \"TAG\") || strstr(\"$tag\", \"tag\")) {
            $hasID3 = true;
            
            # Extract the tag data and close the file
            # This is the correct order...
            $songtitle = trim(substr($tag,3, 30));
            $artist = trim(substr($tag,33, 30));
            $album = trim(substr($tag,63, 30));
            $year =  substr($tag,93, 4);
            $comment = trim(substr($tag,97,30));
            
            # Grab next to last char for v1.1 track number
            $seek = fseek($file, $filesize-2);
            $track = ord(fgetc($file));
            
            # Grab last char for the Genre
            # This can get confused for EOF with fgets
            $seek = fseek($file, $filesize-1);
            $genre = ord(fgetc($file));
        } else {
            $hasID3 = false;
            $songtitle = \"\";
            $artist = \"\";
            $album = \"\";
            $year = \"\"; 
            $comment = \"\";
            $track = 0;
            $genre = 0;
        }
        $headerArray1[\"hasID3\"]=$hasID3;
        $headerArray1[\"songtitle\"]=$songtitle;
        $headerArray1[\"artist\"]=$artist;
        $headerArray1[\"album\"]=$album;
        $headerArray1[\"year\"]=$year;
        $headerArray1[\"comment\"]=$comment;
        $headerArray1[\"track\"]=$track;
        $headerArray1[\"genre\"]=$genre;
    }
    
    fclose($file);

/* Uncomment this to view the output for testing
    while (list($key, $val) = each($headerArray1))
        echo \"$key: $val<BR>\\n\";
*/
    
    return $headerArray1;
}

// Decode a header (assumes it to be valid)
function decodeheader($header, $filename) {
    
    // Array of version, level, E tag
    $bitrates = array (
        array ( // Version 1
            array (0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,-1),    // Layer 1
            array (0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,-1),    // Layer 2
            array (0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,-1)    // Layer 3
        ),
        array ( // Version 2
            array (0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1),        // Layer 1
            array (0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,-1),        // Layer 2
            array (0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,-1)        // Layer 3 (same as 2)
        )
    );
    
    // Array of frequencies, version, F tag
    $frequency = array (
        array (44100, 48000, 32000),
        array (22050, 24000, 16000),
        array (11025, 12000, 8000)
    );
    
    // Assign filesize
    $filesize=filesize($filename);
    
    // Determine Version of Mpeg.
    switch (($header & 0x00180000) >> 19) {
        case 0:
            $version = 2.5;    // Unofficial - not supported
            break;
        case 1:
            $version = 0;    // Reserved - not supported
            break;
        case 2:
            $version = 2;    // Version 2
            break;
        case 3:
            $version = 1;    // Version 1
            break;
    }
    
    // Determine Layer.
    switch (($header & 0x00060000) >> 17) {
        case 0:
            $layer = 0;    // Reserved - not supported
            break;
        case 1:
            $layer = 3;    // Layer 3
            break;
        case 2:
            $layer = 2;    // Layer 2
            break;
        case 3:
            $layer = 1;    // Layer 1
            break;
    }
    
    // Determine CRC checking enabled / disabled 1==disabled
    $crc = $header & 0x00010000 == 0x00010000;
    
    // Determine Bitrate
    $bitrate = $bitrates[$version-1][$layer-1][($header & 0x0000F000) >> 12] * 1000;
    
    // Determine Sample Rate
    $sample = $frequency[$version-1][($header & 0x00000C00) >> 10];
    
    // Determine whether padding is set on. 0 == no & 1 == yes
    $padding = $header & 0x00000200 == 0x00000200;
    
    // Determine the private bit\'s value. Dont know what for though?
    $private = ($header & 0x00000100 == 0x00000100);
    
    // Determine Channel mode
    switch (($header & 0x000000C0) >> 6) {
        case 0:
            $cmode=\"Stereo\";
            break;
        case 1:
            $cmode=\"Joint Stereo\";
            break;
        case 2:
            $cmode=\"Dual Channel Mono\";
            break;
        case 3:
            $cmode=\"Mono\";
            break;
    }
    
    // Determine Copyright 0 == no & 1 == yes
    $copyright = ($header & 0x00000008 == 0x00000008);
    
    // Determine Original 0 == Copy & 1 == Original
    $original = ($header & 0x00000004 == 0x00000004);
    
    // Determine Emphasis
    switch ($header & 0x000000C0) {
        case 0:
            $emphasis=\"none\";
            break;
        case 1:
            $emphasis=\"50/15 ms\";
            break;
        case 2:
            $emphasis=\"reserved\";
            break;
        case 3:
            $emphasis=\"CCIT J.17\";
            break;
    }
    
    // Check for invalid bitrate/cmode combos
    if (($bitrate==32 && $cmode!=\"Mono\") ||
       ($bitrate==42 && $cmode!=\"Mono\") ||
       ($bitrate==56 && $cmode!=\"Mono\") ||
       ($bitrate==80 && $cmode!=\"Mono\") ||
       ($bitrate==224 && $cmode==\"Mono\") ||
       ($bitrate==256 && $cmode==\"Mono\") ||
       ($bitrate==320 && $cmode==\"Mono\") ||
       ($bitrate==384 && $cmode==\"Mono\") ||
       $bitrate==0 || $bitrate==-1) {
        // echo \"Invalid bitrate/cmode: $bitrate, $cmode<BR>\\n\";
        return 0; 
    }
    
    // Determine number of frames.
    if (isset($sample) and isset($bitrate)) {
        if ($layer == 1)
            $frames=floor($filesize/(floor(((12*$bitrate)/($sample+$padding))*4)));    
        else
            $frames=floor($filesize/(floor((144*$bitrate)/($sample+$padding))));
    }
    
    // Determine number of seconds in song.
    if ($layer == 1) {
        $seconds=floor((384 / $sample) * $frames);
    } else {
        $seconds=floor((1152 / $sample) * $frames);
    }    
    
    $info[\"filename\"]=$filename;
    $info[\"filesize\"]=$filesize;
    $info[\"seconds\"]=$seconds;
    $info[\"bitrate\"]=$bitrate;
    $info[\"sample\"]=$sample;
    $info[\"cmode\"]=$cmode;
    $info[\"version\"]=$version;
    $info[\"layer\"]=$layer;
    $info[\"crc\"]=$crc;
    $info[\"copyright\"]=$copyright;
    $info[\"original\"]=$original;
    $info[\"frames\"]=$frames;
    $info[\"padding\"]=$padding;
    
    return($info);
    
}

?>


Comments or questions?
PX is running PHP 5.2.17
Thanks to Miranda Productions for hosting and bandwidth.
Use of any code from PX is at your own risk.