Чтение ID3 тегов и другой информации из mp3 файлов средствами php

Все ID3 теги (ID3 tag) хранятся в последних 128-ми байтах mp3 файла. Это означает, что их можно читать без специальных компонентов.
Распределение информации следующее:


Byte 1-3 = ID 'TAG'
Byte 4-33 = Title
Byte 34-63 = Artist
Byte 64-93 = Album
Byte 94-97 = Year
Byte 98-127 = Comment
Byte 128 = Genre

Для определения тегов достаточно отформатировать распаковку из бинарной строки в соответствии с выше приведенной последовательностью:

$f = fopen('test.mp3', 'rb');
rewind($f);
fseek($f, -128, SEEK_END);
$tmp = fread($f,128);
if ($tmp[125] == Chr(0) and $tmp[126] != Chr(0)) {
    // ID3 v1.1
    $format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a28COMMENT/x1/C1TRACK/C1GENRENO';
} else {
    // ID3 v1
    $format = 'a3TAG/a30NAME/a30ARTISTS/a30ALBUM/a4YEAR/a30COMMENT/C1GENRENO';
}

$id3tag = unpack($format, $tmp);

Результат работы скрипта:
Array ( [TAG] => TAG [NAME] => test [ARTISTS] => oz [ALBUM] => [YEAR] => 2007 [COMMENT] => [GENRENO] => 0 )

Иногда бывает необходимо кроме ID3 tag ‘ов узнать и другую инфу об mp3 файле (частоту, bitrate, длительность и др.), я взял за основу библиотеку http://pear.php.net/package/MP3_ID и написал следующую функцию, которая позволяет это сделать. Я не буду ее комментировать, потому что здесь мало программирования, а в основном знание структуры mp3 файла, кто захочет — разберется.

function readframe($file) {
	if (! ($f = fopen($file, 'rb')) ) die("Unable to open " . $file);
    $res['filesize'] = filesize($file);
    do {
        while (fread($f,1) != Chr(255)) { // Find the first frame
        	if (feof($f))  die( "No mpeg frame found") ;
        }
        fseek($f, ftell($f) - 1); // back up one byte

        $frameoffset = ftell($f);

        $r = fread($f, 4);

        $bits = sprintf("%'08b%'08b%'08b%'08b", ord($r{0}), ord($r{1}), ord($r{2}), ord($r{3}));
    }
	while (!$bits[8] and !$bits[9] and !$bits[10]); // 1st 8 bits true from the while

    // Detect VBR header
    if ($bits[11] == 0) {
        if (($bits[24] == 1) && ($bits[25] == 1)) {
            $vbroffset = 9; // MPEG 2.5 Mono
        } else {
            $vbroffset = 17; // MPEG 2.5 Stereo
        }
    } else if ($bits[12] == 0) {
        if (($bits[24] == 1) && ($bits[25] == 1)) {
            $vbroffset = 9; // MPEG 2 Mono
        } else {
            $vbroffset = 17; // MPEG 2 Stereo
        }
    } else {
        if (($bits[24] == 1) && ($bits[25] == 1)) {
            $vbroffset = 17; // MPEG 1 Mono
        } else {
            $vbroffset = 32; // MPEG 1 Stereo
        }
    }

    fseek($f, ftell($f) + $vbroffset);
    $r = fread($f, 4);

    switch ($r) {
        case 'Xing':
            $res['encoding_type'] = 'VBR';
        case 'VBRI':
        default:
            if ($vbroffset != 32) {
                // VBRI Header is fixed after 32 bytes, so maybe we are looking at the wrong place.
                fseek($f, ftell($f) + 32 - $vbroffset);
                $r = fread($f, 4);

                if ($r != 'VBRI') {
                    $res['encoding_type'] = 'CBR';
                    break;
                }
            } else {
                $res['encoding_type'] = 'CBR';
                break;
            }

            $res['encoding_type'] = 'VBR';
    }

    fclose($f);

    if ($bits[11] == 0) {
        $res['mpeg_ver'] = "2.5";
        $bitrates = 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),
                 );
    } else if ($bits[12] == 0) {
        $res['mpeg_ver'] = "2";
        $bitrates = 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),
                 );
    } else {
        $res['mpeg_ver'] = "1";
        $bitrates = 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),
                 );
    }

    $layer = array(
        array(0,3),
        array(2,1),
              );
    $res['layer'] = $layer[$bits[13]][$bits[14]];

    if ($bits[15] == 0) {
        // It's backwards, if the bit is not set then it is protected.
        $res['crc'] = true;
    }

    $bitrate = 0;
    if ($bits[16] == 1) $bitrate += 8;
    if ($bits[17] == 1) $bitrate += 4;
    if ($bits[18] == 1) $bitrate += 2;
    if ($bits[19] == 1) $bitrate += 1;
    $res['bitrate'] = $bitrates[$res['layer']][$bitrate];

    $frequency = array(
        '1' => array(
            '0' => array(44100, 48000),
            '1' => array(32000, 0),
                ),
        '2' => array(
            '0' => array(22050, 24000),
            '1' => array(16000, 0),
                ),
        '2.5' => array(
            '0' => array(11025, 12000),
            '1' => array(8000, 0),
                  ),
          );
    $res['frequency'] = $frequency[$res['mpeg_ver']][$bits[20]][$bits[21]];

    $mode = array(
        array('Stereo', 'Joint Stereo'),
        array('Dual Channel', 'Mono'),
             );
    $res['mode'] = $mode[$bits[24]][$bits[25]];

    $samplesperframe = array(
        '1' => array(
            '1' => 384,
            '2' => 1152,
            '3' => 1152
        ),
        '2' => array(
            '1' => 384,
            '2' => 1152,
            '3' => 576
        ),
        '2.5' => array(
            '1' => 384,
            '2' => 1152,
            '3' => 576
        ),
    );
    $res['samples_per_frame'] = $samplesperframe[$res['mpeg_ver']][$res['layer']];

    if ($res['encoding_type'] != 'VBR') {
        if ($res['bitrate'] == 0) {
            $s = -1;
        } else {
            $s = ((8*filesize($file))/1000) / $res['bitrate'];
        }
        $res['length'] = sprintf('%02d:%02d',floor($s/60),floor($s-(floor($s/60)*60)));
        $res['lengthh'] = sprintf('%02d:%02d:%02d',floor($s/3600),floor($s/60),floor($s-(floor($s/60)*60)));
        $res['lengths'] = (int)$s;

        $res['samples'] = ceil($res['lengths'] * $res['frequency']);
        if(0 != $res['samples_per_frame']) {
            $res['frames'] = ceil($res['samples'] / $res['samples_per_frame']);
        } else {
            $res['frames'] = 0;
        }
        $res['musicsize'] = ceil($res['lengths'] * $res['bitrate'] * 1000 / 8);
    } else {
        $res['samples'] = $res['samples_per_frame'] * $res['frames'];
        $s = $res['samples'] / $res['frequency'];

        $res['length'] = sprintf('%02d:%02d',floor($s/60),floor($s-(floor($s/60)*60)));
        $res['lengthh'] = sprintf('%02d:%02d:%02d',floor($s/3600),floor($s/60),floor($s-(floor($s/60)*60)));
        $res['lengths'] = (int)$s;

        $res['bitrate'] = (int)(($res['musicsize'] / $s) * 8 / 1000);
    }

    return $res;
}

Результат работы скрипта:

Array (
	[filesize] => 1369759
	[encoding_type] => CBR
	[mpeg_ver] => 1
	[layer] => 3
	[bitrate] => 192
	[frequency] => 44100
	[mode] => Stereo
	[samples_per_frame] => 1152
	[length] => 00:57
	[lengthh] => 00:00:57
	[lengths] => 57
	[samples] => 2513700
	[frames] => 2183
	[musicsize] => 1368000
)

25 thoughts on “Чтение ID3 тегов и другой информации из mp3 файлов средствами php

  1. Большое спасибо! очень интересно , коротко и главное всё по делу!

  2. Cтатья отличная, потому прошу разрешить ее размещения в разделе "Библиотека->программирование->PHP" на моем сайте http://melok.com.ua/

  3. Причем тут ссылка на объект?
    46 строка: $this->encoding_type = 'VBR';

  4. Огромный респект, сотит конечно учесть, что в файле может и не быть тегов , а инфу по длительности все таки можно вытащить … хотя легко доделывается

  5. Все сделано хорошо, удобно, но он у меня выводит неправильную длину песни и битрейт. Когда длина 3:50 и битрейт 256, он выводит 07:42 и 128 kbit

  6. Спасибо, Автору. Коротко и понятно. Только у меня почему-то длительность не правильно выводит. Из трех файлов только у одного вывел реальную длительность. У второго так вообще отрицательная получилась. Из-за чего может быть?

  7. Всем здравствуйте! Очень интересная статья — коротко, ясно и полезно! Тока научиться бы читать еще ID3v2.x теги. Мне очень помогла! НО! Я кажется нашел ошибку во втором листинге! строка 166: $res['samples'] = $res['samples_per_frame'] * $res['frames']; Значение $res['frames'] еще не определено до этой строки! Скорее всего необходимо искать все фреймы в файле! Спасибо автору.

  8. Вы кудесник и как я вижу тоже любите простоту и лаконичность. Великий респект за ресурс.

  9. У меня сайт на дле, можете подсказать куда этот скрипт ставить надо?

  10. олександр: смотря что вы хотите с ним сделать. мое мнение что вам лучше свой двиг написать и походу поймете что и куда нужно вписывать.

  11. Почему никто не говорит про ошибки функции? Скрипт вообще негодный почти всегда ложную инфу выводит… Почему так? кто знает?

  12. КрасавчиК!!! Как раз столкнулся с такой проблемой!

  13. В некоторых случаях не правильно определяет продолжительность, битрейт. -00:00:01 или в 2,3,4 раза больше чем истинная продолжительность mp3-шки

  14. Ещёб статью о том как записать значение тега

  15. В целом работает, но в некоторых случаях выдает отрицательные значения продолжительности. Автор крут, но функция требует доработки.

  16. хочу сказать что скрипт не верно определил ни битрейд ни длительность, а именно эти две вещи так были нужны

  17. Нужна помощь! Вопрос связан с MP3_Id плагином. Можно ли сделать,чтобы при загрузке mp3 файла во временную папку на одной странице,открыть этот же файл при переходе на другую страницу? С помощью сессии сохраняются данные о загружаемом файле ,но вывести теги из временной папки не получается. Как можно решить этот вопрос?

  18. А как быть с кодировкой? Попробовал добавить наши "буквы" в тег (в винампе). На странице идут иероглифы.

  19. у меня выходит просто Array. почему? в чем ошибка?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *