Click here to Skip to main content
Licence 
First Posted 13 Aug 2005
Views 119,575
Bookmarked 65 times

SHOUTcast Stream Ripper

By | 13 Aug 2005 | Article
Separate metadata from the SHOUTcast stream to automatically name and split the MP3 data and save to disk.

Introduction

This article is an extension of the article from Dani Forward. It implements the SHOUTcast protocol to get the metadata header from the SHOUTcast streams and read out the song titles. With this information, it is possible to automatically split the songs, store them as MP3 files on the hard disk and give them the correct song title that comes with the stream.

The source code

This is a 'one lazy night' project and doesn't pretend to be the best implementation of ripping the SHOUTcast stream. But it works simple and fine and may give you an idea, how to get the necessary information from the SHOUTcast stream and how to use them. So, any comments or improvements would be appreciated!

System libraries

First of all, we need the following Libraries:

using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

Establish the connection to the SHOUTcast Server

Then we establish a connection to the SHOUTcast Server. To get the song titles from the SHOUTcast stream, we need to alter the HttpWebRequest header and add "Icy-MetaData: 1". With that flag set, the SHOUTcast servers will send us the metadata with the song titles, if available.

The stream always begins with the ICY metadata header that comes with the HttpWebResponse object. This header contains some information about the SHOUTcast server and might look like this:

icy-notice1: This stream requires <A href="http://www.winamp.com/" target=_blank>Winamp</A> 
icy-notice2: SHOUTcast Distributed Network Audio Server/Linux v1.9.5
icy-name: RadioABF.net - Paris Electro Spirit Live From FRANCE
icy-genre: Techno House Electronic
icy-url: <A href="http://www.radioabf.net/" target=blank>http://www.radioabf.net/</A>
content-type: audio/mpeg
icy-pub: 1
icy-metaint: 32768
icy-br: 160

