Web/PHP

mp3파일 정보 얻기

aucd29 2013. 9. 26. 21:38
(7/16 8:19pm 소스 수정 했습니다.)
최근 올라왔던
흑심품은연필 님의 'mp3 파일의 정보를 알아내는 방법',
loverise 님 의 'mp3 파일정보 전부 긁어내기',
그리고 제목에 'id3' 로 검색하면 나오는 전영복 님의 'MP3 ID3v1 Tag 정보보기'
세 글을 짜집기하여-_- 거기에 최종적으로 ID3v2 태그 정보 뽑아내는 루틴을 추가하여
클래스로 만들었습니다.

허락없이 마음대로 짜집기 한 점 세 분께 사과드리구요 ^^;;;

소스는 맨 아래에 있고, 음.. 간단히 id3 태그 구조에 대하여 설명을..


http://www.id3.org/id3v1.1_blocks.gif

ID3v1.1 태그 구조입니다.
v1 (http://www.id3.org/id3v1_blocks.gif) 과 달라진 점이라면,
comment 태그가 1바이트 줄어들고,
그 1바이트에 track number 를 쓸 수 있게 했다는 점입니다.

ID3v1 태그는 mp3 파일의 제일 끝에 위치해 있고, 그 크기는 128 바이트 고정입니다.
제일 처음에 'TAG' 라는 문자열로 ID3v1 태그가 시작됨을 알리구요. (3 bytes)
Title - 30 bytes
Artist - 30 bytes
Album - 30 bytes
Year - 4 bytes
Comment - 29 bytes
Track - 1 byte
genre - 1 byte
로 이루어져 있습니다.



ID3v2 태그는 mp3 파일 제일 처음에 위치합니다.
역시 ID3v1 태그처럼 'ID3' 라는 문자열로서 ID3v2 태그가 시작됨을 알립니다.

ID3v2 태그의 구조를 보면,

+-----------------------------+
|     Header (10 bytes)     |
+-----------------------------+
|     Extended Header     |
| (variable length, OPTIONAL) |
+-----------------------------+
| Frames (variable length) |
+-----------------------------+
|         Padding         |
| (variable length, OPTIONAL) |
+-----------------------------+
| Footer (10 bytes, OPTIONAL) |
+-----------------------------+

와 같이 이루어져 있습니다.



Header 부분을 보면,

ID3 - 3 bytes
version - 2 bytes
flags - 1 byte
size - 4 bytes

와 같이 되어있구요. 여기서 size 로부터 ID3v2 태그 전체의 사이즈를 구할 수 있습니다.

Extended Header, Padding, Footer 는 말그대로 option 인 고로 넘어갑니다.
사실은 뭔 소린지 읽어봐도 잘 모르겠습니다. ㅠ_ㅠ


중요한 건 Frames 입니다. 이곳에 ID3v1 태그에서의 title, artist, ... 와 같은 정보가
들어있죠.


하나의 frame 은

frame ID - 4 bytes
frame size - 4 bytes
frame flags - 2bytes
frame body - frame size 로 부터 구할 수 있는 프레임 사이즈 bytes

로 구성됩니다.

frame ID 는 해당 프레임의 종류를 나타내 주는 4 바이트 문자열입니다.
http://www.id3.org/id3v2.3.0.html#sec4 에 현 ID3v2 버전에서 지원되는 frame ID
종류가 나열되어 있습니다. (무쟈게 많죠-_-?)

이중에 우리가 가장 많이 쓰는 mp3 플레이어인 Winamp 에서 쓰이는 ID 는
아래 소스에 $frame_id 에 있는 것입니다.
그 외에 윈앰프에서 보면 Composer, Org.Artist, Copyright, URL,
Encoded By 등의 항목이 있지만 이 부분이 채워져 있는 mp3 를 한번도
본 적이 없는 관계로 제 소스에는 없습니다.
필요한 경우 위 ID 리스트에서 찾아서 추가해주시면 됩니다. ^^


결국, mp3 파일 하나의 구조를 전체적으로 보면 다음 그림과 같이 됩니다.
그림이 허접해도 이해를..^^;;

http://www.flyforu.com/temp/mp3.gif

아무쪼록 도움이 되길 바랍니다. ^^;


<?

class mp3vars
{
    var $mpeg = array(
        '00' => 'MPEG v2.5',
        '01' => 'reserved',
        '10' => 'MPEG v2.0',
        '11' => 'MPEG v1.0'
    );

    var $layer = array(
        '00' => 'reserved',
        '01' => 'Layer III',
        '10' => 'Layer II',
        '11' => 'Layer I'
    );

    var $bitrate = array(
        '0000' => '00',
        '0001' => '32',
        '0010' => '40',
        '0011' => '48',
        '0100' => '56',
        '0101' => '64',
        '0110' => '80',
        '0111' => '96',
        '1000' => '112',
        '1001' => '128',
        '1010' => '160',
        '1011' => '192',
        '1100' => '224',
        '1101' => '256',
        '1110' => '320',
        '1111' => '0'
    );

    var $sampling = array(
        '00' => '44100',
        '01' => '48000',
        '10' => '32000',
        '11' => 'reserved'
    );

    /*
    var $crc = array(
        '0' => 'Yes',
        '1' => 'No'
    );

    var $padding = array(
        '0' => 'Frame is padded',
        '1' => 'Frame is not padded'
    );

    var $channel = array(
        '00' => 'Stereo',
        '01' => 'Joint Stereo',
        '10' => 'Dual',
        '11' => 'Mono'
    );

    var $copyright = array(
        '0' => 'No',
        '1' => 'Yes'
    );

    var $original = array(
        '0' => 'Copy',
        '1' => 'Original'
    );

    var $emphasis = array(
        '00' => 'None',
        '01' => '50/15',
        '10' => 'reserved',
        '11' => 'CCIT J.17'
    );

    */

    var $frame_id = array(
        'TPE1' => 'artist',
        'TIT2' => 'title',
        'TALB' => 'album',
        'TRCK' => 'track',
        'COMM' => 'comment',
        'TCON' => 'genre',
        'TYER' => 'year'
    );


function gettag($filename)
{
    $this->tag = array();
    
    $fp = fopen($filename, 'r');
    $tag_header = fread($fp, 10);
    if (substr($tag_header, 0, 3) != 'ID3')
    {
        $id3v2 = 0;
    }
    else
    {
        $id3v2 = 1;

        // 태그 버전 읽어오기
        $tver = substr($tag_header, 3, 2);
        $this->tag['id3ver'] = '2.'.ord($tver[0]).'.'.ord($tver[1]);

        // flag읽어오기
        $flag = substr($tag_header, 5, 1);

        // 태그 사이즈 읽어오기
        $tsize = substr($tag_header, 6, 4);
        for ($i = 0; $i < 4; $i++)
        {
            $tgsize[$i] = sprintf("%07d", decbin(ord($tsize[$i])));
            $tagsize .= $tgsize[$i];
        }
        $tagsize = bindec($tagsize) + 10;


        // 태그 프레임 읽어오기
        for($i = 10; $i < $tagsize; $i++) // 태그 헤더에 해당하는 10 바이트 다음부터 태그 사이즈까지
        {
            fseek($fp, $i);
            $id = fread($fp, 4); // 4바이트 씩 읽어와서
            if ($id[0] >= 'A' && $id[0] <= 'Z' && array_key_exists($id, $this->frame_id))
            {
                $size = fread($fp, 4);
                $frame_size = '';
                for ($j = 0; $j < 4; $j++)
                {
                    $frsize[$j] = sprintf("%07d", decbin(ord($size[$j])));
                    $frame_size .= $frsize[$j];
                }
                $frame_size = bindec($frame_size);
                
                $flag = fread($fp, 2);
                
                $key = $this->frame_id[$id];
                $this->tag[$key] = fread($fp, $frame_size);

                if ($key == 'genre')
                    $this->tag[$key] = preg_replace("/\([0-9]*\)/", "", $this->tag[$key]);
                elseif ($key == 'comment')
                    $this->tag[$key] = substr($this->tag[$key], 4);
                $i += 9 + $frame_size;
            }
        }
    }

    fseek($fp, $tagsize);     // 읽어온 태그 사이즈를 건너뛰어서
    $contents = fread($fp, 4); // mp3의 헤드를 읽어옵니다.
    for($i = 0; $i < 4; $i++)
    {
        $bin[$i] = sprintf("%08d", decbin(ord($contents[$i]))); // 아스키 값으로 불러와서 이진수로 바꾸고 다시 8자리 이진수로 바꾼뒤에
    }
    $z = implode('', $bin); // 합쳐주면 32자리의 헤드가 완성됩니다


    // 처음 11자리 A부분
    //$a = "$z[0]$z[1]$z[2]$z[3]$z[4]$z[5]$z[6]$z[7]$z[8]$z[9]z[10]";

    // 2자리 B부분 MPEG정보
    $b = "$z[11]$z[12]";
    $this->tag['mpeg'] = $this->mpeg[$b];

    // 2자리 C부분 Layer정보
    $c = "$z[13]$z[14]";
    $this->tag['layer'] = $this->layer[$c];

    /*
    $d = "$z[15]"; //1자리 D부분 CRC정보
    $this->tag['crc'] = $this->crc[$d];
    */

    // 4자리 E부분 비트레이트, 음질이라고도 하죠
    $e = "$z[16]$z[17]$z[18]$z[19]";
    $this->tag['bitrate'] = $this->bitrate[$e];

    // 2자리 F부분 샘플링
    $f = "$z[20]$z[21]";
    $this->tag['sampling'] = $this->sampling[$f];

    /*
    $g = "$z[22]"; //2자리 G부분 프레임 패딩
    $this->tag['padding'] = $this->padding[$g];

    $h = "$z[23]"; //1자리 H부분

    $i = "$z[24]$z[25]"; //2자리 I부분 스테레오정보
    $this->tag['channel'] = $this->channel[$i];

    $j = "$z[26]$z[27]"; //2자리 J부분

    $k = "$z[28]"; //2자리 K부분 카리라이터 여부
    $this->tag['copyright'] = $this->copyright[$k];

    $l = "$z[29]"; //2자리 L부분 원본 여부
    $this->tag['original'] = $this->original[$l];

    $m = "$z[30]$z[31]"; //2자리 M부분 Emphasis
    $this->tag['emphasis'] = $this->emphasis[$m];
    */

    // 노래 플레이타임
    $this->tag['time'] = (int)(filesize($filename) / ( $this->tag['bitrate'] * 1000 / 8 ));


    // ID3v2 태그가 없으면 v1 태그 정보를 가져옴
    if (!$id3v2)
    {
        fseek($fp, filesize($filename) - 128);
        $mp3tag = fread($fp, 128);
        fclose($fp);

        if (substr($mp3tag, 0, 3) != 'TAG')
        {
            echo "ID3 Tag 가 없습니다<br>\n";
            return array();
        }
        else
        {
            $this->tag['id3ver'] = '1.1';

            $mp3tag = substr($mp3tag, 3);

            $id3v1 = array('title'=>30, 'artist'=>30, 'album'=>30, 'year'=>4, 'comment'=>29, 'track'=>1, 'genre'=>1);

            $start = 0;
            foreach($id3v1 as $key => $value)
            {
                $id3v1val = substr($mp3tag, $start, $value);

                if ($key == 'track' || $key == 'genre')
                    $id3v1val = ord($id3v1val);

                $this->tag[$key] = $id3v1val;

                $start += $value;
            }

            return $this->tag;
        }
    }

    return $this->tag;
}
}


$mp3 = new mp3vars;

$tag = $mp3->gettag("t.mp3");


foreach($tag as $key => $value)
{
    echo "$key => $value <br>";
}

?>