Benutzer-Werkzeuge

Webseiten-Werkzeuge


start:themen:webradiosender-suchmaschine

Webradio-Sender Suchmaschine

Im Rahmen eines beruflichen Projekts benötige ich zum Testen eines Webradio-Players eine umfangreiche Datenbank mit vielen Webradio- Sendern. Webradiosender stellen in einem kontinuierlichem Datenstrom mit definierter Bitrate Musik und/oder Informationen zur Verfügung und werden mit unterschiedlichen Protokollen angesprochen. Als beliebtestes Datenstrom-Format hat sich das von Nullsoft, dem Hersteller des populärem MP3-Players Winamp erdachte Shoutcast-Protokoll etabliert.

Das liegt wohl einerseits daran, daß Musik von den Shoutcast-Sendern im überall problemlos abspielbarem MP3-Format „gesendet“ wird, daß man auch abspeichern und auf jedem wieder MP3-Player abspielen kann und andererseits an quelloffenen Servern wie Icecast, welche kostenlos genutzt werden können und nahezu jedes ans Internet anschließbare Gerät zu einem Webradiosender machen können. Die Popularität der Icecast-Server trägt auch zu einer stärkeren Verbreitung des MP3 überlegenen Audioformats Vorbis (Ogg) bei, das aber leider nur selten von Playerhardware unterstützt wird, auch weil es höhere Anforderungen an die Decoder-Hardware als MP3 stellt.

Webradio-Serverprogramme wie Icecast oder Shoutcast können sich automatisch regelmäßig in einer Datenbank bei http://www.shoutcast.com eintragen, wenn der Betreiber den Sender als „public“ konfiguriert. Deshalb findet man auf dieser Webseite auch die meisten bzw. fast alle öffentlich zugänglichen Webradio-Sender, die im Shoutcast-Format senden. Darüberhinaus gibt es trotzdem noch andere Senderverzeichnisse wie http://www.net-trax.org, bei denen viele Sender per Suchabfrage gefunden werden können.

All diesen Sender-Verzeichnissen ist jedoch gemein, daß sie als dynamische Webseiten realisiert sind- eine maschinenlesbare Liste bzw. ein Webservice für Senderdaten ist mir nicht bekannt. Um zu einer Senderliste zu kommen, muß man also erst mal eine Suchmaschine (im Netz-Slang auch Webcrawler, Spider oder kurz Bot genannt) bauen, welche Abfragen an solche Seiten generiert und die Ergebnisse sammelt, um eine eigene Datenbasis aufzubauen.

Download

Dieser Artikel beschreibt detailliert mit C# Quellcode, wie solch ein Programm aufgebaut ist, das fertige Programm und den kompletten Quellcode mit Projektdateien für Microsoft VisualStudio 2005 kann man hier herunterladen:

shoutcast_runner.zip

Das fertige Progamm benötigt natürlich das Microsoft .Net Framework Runtime 2.0, der Quellcode sollte aber auch noch für das .NET Framework 1.1 übersetzbar sein.

Kalter Krieg der Maschinen

Gerade in jüngster Zeit und vor allem im Zusammenhang mit der immer mächtiger werdenden Firma Google stellt sich die Frage nach der Legalität solcher Bots bzw. der Verfügbarmachung der von einem Bot gewonnenen Ergebnisse in anderen Medien oder Webseiten. Einerseits möchte natürlich jeder Webseitenbetreiber möglichst hochrangig in einer zum Quasimonopol etablierten Suchmaschine wie Google stehen, und hier scheint jedes Mittel recht, dieses Ziel zu erreichen, wozu auch regelrechte Wettbewerbe mit Nonsense-Webseiten gehören.

Andererseits möchte der Seitenbetreiber natürlich nicht, daß sein „geistiges Eigentum“ gestohlen wird und völlig unreferenziert und ohne Zusammenhang auf anderen Webseiten landet. Für den Seitenbetreiber ist dabei besonders bitter, daß nicht nur seine Werbe- oder sonstige Botschaft nicht die gewünschte Zielgruppe erreicht, er hat durch die Bots, welche ihn „bestehlen“ auch noch Kosten, denn häufige und heftige maschinelle Abfragen kosten unter Umständen wesentlich mehr Bandbreite als reguläre Besucher der Seite. Bei schlecht abgesicherten Seiten besteht sogar noch die Gefahr, daß Bots an vertrauliche Daten wie Kundenadressen oder gar Kreditkartennummern gelangen, wenn sie automatisiert den Shop „angreifen“.