The icy-metaint: 32768 parameter is the most important value for us, because it tells us the blocksize of the MP3 data. In this example, the size of one MP3 block is 32768 bytes. After the connection has been established, the stream starts with an MP3 block of 32768 bytes. This block is followed by one single byte, that indicates the size of the following metadata block. This byte usually has the value 0, because there is no metadata block after each MP3 block. If there is a metadata block, the value of the single byte has to be multiplied by 16 to get the length of the following metadata block. The metadata block is followed by an MP3 block, the single metadata length byte, eventually a metadata block, an MP3 block, and so on (source: Shoutcast Metadata Protocol by Scott McIntyre).

  [STAThread]
  static void Main()
  {
   // examplestream: Radio ABF - http://www.radioabf.net
   // url: http://relay.pandora.radioabf.net:9000

   // station parameters
   String server = "http://relay.pandora.radioabf.net:9000";
   String serverPath = "/";

   String destPath = "C:\\"; // destination path for saved songs

   HttpWebRequest request = null; // web request
   HttpWebResponse response = null; // web response

   int metaInt = 0; // blocksize of mp3 data
   int count = 0; // byte counter
   int metadataLength = 0; // length of metadata header

   // metadata header that contains the actual songtitle
   string metadataHeader = ""; 
   // last metadata header, to compare with 
   // new header and find next song
   string oldMetadataHeader = null; 

   byte[] buffer = new byte[512]; // receive buffer

   // input stream on the webrequest
   Stream socketStream = null; 
   // output stream on the destination file
   Stream byteOut = null; 

   // create request
   request = (HttpWebRequest) WebRequest.Create(server);

   // clear old request header and build 
   // own header to activate Icy-metadata
   request.Headers.Clear();
   request.Headers.Add("GET", serverPath + " HTTP/1.0");
   // needed to receive metadata informations
   request.Headers.Add("Icy-MetaData", "1"); 
   request.UserAgent = "WinampMPEG/5.09";

   // execute request
   try
   {
    response = (HttpWebResponse) request.GetResponse();
   }
   catch (Exception ex)
   {
    Console.WriteLine(ex.Message);
    return;
   }

   // read blocksize to find metadata block
   metaInt = Convert.ToInt32(
             response.GetResponseHeader("icy-metaint"));

Receive bytes and separate the metadata from the MP3 data

After the connection to the SHOUTcast server has been established, byte blocks are read from the stream in an endless loop. Every single byte from the MP3 block will be counted and written to the output stream. As long as no metadata block with a song title information has been received, no output file will be created and no MP3 data will be written.

This is an example of a metadata block within the stream:

StreamTitle=' House Bulldogs - But your love (Radio Edit)';StreamUrl='';

With Regular Expressions it's quite simple to extract the song title from the metadata block. After 32768 bytes have been counted and written, the MP3 block is followed by the metadata length byte. This value is multiplied by 16 and stored in the headerLength integer. If this field is != 0, the metadata header will be written into the metadataHeader string and the headerLength is decremented by 1. When the headerLength field reaches 0, the complete header is written to the metadataHeader string. Now, the metadataHeader string will be compared with the oldMetadataHeader string that stores the last read metadata block. If the new block is not equal to the last block, that means the song has changed. Then, the song title will be extracted from the metadata block, a new file will be created with the extracted title and the output stream will be set to this file. Then, the writing process of MP3 data to the file starts again.

   try
   {
    // open stream on response
    socketStream = response.GetResponseStream();
    
    // rip stream in an endless loop
    while (true)
    {
     // read byteblock
     int bytes = socketStream.Read(buffer, 
                              0, buffer.Length);
     if (bytes < 0)
      return;

     for (int i=0 ; i < bytes ; i++)
     {
      // if there is a header, the 'metadataLength' 
      // would be set to a value != 0. Then we save 
      // the header to a string
      if (metadataLength != 0)
      {
       metadataHeader += Convert.ToChar(buffer[i]);
       metadataLength--;
       // all metadata informations were written 
       // to the 'metadataHeader' string
       if (metadataLength == 0) 
       {
        string fileName = "";

        // if songtitle changes, create a new file
        if (!metadataHeader.Equals(oldMetadataHeader))
        {
         // flush and close old byteOut stream
         if (byteOut != null)
         {
          byteOut.Flush();
          byteOut.Close();
         }
            
         // extract songtitle from metadata header. 
         // Trim was needed, because some stations 
         // don't trim the songtitle
         fileName = 
            Regex.Match(metadataHeader, 
             "(StreamTitle=')(.*)(';StreamUrl)").Groups[2].Value.Trim();
                                
         // write new songtitle to console for information
         Console.WriteLine(fileName);

         // create new file with the songtitle from 
         // header and set a stream on this file
         byteOut = createNewFile(destPath, fileName);

         // save new header to 'oldMetadataHeader' string, 
         // to compare if there's a new song starting
         oldMetadataHeader = metadataHeader;
        }
        metadataHeader = "";
       }
      }
      // write mp3 data to file or extract metadata headerlength
      else 
      {
       if (count++ < metaInt) // write bytes to filestream
       {
        // as long as we don't have a songtitle, 
        // we don't open a new file and don't write any bytes
        if (byteOut != null) 
        {
         byteOut.Write(buffer, i, 1);
         if (count%100 == 0)
          byteOut.Flush();
        }
       }
       // get headerlength from lengthbyte and 
       // multiply by 16 to get correct headerlength
       else 
       {
        metadataLength = Convert.ToInt32(buffer[i])*16;
        count = 0;
       }
      }
     }
    }
   }
   catch (Exception ex)
   {
    Console.WriteLine(ex.Message);
   }
   finally
   {
    if (byteOut != null)
     byteOut.Close();
    if (socketStream != null)
     socketStream.Close();
   }
  }

Method to create a new file and return the stream on this file

The method Main is used to create a new file and return an output stream onto this file. First, the method removes all the characters, that are not allowed in filenames. Then it checks, if the destination folder exists. If not, it will be created. After that, the method checks, if the filename already exists. If it exists, the file will not be overwritten. Instead, a new file with the filename <filename>(i).mp3 will be created.

  private static Stream createNewFile(String destPath, 
                                          String filename)
  {
   // remove characters, that are not allowed 
   // in filenames. (quick and dirrrrrty ;) )
   filename = filename.Replace(":", "");
   filename = filename.Replace("/", "");
   filename = filename.Replace("\\", "");
   filename = filename.Replace("<", "");
   filename = filename.Replace(">", "");
   filename = filename.Replace("|", "");
   filename = filename.Replace("?", "");
   filename = filename.Replace("*", "");
   filename = filename.Replace("\"", "");

   try
   {
    // create directory, if it doesn't exist
    if (!Directory.Exists(destPath))
     Directory.CreateDirectory(destPath);

    // create new file
    if (!File.Exists(destPath + filename + ".mp3"))
    {
     return File.Create(destPath + filename + ".mp3");
    }
    // if file already exists, don't overwrite it. Instead, 
    // create a new file named <filename>(i).mp3
    else 
    {
     for (int i=1;; i++)
     {
      if (!File.Exists(destPath + filename + 
                                "(" + i + ").mp3"))
      {
       return File.Create(destPath + filename + 
                                 "(" + i + ").mp3");
      }
     }
    }
   }
   catch (IOException)
   {
    return null;
   }
  }

Summary

This is a quick example, how to use the metadata in the SHOUTcast streams, to automatically name and split the stream into separate MP3 files. But it is not absolutely 100% safe for all possible streams. For example, if a station doesn't send any track information and the metadata block looks like this: StreamTitle='';StreamUrl='';, the program would split the songs correctly, but would name them ".mp3", "(1).mp3", "(2).mp3" and so on. But I kept it simple to show you just the basics of the protocol.

With some experience about using threads, it is no problem to use this code for downloading multiple streams at the same time. I have added some extra features, like different destination folders for the different streams, storing the stream information in an XML file, a view over all the streams with their status, downloaded bytes, bytes per second, etc. All this together with a GUI gives you quite a nice program to rip multiple streams at the same time. But as I mentioned at the beginning of this article, it was a 'quick and dirty' program that was developed during a lazy nightshift, and it's nothing I would let somebody see the spaghetti-source code of. ;-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

espitech

Web Developer

