Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pulling Track Info From an Audio Stream Using PHP

Is it possible to pull track info from an audio stream using PHP? I've done some digging and the closest function I can find is stream_get_transports but my host doesn't support http transports via fsockopen() so I'll have to do some more tinkering to see what else that function returns.

Currently, I'm trying to pull artist and track metadata from an AOL stream.

like image 336
Levi Hackwith Avatar asked Feb 06 '11 02:02

Levi Hackwith


3 Answers

This is a SHOUTcast stream, and yes it is possible. It has absolutely nothing to do with ID3 tags. I wrote a script awhile ago to do this, but can't find it anymore. Just last week I helped another guy who had a fairly complete script to do the same thing, but I can't just post the source to it, as it isn't mine. I will however get you in touch with him, if you e-mail me at [email protected].

Anyway, here's how to do it yourself:

The first thing you need to do is connect to the server directly. Don't use HTTP. Well, you could probably use cURL, but it will likely be much more hassle than its worth. You connect to it with fsockopen() (doc). Make sure to use the correct port. Also note that many web hosts will block a lot of ports, but you can usually use port 80. Fortunately, all of the AOL-hosted SHOUTcast streams use port 80.

Now, make your request just like your client would.

GET /whatever HTTP/1.0

But, before sending <CrLf><CrLf>, include this next header!

Icy-MetaData:1

That tells the server that you want metadata. Now, send your pair of <CrLf>.

Ok, the server will respond with a bunch of headers and then start sending you data. In those headers will be an icy-metaint:8192 or similar. That 8192 is the meta interval. This is important, and really the only value you need. It is usually 8192, but not always, so make sure to actually read this value!

Basically it means, you will get 8192 bytes of MP3 data and then a chunk of meta, followed by 8192 bytes of MP3 data, followed by a chunk of meta.

Read 8192 bytes of data (make sure you are not including the header in this count), discard them, and then read the next byte. This byte is the first byte of meta data, and indicates how long the meta data is. Take the value of this byte (the actual byte with ord() (doc)), and multiply it by 16. The result is the number of bytes to read for metadata. Read those number of bytes into a string variable for you to work with.

Next, trim the value of this variable. Why? Because the string is padded with 0x0 at the end (to make it fit evenly into a multiple of 16 bytes), and trim() (doc) takes care of that for us.

You will be left with something like this:

StreamTitle='Awesome Trance Mix - DI.fm';StreamUrl=''

I'll let you pick your method of choice for parsing this. Personally I'd probably just split with a limit of 2 on ;, but beware of titles that contain ;. I'm not sure what the escape character method is. A little experimentation should help you.

Don't forget to disconnect from the server when you're done with it!

There are lots of SHOUTcast MetaData references out there. This is a good one: http://www.smackfu.com/stuff/programming/shoutcast.html

like image 152
Brad Avatar answered Nov 17 '22 13:11

Brad


Check this out: https://gist.github.com/fracasula/5781710

It's a little gist with a PHP function that lets you extract MP3 metadata (StreamTitle) from a streaming URL.

Usually the streaming server puts an icy-metaint header in the response which tells us how often the metadata is sent in the stream. The function checks for that response header and, if present, it replaces the interval parameter with it.

Otherwise the function calls the streaming URL respecting your interval and, if any metadata isn't present, then it tries again through recursion starting from the offset parameter.

<?php

/**
 * Please be aware. This gist requires at least PHP 5.4 to run correctly.
 * Otherwise consider downgrading the $opts array code to the classic "array" syntax.
 */
function getMp3StreamTitle($streamingUrl, $interval, $offset = 0, $headers = true)
{
    $needle = 'StreamTitle=';
    $ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';

    $opts = [
            'http' => [
            'method' => 'GET',
            'header' => 'Icy-MetaData: 1',
            'user_agent' => $ua
        ]
    ];

    if (($headers = get_headers($streamingUrl))) {
        foreach ($headers as $h) {
            if (strpos(strtolower($h), 'icy-metaint') !== false && ($interval = explode(':', $h)[1])) {
                break;
            }
        }
    }

    $context = stream_context_create($opts);

    if ($stream = fopen($streamingUrl, 'r', false, $context)) {
        $buffer = stream_get_contents($stream, $interval, $offset);
        fclose($stream);

        if (strpos($buffer, $needle) !== false) {
            $title = explode($needle, $buffer)[1];
            return substr($title, 1, strpos($title, ';') - 2);
        } else {
            return getMp3StreamTitle($streamingUrl, $interval, $offset + $interval, false);
        }
    } else {
        throw new Exception("Unable to open stream [{$streamingUrl}]");
    }
}

var_dump(getMp3StreamTitle('http://str30.creacast.com/r101_thema6', 19200));

I hope this helps!

like image 23
Francesco Casula Avatar answered Nov 17 '22 13:11

Francesco Casula


Thanks a lot for the code fra_casula. Here is a slightly simplified version running on PHP <= 5.3 (the original is targeted at 5.4). It also reuses the same connection resource.

I removed the exception because of my own needs, returning false if nothing is found instead.

    private function getMp3StreamTitle($steam_url)
    {
        $result = false;
        $icy_metaint = -1;
        $needle = 'StreamTitle=';
        $ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36';

        $opts = array(
            'http' => array(
                'method' => 'GET',
                'header' => 'Icy-MetaData: 1',
                'user_agent' => $ua
            )
        );

        $default = stream_context_set_default($opts);

        $stream = fopen($steam_url, 'r');

        if($stream && ($meta_data = stream_get_meta_data($stream)) && isset($meta_data['wrapper_data'])){
            foreach ($meta_data['wrapper_data'] as $header){
                if (strpos(strtolower($header), 'icy-metaint') !== false){
                    $tmp = explode(":", $header);
                    $icy_metaint = trim($tmp[1]);
                    break;
                }
            }
        }

        if($icy_metaint != -1)
        {
            $buffer = stream_get_contents($stream, 300, $icy_metaint);

            if(strpos($buffer, $needle) !== false)
            {
                $title = explode($needle, $buffer);
                $title = trim($title[1]);
                $result = substr($title, 1, strpos($title, ';') - 2);
            }
        }

        if($stream)
            fclose($stream);                

        return $result;
    }
like image 3
Simon Avatar answered Nov 17 '22 13:11

Simon