Die Grenze von einem „guten“ Bot, dessen Zugriff dem Seitenbetreiber Vorteile und durch die wegen der Bots eigentlich total unsinnige Seiten-Hit-Zählerei sogar bares Geld einbringt zu einem „bösen“ Bot, der letztlich sogar als Denial of Service- Attacke„ oder Spamschleuder auswirken, ist also fließend.

Wie also das automatisierte Auslesen von Webradio-Sendern von http://shoutcast.com zu bewerten ist, möchte ich dem Leser überlassen. Meine Meinung ist, daß Nullsoft durch das automatische Eintragen seitens der Shoutcast/Icecast Server wenig zur Gewinnung solcher Daten beiträgt, aber eine Art Monopol aufbaut. Solange Nullsoft diese Daten also nicht etwa auch durch einen Webservice bereitstellt, behalte ich mir das Recht vor, sie mit einem Bot abzuholen.

„Faire“ Bots bzw. Suchmaschinen werden auch für den mit Werbemüll über jedes vertretbare Maß hinaus gefluteten Web-Benutzer immer wichtiger, gerade die Portale der großen Provider enthalten ja nur noch wenige Prozent objektiv verwertbare Information in einem Ozean aus Werbemüll. Gegenmaßnahmen „mündiger“ Benutzer wie der Einsatz von allerlei Such- und Filtertools ergeben sich da wohl unausweichlich.

Wegen dem beschriebenen Dilemma muß man als Betreiber eines Bots natürlich auch mit unter Umständen recht massiver Gegenwehr rechnen. Im Falle von Shoutcast.com erhält der Bot nach einigen hundert Abfragen etwa die Meldung „Too many requests“. Die ganze Senderliste mit einigen Tausend Sendern kann also nicht in einem Durchgang ermittelt werden- man muß sich da wohl einige Tage Zeit lassen, um alle Senderkennungen „abzugrasen“. Bei zu „aggressivem“ oder gar kriminellem Vorgehen des Bots muß man auch mit einer Sperrung der IP-Adresse, Beschwerde des Seitenbetreibers beim Provider oder in letzterem Fall mit Strafanzeige rechnen. Man sollte sich also gut überlegen, ob das was der Bot da tut noch rechtens ist und ob man die angeforderten Daten überhaupt abfragen oder gar verwenden darf- auch wenn die Webseite noch so schlampig abgesichert ist.

Dieser Artikel versteht sich daher nicht als „Anleitung für Hacker“, sondern soll auch Seitenbetreiber sensibel für mögliche Sicherheitsprobleme ihrer Seiten machen. Denn ich bin auch der Meinung, daß der „Rechteinhaber“ bzw. legale Nutzer von Daten auch ein Mindesmaß an Verantwortung für den Schutz dieser Daten besitzt und sich nicht beschweren darf, wenn simpelste Webabfragen genügen, ihm seine Daten zu „stehlen“.

GET it und ab die POST

Browser und Webserver kommunizieren mit dem Hypertext Transfer Protocol (http) miteinander. Der Browser schickt dabei im Wesentlichen GET- Befehle, um Dateien herunter zu laden und POST-Befehle um Daten bzw. Formular-Inhalte an den Server zu übermitteln. Sowohl mit diesen Kommandos als auch mit den Antworten des Servers wird stets noch ein „Header“ übermittelt, der Informationen (Metadaten) über Browser, Server, Dateiinhalt bzw. die berüchtigten „Cookies“ enthält. Außerdem können Befehlszeilen-Parameter übergeben werden, die beim GET- Befehl z.B. einfach an die Seiten-URL angehängt werden.

Die vom Browser in der Statuszeile angezeigten bzw. in der Adresszeile auch editierbaren URLs verraten also sehr viel über die auf dem Server verfügbaren oder vom Benutzer bereit gestellten Informationen- häufig soviel, daß man an Informationen gelangen kann, die nicht für einen bestimmt sind. Wenn wir z.B. bei einem Webshop mit der Maus über den „Bestellen“-Button fahren, können wir vielleicht etwas von dieser Art in der Statuszeile sehen:

http://www.supershop.de/shop/bestellung.php?kdnr=0815&an=1234&kto=2345&blz=40030010

Bei solch einem Shop sollte man besser nichts bestellen. Denn in dieser URL werden offensichtlich Kundennummer (kdnr), Auftragsnummer(an), Kontonummer (kto) und Bankleitzahl (blz) des Bestellers ungeschützt durchs Internet geschickt und in jedem Proxy bzw. Logfile auf dem Weg zum Shop gespeichert und für jedermann zugänglich gemacht. Ein potentieller Angreifer kann weiterhin „aus Spaß“ auch mal andere Kundennummern als 0815 einsetzen und gelangt so vielleicht an Daten wie Adressen oder Bankverbindungen anderer Kunden. Eventuell gibt es bei solchen Versuchen auch Datenbank- Fehlermeldungen, das ist aber umso besser, denn dann erhält der Angreifer auch noch Informationen über die Datenbankstruktur des Shops und kann noch effektiver angreifen.

Wenn der Angreifer dann genug Informationen hat, programmiert er einen Bot, der automatisiert all diese offensichtlich völlig ungeschützten Daten in eine eigene Datenbank einträgt. Vermutlich ist dann auch der Rest des Shops so schlampig programmiert, daß der Angreifer dann bald alle Kunden auf eine eigene Webseite umlenken und um ihr Geld bringen kann (:de:Cross-Site_Scripting).

Ich will dieses unangenehme Thema nicht weiter vertiefen, weil dies keine Anleitung zum Hacken werden soll. Der Leser soll nur sensibilisiert werden, sich die von ihm bei jedem Klick übertragenen Daten mal anzuschauen und sich ein Urteil bilden können, ob da nicht vertrauliche Informationen veröffentlicht werden.

Schaun wir uns doch mal die URLs an, welche die „Tune In“-Buttons der Seite http://www.shoutcast.com per GET- Befehl abschicken, wenn man sie anklickt:

http://www.shoutcast.com/sbin/shoutcast-playlist.pls?rn=7955&file=filename.pls

Bei all diesen Buttons unterscheidet sich nur der Parameter rn. Dabei handelt es sich offensichtlich um eine Datenbank- interne Radiosender-Kennung. Der Parameter file ist wohl ohne Bedeutung, zumal die beim Anklicken herunter geladenen Dateien immer shoutcast_playlist.pls heißen, egal was man für file übergibt. Wir wissen also bereits, was unser Bot zu tun hat: Abfragen mit obiger URL mit allen bis zu vierstelligen Zahlen von 0 bis 9999 durchführen. Viele dieser Nummern sind natürlich nicht benutzt, wobei dann „leere“ PLS-Dateien geliefert werden.

Diese „Brute Force“ Taktik, welche stolze 10000 Hits per Programmdurchlauf auf die Shoutcast.com- Seite verursachen würde, läßt uns der Server natürlich nicht durchgehen. Gäbe man solch einen Bot noch an viele Enduser weiter, die dann alle paar Minuten solche Abfragen an Shoutcast.com machen würden, wäre Shoutcast.com sehr schnell nicht mehr erreichbar und die Kosten für den Betreiber wären immens. So ist es verständlich, daß pro Stunde nur einige hundert Abfragen von einer IP-Adresse zugelassen werden, dann liefert Shoutcast.com nur noch eine Datei, welche den Satz „Too many requests. Try again tomorrow“ enthält.

Playlisten

Die vom Server Shoutcast.com gelieferten PLS-Dateien sind Playlisten (Wiedergabelisten), welche MP3-Player wie Winamp oder XMMS verwenden. Andere Webradiosender- Verzeichnisse wie das bereits genannte http://www.net-trax.org verwenden noch das ebenfalls von Winamp stammende, alte M3U- Format.

Die von Shoutcast.com gelieferten PLS-Dateien enthalten jedenfalls die Felder numberofentries, File%, Title% und Length%, wie folgendes Beispiel einer gelieferten PLS-Datei zeigt:

[playlist]
numberofentries=6
File1=http://64.236.34.97:80/stream/1010
Title1=(#1 - 520/14484) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length1=-1
File2=http://64.236.34.4:80/stream/1010
Title2=(#2 - 504/13681) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length2=-1
File3=http://64.236.34.196:80/stream/1010
Title3=(#3 - 593/16055) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length3=-1
File4=http://64.236.34.67:80/stream/1010
Title4=(#4 - 2349/14414) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length4=-1
File5=http://160.79.128.30:7702
Title5=(#5 - 3/3) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length5=-1
File6=http://207.200.96.231:8008
Title6=(#6 - 10/10) S K Y . F M - Absolutely Smooth Jazz - the world's smoothest jazz 24 hours a day
Length6=-1
Version=2

Dabei scheint Length auch wenig Sinn zu machen, denn ein Radiosender hat ja keine begrenzte „Spieldauer“- so ist der Wert -1 überall zu finden. Das ein Sender aber wie hier mehrere URLs hat, ist für populäre Sender aber Pflicht: So ein Webserver benötigt viel Bandbreite und kann höchstens ein paar hundert Hörer mit einer URL bedienen. Ein Sender, der von tausenden gleichzeitig zu hören sein soll, benötigt also viele Webadressen bzw. Anschlüsse ins Internet und jeder Anschluß läßt nur eine Maximalzahl Hörer zu, dann muß der Player einen anderen Anschluß probieren.

Unser Bot kann die Informationen all dieser PLS-Dateien also in seine eigene Datenbank schreiben. Schade ist nur, daß solch eine Playlist keine weiteren Informationen über die Sender enthält. Für eine eigene Liste bräuchte man auf jeden Fall noch Genres und Bitrate (Qualität) der Sender. Wenn man nun aber die Adressen der Sender schon hat, kann man dann nicht die Sender selbst befragen? Genau darum geht es im nächsten Abschnitt.

Das ICY Protokoll

Ein Shoutcast- Server funktioniert sehr ähnlich zu einem HTTP-Webserver. Da er auf den meisten Servern aber parallel zu einem Webserver läuft, wird er meist mit einer anderen Portadresse angesprochen. Sehr häufig werden Port 8000..8100 verwendet, wie auch das Beispiel oben zeigt. Im Beispiel wird aber auch der für HTTP-Webserver übliche Port 80 genutzt, wohl um Musik auch durch Firewalls oder Proxies zu schleusen, die nur Port 80 durchlassen.

Der GET-Befehl an einen Shoutcast-Server ist auch HTTP-konform, ein Browser kann ihn also ganz normal an den Shoutcast-Server schicken. Die Antwort (Webresponse) unterscheidet sich aber erheblich, insbesondere was die Header-Daten angeht. Deshalb muß man bei Nutzung der .NET-Klasse HttpWebRequest zur Anwendung auch eine Config-Datei beilegen, welche den Parameter „useUnsafeHeaderParsing“ auf „true“ setzt, sonst verwirft diese Klasse den Webresponse-Header als fehlerhaft.

Konkret muß unser Bot-Programm im Anwendungsverzeichnis also eine Datei ProgramName.exe.config mit folgendem Inhalt haben, sonst funktioniert das Programm nicht:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.net>
    <settings>
      <httpWebRequest useUnsafeHeaderParsing = "true" />
    </settings>
  </system.net>
</configuration>

Jedenfalls liefert uns ein Shoutcast-Server, wenn er denn überhaupt online ist und noch ein Plätzchen für uns frei hat, die folgenden Header-Parameter des Shoutcast-Protokolls. Als Statusmeldung kommt übrigens bei Icecast-Servern auch nicht OK wie bei eimem HTTP- Server, sondern ICY zurück. Auch das irritiert so manche Lesesoftware.

Parameter Bedeutung
icy-notice1 allgemeiner Hinweis: etwa welcher Client benutzt werden sollte
icy-notice2 Hinweise zur verwendeten Server-Software
icy-name Sendername
icy-genre Genre des Senders
icy-url Webadresse des Senders
Content-Type MIME-Typ des Datenstroms (wie bei Webseiten üblich)
icy-pub 1= Sender wird in Shoutcast.com eingetragen, sonst 0
icy-br Bitrate in kb/s

Die uns interessierenden Informationen Genre und Bitrate sollten also vom Sender geliefert werden!

Wir sollten also alle von Shoutcast.com gelieferten URLs „abklappern“ und uns bei den Sendern die fehlenden Daten für unsere Datenbank holen.

Realisierung

Unser Bot läßt sich Dank des .NET Frameworks erstaunlich einfach in C# entwickeln. Für die Webzugriffe gibt es die Klassen HttpWebRequest und HttpWebResponse, die synchronen und asynchronen Zugriff auf Webseiten ermöglichen und die Headerdaten komfortabel in Kollektionen abbilden. Bei den kleinen Datenmengen respektive Headerdaten, die wir benötigen, reicht synchroner Zugriff aus. Zum Rausschreiben der Daten können wir die Klasse XmlTextWriter nutzen, als XML-Datei können wir sie leicht in Player, Datenbanken oder andere Programme importieren.

Die Sender Metadaten-Struktur

Für das Abbilden der Metadaten eines Senders verwenden wir eine Struktur IcyHeader, in welcher wir den Inhalt der Tabelle oben wiedererkennen können:

  /// <summary>
  /// die wichtigsten Felder des per WebResponse gelieferten Icy-Headers
  /// </summary>
  struct IcyHeader
  {
    /// <summary>
    /// setze Standardwerte für den Fall, das Radiosender nicht abgefragt werden
    /// </summary>
    /// <param name="name">Name des Radiosenders aus der Playlist</param>
    /// <param name="url">URL des Radiosenders aus der Playlist</param>
    public IcyHeader(string name, string url)
    {
      this.name = name;
      this.url = url;
      this.bitrate = "128";
      this.notice1 = "";
      this.notice2 = "";
      this.type = "MP3";
      this.genre = "Mixed";
    }
 
    public string name; // Sendername
    public string genre; // Sender-Genre
    public string bitrate; // Bitrate in kb/s
    public string url; // Webadresse des Senders (mit Port)
    public string type; // MP3 oder OGG
    public string notice1;  // Hinweis an den User: z.B. benötigter Client
    public string notice2; // Hinweis an den User: Server-Info
  }

Für alles außer Sendername und URL geben wir im Konstruktor Defaultwerte vor, die verwendet werden können, wenn es uns nicht gelingt, einen Sender zu erreichen, weil der gerade nicht sendet oder „überfüllt“ ist.

Die Klasse ShoutcastRunner

Die Klasse ShoutcastRunner ist die Implementierung des Bots, welcher Shoutcast.com ausliest. Hier brauchen wir folgende Variablen:

    // Webadresse des Radio-Directories
    const string ShoutcastUrl = "http://www.shoutcast.com/sbin/shoutcast-playlist.pls?rn={0}&file=filename.pls";
 
    // Dateiname der XML-Ausgabedatei, kompatibel zum Info-Cockpit Webradio-Plugin
    const string ListName = "stationlist.xml";
 
    // XML- Ausgabedatei-Schreiber
    XmlTextWriter m_writer = null;
 
    // hole die Radio-Metadaten direkt von den Sendern
    bool m_verify = true;
 
    // die Metadaten aller gefundenen Sender auf Console ausgeben
    bool m_verbose = true;
 
    // true bei Rauswurf durch Shoutcast.com
    bool m_cancelled = false;

Zum Flag m_verify ist noch zu sagen, daß damit der Zugriff auf die Radiosender unterdrückt werden kann, wodurch zwar leider keine Genres und Bitraten erkannt werden, die Verarbeitung aber wesentlich schneller geht. Deshalb gibt es dafür im fertigem Programm die Kommandozeilenoption -q. Das Flag m_cancelled wird gesetzt, wenn Shoutcast.com uns nichts mehr liefern mag und nur noch mit diesen ominösen „To much requests“- Dateien antwortet.

Sender-Iteration und Ausgabe als XML-Datei

Die einzige öffentliche Methode der Klasse ShoutcastRunner heißt GetStations. Ihr werden das zu durchsuchende Sender-Kennungen- Intervall in den Parametern start und count sowie die Kommandozeilenparameter übergeben:

    /// <summary>
    /// lies / prüfe ein Intervall von Radiostationen in www.shoutcast.com
    /// </summary>
    /// <param name="start">erste Senderkennung</param>
    /// <param name="count">Anzahl der Sender</param>
    /// <param name="verify">lies Metadaten direkt von Sender</param>
    /// <param name="verbose">wenn true, gib die Sender-Metadaten auf die Konsole aus</param>
    public void GetStations(int start, int count, bool verify, bool verbose)
    {
      m_verify = verify;
      m_verbose = verbose;
 
      // initialisiere XML-Datei
      m_writer = new XmlTextWriter(ListName, Encoding.Default);
      m_writer.Formatting = Formatting.Indented;
      m_writer.Indentation = 2;
      m_writer.WriteStartDocument();
      m_writer.WriteStartElement("StationsDataSet");
 
      // iteriere die Radiostations- Kennungen
      for (int i = start; i < start+count; ++i)
      {
        IcyHeader[] headers;
 
        if (m_cancelled) goto exit;
 
        // versuche Metadaten der Station zu finden
        if (GetPlaylist(i, out headers))
        {
          foreach (IcyHeader header in headers)
          {
            // im Erfolgsfall trage Sender ein
            m_writer.WriteStartElement("stations");
            m_writer.WriteElementString("name", header.name);
            m_writer.WriteElementString("genre", header.genre);
            m_writer.WriteElementString("typ", header.type);
            m_writer.WriteElementString("bitrate", header.bitrate);
            m_writer.WriteElementString("url", header.url);
            m_writer.WriteEndElement();
 
            // gebe Daten auch auf Console aus
            if (m_verbose)
            {
              Console.WriteLine(header.name);
              Console.WriteLine(header.genre);
              Console.WriteLine(String.Format("{0}, {1} kb/s", header.type, header.bitrate));
              Console.WriteLine(header.url);
              Console.WriteLine(header.notice1);
              Console.WriteLine(header.notice2);
            }
          }
        }
      }
    exit:
      // schließe XML-Datei
      m_writer.WriteEndDocument();
      m_writer.Close();
    }

Nachdem die Methode ein XmlTextWriter -Objekt erzeugt und initialisiert hat, iteriert eine Schleife die Senderkennungen des angegebenen Intervalls. Solange uns Shoutcast.com noch nicht rausgeworfen hat, verwenden wir in dieser Schleife die Methode GetPlaylist, um den Inhalt einer Playliste bzw. PLS-Datei als Array aus IceCast- Objekten zu erhalten. Eine foreach- Schleife iteriert dieses Array und schreibt für jedes Element ein entsprechendes XML-Element <stations> raus. Wurde die „verbose“-Kommandozeilen-Option (-v) benutzt, werden die gefundenen Senderdaten auch auf den Bildschirm ausgegeben. Wenn wir fertig sind oder Shoutcast.com uns nicht mehr mag, wird die XML-Datei fertiggestellt und geschlossen.

Lesen einer Playlist von Shoutcast.com

Der privaten Methode GetPlaylist wird in id eine vermutete Senderkennung übergeben und sie liefert in header ein Array aus IcyHeader-Strukturen zurück, wenn es den Sender gibt oder die Funktion gibt einfach nur false zurück.

    /// <summary>
    /// lies Playlist-Eintrag von www.shoutcast.com
    /// </summary>
    /// <param name="id">Senderkennung</param>
    /// <param name="header">zurückgelieferte Sender-Metadaten</param>
    /// <returns>true, wenn zumindest Playlist gefunden</returns>
    private bool GetPlaylist(int id, out IcyHeader[] headers)
    {
      string infoText;
      // bestimme shoutcast.com Abfrage-URL
      string url = String.Format(ShoutcastUrl, id.ToString());
      headers = null;
 
      // baue Request auf
      HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
      HttpWebResponse webResponse = null;
 
      try
      {
        // lies Playlist von www.shoutcast.com
        webResponse = (HttpWebResponse)webRequest.GetResponse();
 
        if (webResponse == null || webResponse.StatusCode != HttpStatusCode.OK)
        {
          // keine Antwort von shoutcast.com
          WriteErrorFor(id, webResponse.StatusDescription);
          return false;
        }
      }
      catch (Exception e)
      {
        // Fehler beim Webzugriff. Nicht Online?
        WriteErrorFor(id, e.Message);
        return false;
      }
 
      // lies die Playlist
      StreamReader reader = new StreamReader(webResponse.GetResponseStream(), Encoding.Default);
 
      try
      {
        infoText = reader.ReadToEnd();
        if (infoText.StartsWith("Too many requests"))
        {
          m_cancelled = true;
          throw new Exception("Shoutcast.com mag nicht mehr!");
        }
 
        // prüfe Playlist- Syntax, ob PLS-konform
        char[] delim = { '\n' };
        string[] lines = infoText.Split(delim);
 
        if (lines[0] != "[playlist]")
        {
          throw new Exception("ungültiges Playlist-Format");
        }
 
        delim[0] = '=';
        string[] assoc = lines[1].Split(delim);
        if (assoc[0] != "numberofentries")
        {
          throw new Exception("ungültiges Playlist-Format");
        }
 
        int stations = int.Parse(assoc[1]);
        if (stations == 0)
        {
          throw new Exception("ungenutzte Kennung");
        }
 
        string stationName = "";
        string stationUrl = "";
        headers = new IcyHeader[stations];
        int idx = 0;
 
        for (int i = 2; i < lines.Length - 1; ++i)
        {
 
          if (lines[i].StartsWith("File"))
          {
            stationUrl = (lines[i].Split(delim))[1];
          }
          if (lines[i].StartsWith("Title"))
          {
            stationName = (lines[i].Split(delim))[1];
          }
          if (lines[i].StartsWith("Length"))
          {
            IcyHeader header = new IcyHeader(stationName, stationUrl);
            // Daten vom Radiosender holen?
            if (m_verify)
            {
              if (!CheckStation(id, ref header))
              {
                WriteErrorFor(id, "sendet momentan nicht");
              }
            }
 
            headers[idx++] = header;
 
            // Erfolgsmeldung
            Console.WriteLine(String.Format("Station {0} gefunden: {1}", id, header.name));
          }
        }
 
        return true;
      }
      catch (Exception e)
      {
        // Syntaxfehler in M3U-Datei
        WriteErrorFor(id, e.Message);
        return false;
      }
      finally
      {
        reader.Close();
      }
    }

Zuerst wird die Senderkennung in die URL eingebaut und damit ein Webrequest durchgeführt. Beim Holen der Antwort mit GetResponse kann bereits eine WebException geworfen werden, wenn etwa Shoutcast.com gerade nicht zu erreichen ist oder man gar keine Online-Verbindung hat. Bei allen Fehlern wird nur die Funktion WriteErrorFor benutzt, um eine Fehlermeldung auf dem Bildschirm auszugeben. Wenn Shoutcast.com mit einer OK-Statusmeldung geantwortet hat, können wir die Playlist downloaden. Dazu verwenden wir ein Streamreader- Objekt, daß uns den Inhalt der kleinen PLS-Datei mit ReadToEnd direkt als String liefert.

Diesen String infoText vergleichen wir erst einmal mit Shoutcast.com's Rauswerf- Meldung und brechen mit Fehlermeldung ab, wenn wir keine vernünftigen Daten mehr zu erwarten haben. Die bis dahin in der XML-Datei gespeicherten Daten sind uns aber sicher.

Im Erfolgsfall zerlegen wir die Datei mit der Split Methode in Zeilen und prüfen die ersten beiden Zeilen auf Konformität zum PLS- „Standard“. Dabei erfahren wir auch über das Attribut numberofentries, über wieviele URLs dieser Sender sendet. Ein Wert von Null bedeutet dabei, daß es mit dieser Kennung gar keinen Sender gibt und wir brechen ab.

Andernfalls wissen wir, wie groß unser Ergebnis-Array werden muß und können es erzeugen und iterieren anschließend über die restlichen Zeilen der Playlist, die aus Title, File und den eigentlich überflüßigen Length Attributen besteht. Da letztere aber immer zuletzt kommen, kann man sie benutzen, um die Sender-Metadaten als IcyHeader zu erzeugen und im Ergebnis-Array abzuspeichern. Vorher können wir noch mit der Methode CheckStation die Radiosender direkt überprüfen bzw. die fehlenden Metadaten von ihnen abholen.

Holen der Sender-Daten direkt vom Webradio-Sender

Die Methode CheckStation übernimmt im Parameter id die Senderkennung, braucht sie aber eigentlich nur für die Fehlermeldungen. Wichtiger ist die per Referenz übergebene und mit Sender-URL vorinitialisierte IceHeader- Struktur. Eine Übergabe per Referenz ist hier wichtig, da Strukturen bei C# ja Wert-Typen sind. Die Methode CheckStation ergänzt im Erfolgsfall diese Struktur um das Genre und die Bitrate, im Fehlerfall wird stattdessen false zurückgegeben.

    /// <summary>
    /// greife auf Radiostation zu und liefere Metadaten in IcyHeader-Struktur zurück
    /// die Felder "name" und "url" müssen vor dem Aufruf gesetzt werden
    /// </summary>
    /// <param name="id">Senderkennung</param>
    /// <param name="header">IcyHeader, dessen Metadaten im Erfolgsfall ergänzt werden</param>
    /// <returns>true bei erfolgreichem Zugriff auf den Sender</returns>
    private bool CheckStation(int id, ref IcyHeader header)
    {
      // setze Request auf
      HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(header.url);
      webRequest.Timeout = 10000;
      HttpWebResponse webResponse = null;
 
      try
      {
        // Zugriff auf den Sender
        webResponse = (HttpWebResponse)webRequest.GetResponse();
 
        if (webResponse == null || webResponse.StatusCode != HttpStatusCode.OK)
        {
          // Sender antwortet nicht oder liefert Fehlercode
          WriteErrorFor(id, webResponse.StatusDescription);
          return false;
        }
      }
      catch (WebException e)
      {
        // Sender antwortet nicht
        WriteErrorFor(id, e.Message);
        return false;
      }
 
      // Sender hat geantwortet, lies Icy-Header-Daten aus
      header.name = webResponse.Headers["icy-name"];
      header.genre = webResponse.Headers["icy-genre"];
      header.bitrate = webResponse.Headers["icy-br"];
      header.url = webResponse.Headers["icy-url"];
      header.type = webResponse.Headers["Content-Type"];
      header.notice1 = webResponse.Headers["icy-notice1"];
      header.notice2 = webResponse.Headers["icy-notice2"];
 
      return true;
    }

Auch CheckStation verwendet wieder ein HttpWebRequest und ein HttpWebResponse Objekt. Als Response erhalten wir aber wegen dem Shoutcast-Protokoll die beschriebenen, „ungewöhnlichen“ Header-Daten. Hier sei nochmals darauf hingewiesen, daß ohne eine config-Datei HttpWebResponse überhaupt keine Daten liefert sondern mit einer WebException abbricht. Ansonsten erhalten wir eine WebException nur, wenn der Sender nicht ereichbar ist und brechen dann ab. Im Erfolgsfall können wir dagegen die Headerdaten in unsere IcyHeader- Struktur übernehmen.

Befehlszeilen-Optionen

Bleibt nur noch die Beschreibung der Main- Funktion, welches die Console-Anwendung startet. Main wertet eigentlich nur die Befehlszeilen- Parameter aus, ruft bei fehlerhaftem Aufruf ShowHelp auf oder erzeugt und erzeugt einen ShoutcastRunner und startet ihn mit GetStations:

    /// <summary>
    /// Parameter-Hilfe ausgeben
    /// </summary>
    static void ShowHelp()
    {
      Console.WriteLine("Aufruf: Shoutcast_runner [-sXXXX] [-nXXXX] [-v]\n");
      Console.WriteLine("wobei:");
      Console.WriteLine("  -sXXXX XXXX ist die Start-Senderkennung: 0..9999");
      Console.WriteLine("  -nXXXX XXXX ist die Anzahl der zu prüfenden Sender: 1..10000");
      Console.WriteLine("  -r die Erreichbarkeit des Senders wird geprüft und Daten werden ergänzt");
      Console.WriteLine("  -q Quick-Modus: nur die Playlisten werden gelesen, außer Sendername und URL keine Metadaten");
      Console.WriteLine("  -v Verbose: die Metadaten aller gefundenen Sender werden ausgegeben\n");
 
    }
 
    /// <summary>
    /// Hauptprogramm
    /// </summary>
    /// <param name="args">siehe oben</param>
    static void Main(string[] args)
    {
      ShoutcastRunner runner = new ShoutcastRunner();
 
      // Standardwerte
      int start = 0;
      int count = 10000;
      bool verify = true;
      bool verbose = false;
 
      // lies Kommandozeilen-Parameter
      foreach (string arg in args)
      {
        if (arg[0] == '-')
        {
          try
          {
            switch (Char.ToLower(arg[1]))
            {
              case 's':
                start = Int16.Parse(arg.Substring(2));
                break;
 
              case 'n':
                count = Int16.Parse(arg.Substring(2));
                break;
 
              case 'r':
                verify = true;
                break;
 
              case 'q':
                verify = false;
                break;
 
              case 'v':
                verbose = true;
                break;
 
              default:
                Console.WriteLine("ungültiger Parameter");
                ShowHelp();
                break;
            }
          }
          catch (Exception e)
          {
            // falscher Zahlenwert bei -s oder -n
            Console.WriteLine(e.Message);
            ShowHelp();
          }
        }
 
      }
 
      // go!
      runner.GetStations(start, count, verify, verbose);
    }
  }
}
start/themen/webradiosender-suchmaschine.txt · Zuletzt geändert: 2018/09/21 16:45 (Externe Bearbeitung)