Germany Germany

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 PinmemberAndrei Rinea10:14 11 Nov '11  
GeneralHelp with GetResponse() PinmemberMember 459494717:09 26 Mar '09  
GeneralRe: Help with GetResponse() PinmemberMember 459494717:15 26 Mar '09  
GeneralRun this Document in microsoft visual studio 2005....... PinmemberAppupatel14:30 13 Nov '07  
GeneralThanx PinmemberIbrahim Dwaikat13:23 3 Nov '07  
QuestionHow do i get the current song title without streaming? PinmemberMaToLin7:34 21 Aug '07  
NewsOpenSource Audio library and Server Shoutcast PinmemberDAXweb_IT6:33 31 Jul '07  
QuestionHow do we compile this? Pinmemberjeanray1:19 10 Jun '07  
AnswerRe: How do we compile this? Pinmemberespitech19:36 11 Jun '07  
GeneralRe: How do we compile this? Pinmemberjeanray21:14 11 Jun '07  
GeneralRe: How do we compile this? Pinmemberespitech3:54 12 Jun '07  
GeneralRe: How do we compile this? [modified] Pinmemberjeanray6:15 12 Jun '07  
Yeah!... 35+ years of programming (can't remember!)
I have been called by a "label company" (records) to design a part of their website. You can check www.tinmandownloads.com.
The first objective was to allow uploads of songs from artists. Went beseark afterwards with the classic "can you add this, .. and that, "...
 
Your code has been invaluable to me, as it allowed me (after a few mods for VB6) to understand, check and prove the SHOUTCast stream bastard "format". (no insult intended).
Unfortunately, it does not (and will not) allow me to do what my client wants.
Have a look at the specs:
- A stream server outputs playlist music AND live "speach" (DJ), mixed.
- The stream must be "read" through TWO players: one in flash, embedded in an HTML page, the second as a downloadable player.
- Appart from playing the music (or the DJ chat), both players have two "screens". The first screen shows the contents of the ID3v2.4 tags (artist, song title, album, duration etc). The second shows a small image/flash/video of the artist during the song, or of the studio during live.
- Clicking the "video" picture during live opens a browser page to the main website, offering recording studio facilities (portugal at this time).
- Clicking the "video" picture during a song opens a browser page relating to the artist's details, where the CD can be purchased.
Simple enough, no?
Not so...
- I am using ID3v2.4 tag WXXX to store a link (just the artist's reference)
- The player reads that reference and build the website address/directory, then jumps there (no big deal here). Problem: SHOUTCast does NOT issue the ID3 tags!!!
- Flash 8 players won't work in IE6/7: memory leak problem, because SHOUTCast outputs a "continuous MP3 stream". There is a bit of metadata at connection, and a bit between songs. Full stop. Flash 8 fills its buffer until the PC crashes. Adobe has no intention of fixing this problem.
- SHOUTCast only accepts fixed bitrates (in this case 128kbps). Most artists have CD's wrongly formatted.
... and I can go on and on...
 
SO...
 
I scraped the lot. Don't want SHOUTCast (it's free, but intended to promote WINAMP and no technical help is provided).
Been working three months on this project and have designed several programs to help me.
SKYSERVER:
Gonna make my own server! Runs on any PC with broadband connection 24/7.
PLAYING SONGS: Input playlist (a list of MP3s). Outputs metadata + ID3v2.4 tags at the beginning of the song. ID3v1 tags removed. Metadata between songs only.
SWITCH FROM SONG TO LIVE:
change in the stream format: constant length chunk with metadata. One byte added: "next is live" or "next is song". In the metadata will be a link to main website studio services.
SKYBANDWIDTH
A utility to test your real bandwidth (no point in setting up a server if you do not have bandwidth!) You must have at least "max_number_of_simultaneous_listeners" * "bitrate".
VB6 (sorry!) SKYPLAYER
Downloadable and installable media player. Customisable. Slim mode (just the song) or expanded (with the video and the clickable link).
SKYTAGGER
A utility to update the MP3s TAGs to the correct format. Customisable.
SKYRESAMPLER
A utility to convert the sampling rates of the MP3s.
...
What the hell am I going into? I am retired!
I'll be offering these as a bundle package on my site: www.skytargets.com.
(don't criticise that site: I do all my trials there, so often things are not working!Laugh | :laugh: )
...
Your opinion would be appreciated: should I make that "freeware", "shareware" or sold under licence???
 
PS: interested in helping me? email to jrc_skyexpress@yahoo.co.uk with subject "tinman" (My system rejects any unknown address.) I am not saying there is money there: I don't know! (and I have not been able to get any from the client yet!)
PS2: VB.net. I am ashame to admit... I bought it 4-5 years ago. Saw the books. Read "classes". Thought "F*k"... and throw it away (bad mood at the time). Nt going to buy it again (don't have the money). WHY did they not stick to "Structures", "Unions" as in C???? WHY do they have to change the names of everything??? I thought programmers were LOGICAL
 


 
Jean-Ray Charlier (AKA "JR")
59 under the sunny Portugal (and it's hot today)
DEng, PEng, CEng, BSc, MPhil, PhD and more...
and (re-)tired...
 

 
-- modified at 12:20 Tuesday 12th June, 2007
GeneralI tried this PinmemberSingh Vikas9:29 30 May '07  
QuestionHow play the stream? PinmemberQWERin23:49 19 Feb '07  
AnswerRe: How play the stream? Pinmemberthund3rstruck5:43 2 Apr '07  
GeneralRetrieve a list of all radio stations from a given server Pinmembertabor2523:00 23 Jun '06  
GeneralI'm the "House Bulldogs" Pinmemberthecourier23:16 18 Apr '06  
GeneralProtocol violation Pinmemberpiotr.kolodziej2:09 2 Oct '05  
QuestionRe: Protocol violation Pinmemberalienhunter13:37 7 Oct '05  
AnswerRe: Protocol violation Pinmemberespitech22:53 7 Oct '05  
GeneralRe: Protocol violation Pinmemberpiotr.kolodziej0:51 8 Oct '05  
GeneralRe: Protocol violation Pinmemberjmarcos22:05 17 Oct '05  
NewsRe: Protocol violation Pinmemberpiotr.kolodziej4:53 18 Oct '05  
GeneralRe: Protocol violation Pinmemberjmarcos13:39 18 Oct '05  
GeneralRe: Protocol violation Pinmembersqlguy13:17 6 Dec '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120529.1 | Last Updated 13 Aug 2005
Article Copyright 2005 by espitech
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid