Index: mythtv/themes/default-wide/music-ui.xml
===================================================================
--- mythtv/themes/default-wide/music-ui.xml	(revision 16380)
+++ mythtv/themes/default-wide/music-ui.xml	(working copy)
@@ -831,6 +831,159 @@
       </container>
    </window>
 
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>30</size>
+          <size:small>18</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="labels" face="Trebuchet MS">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="display" face="Trebuchet MS">
+          <color>#ffffff</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <size:small>14</size:small>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <container name="edit_container">
+
+         <area>0,0,1820,720</area>
+
+             <!--
+                    Labels
+            -->
+
+             <textarea name="title" draworder="1" align="center">
+                 <area>0,30,1280,50</area>
+                 <font>title</font>
+                 <value>Track Information</value>
+             </textarea>
+
+             <textarea name="station_label" draworder="1" align="right">
+                 <area>15,100,170,30</area>
+                 <font>labels</font>
+                 <value>Station:</value>
+             </textarea>
+
+             <textarea name="channel_label" draworder="1" align="right">
+                 <area>15,150,170,30</area>
+                 <font>labels</font>
+                 <value>Channel:</value>
+             </textarea>
+
+             <textarea name="url_label" draworder="1" align="right">
+                 <area>15,200,170,30</area>
+                 <font>labels</font>
+                 <value>URL:</value>
+             </textarea>
+
+             <textarea name="metaformat_label" draworder="1" align="right">
+                 <area>15,250,170,30</area>
+                 <font>labels</font>
+                 <value>Meta Format:</value>
+             </textarea>
+
+             <textarea name="genre_label" draworder="1" align="right">
+                 <area>15,300,170,30</area>
+                 <font>labels</font>
+                 <value>Genre:</value>
+             </textarea>
+
+             <textarea name="rating_label" draworder="1" align="right">
+                 <area>15,350,170,30</area>
+                 <font>labels</font>
+                 <value>Rating:</value>
+             </textarea>
+
+             <!--
+                    edits
+            -->
+
+             <remoteedit name="station_edit" draworder="1" align="left">
+                 <area>200,100,880,35</area>
+                 <font>display</font>
+             </remoteedit> 
+
+             <pushbutton name="searchstation_button" draworder="2">
+                 <position>1100,100</position>
+                 <image function="on" filename="blankbutton_on.png"> </image>
+                 <image function="off" filename="blankbutton_off.png"> </image>
+                 <image function="pushed" filename="blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <remoteedit name="channel_edit" draworder="1" align="left">
+                 <area>200,150,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="url_edit" draworder="1" align="left">
+                 <area>200,200,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="metaformat_edit" draworder="1" align="left">
+                 <area>200,250,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="genre_edit" draworder="1" align="left">
+                 <area>200,300,880,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <pushbutton name="searchgenre_button" draworder="2">
+                 <position>1100,300</position>
+                 <image function="on" filename="blankbutton_on.png"> </image>
+                 <image function="off" filename="blankbutton_off.png"> </image>
+                 <image function="pushed" filename="blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                 <filename>mm_rating.png</filename>
+                 <position>200,360</position>
+                 <orientation>LeftToRight</orientation>
+             </repeatedimage>
+
+             <selector name="rating_button" draworder="0">
+                 <area>1100,350,30,30</area>
+                 <font>display</font>
+                 <image function="on" filename="leftright_on.png"> </image>
+                 <image function="off" filename="leftright_off.png"> </image>
+                 <image function="pushed" filename="leftright_pushed.png"> </image>
+             </selector>
+
+
+             <!--
+                    Push buttons
+            -->
+
+             <textbutton name="done_button" draworder="0">
+                 <position>500,540</position>
+                 <font>display</font>
+                 <image function="on" filename="text_button_on.png"> </image>
+                 <image function="off" filename="text_button_off.png"> </image>
+                 <image function="pushed" filename="text_button_pushed.png"> </image>
+             </textbutton>
+
+      </container>
+
+   </window>
+
    <window name="cdripper">
 
        <font name="title" face="Arial">
Index: mythtv/libs/libmyth/output.cpp
===================================================================
--- mythtv/libs/libmyth/output.cpp	(revision 16380)
+++ mythtv/libs/libmyth/output.cpp	(working copy)
@@ -22,11 +22,8 @@
 
 
 void OutputListeners::error(const QString &e) {
-    QObject *object = firstListener();
-    while (object) {
-	QApplication::postEvent(object, new OutputEvent(e));
-	object = nextListener();
-    }
+    OutputEvent ev (e);
+    dispatch (ev);
 }
 
 void OutputListeners::addVisual(MythTV::Visual *v)
Index: mythtv/libs/libmyth/mythobservable.cpp
===================================================================
--- mythtv/libs/libmyth/mythobservable.cpp	(revision 16380)
+++ mythtv/libs/libmyth/mythobservable.cpp	(working copy)
@@ -12,50 +12,57 @@
 
 void MythObservable::addListener(QObject *listener)
 {
+    QMutexLocker locked(&m_mutex);
     if (m_listeners.find(listener) == -1)
         m_listeners.append(listener);
 }
 
 void MythObservable::removeListener(QObject *listener)
 {
+    QMutexLocker locked(&m_mutex);
     if (m_listeners.find(listener) != -1)
         m_listeners.remove(listener);
 }
 
-QObject* MythObservable::firstListener()
-{
-    return m_listeners.first();
-}
-
-QObject* MythObservable::nextListener()
-{
-    return m_listeners.next();
-}
-
 QPtrList<QObject> MythObservable::getListeners()
 {
+    QMutexLocker locked(&m_mutex);
     return m_listeners;
 }
 
 void MythObservable::dispatch(MythEvent &event)
 {
-    QObject *listener = firstListener();
-    while (listener)
+    // Copy the list and iterate on the copy, in case another thread
+    // modifies the list.
+
+    m_mutex.lock ();
+    QPtrList<QObject> listeners(m_listeners);
+    m_mutex.unlock ();
+
+    QPtrListIterator<QObject> it (listeners);
+    while (class QObject *listener = it.current ())
     {
         QApplication::postEvent(listener, event.clone());
-        listener = nextListener();
+        ++it;
     }
 }
 
 void MythObservable::dispatchNow(MythEvent &event)
 {
-    QObject *listener = firstListener();
-    while (listener)
+    // Copy the list and iterate on the copy, in case another thread
+    // modifies the list.
+
+    m_mutex.lock ();
+    QPtrList<QObject> listeners(m_listeners);
+    m_mutex.unlock ();
+
+    QPtrListIterator<QObject> it (listeners);
+    while (class QObject *listener = it.current ())
     {
         // Note: unlike postEvent, send event does not take
         // ownership of pointer and will not delete it.
         QApplication::sendEvent(listener, &event);
-        listener = nextListener();
+        ++it;
     }
 }
 
Index: mythtv/libs/libmyth/mythobservable.h
===================================================================
--- mythtv/libs/libmyth/mythobservable.h	(revision 16380)
+++ mythtv/libs/libmyth/mythobservable.h	(working copy)
@@ -2,6 +2,7 @@
 #define MYTHOBSERVABLE_H_
 
 #include <qptrlist.h>
+#include <qmutex.h>
 #include "mythexp.h"
 #include "mythevent.h"
 
@@ -61,47 +62,6 @@
     */
     void removeListener(QObject *listener);
 
-    /** \brief Begin iteration across listeners
-
-        If you simply need to iterate across the listeners, use \p
-        firstListener and \p nextListener to iterate across the
-        listeners. Ie. instead of 
-
-        \code
-        {
-            QPtrList<QObject> listeners = getListeners();
-            QObject *listener = listeners.first();
-            while (listener) {
-                // use listener...
-                listener = listeners.next();
-            }
-        }
-        \endcode
-
-        you can avoid the copy and just do
-
-        \code
-        {
-            QObject *listener = firstListener();
-            while (listener) {
-                // use listener...
-                listener = nextListener();
-            }
-        } 
-        \endcode
-
-        \returns pointer to the first listener, NULL if there are no listeners
-    */
-    QObject* firstListener();
-
-    /** \brief Continue iteration to the next listener
-
-        See firstListener. Returns NULL if there are no more listeners.
-
-        \returns pointer to the next listener, NULL if there are no more listeners
-    */
-    QObject* nextListener();
-
     /** \brief Get a copy of the list of listener
 
         If you need access to more than just iteration via
@@ -135,6 +95,7 @@
 
   private:
     QPtrList<QObject> m_listeners;
+    QMutex m_mutex;
 };
 
 #endif /* MYTHOBSERVABLE_H */
Index: mythplugins/mythmusic/mythmusic/playbackbox.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/playbackbox.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/playbackbox.cpp	(working copy)
@@ -20,14 +20,17 @@
 // MythMusic includes
 #include "metadata.h"
 #include "constants.h"
-#include "streaminput.h"
 #include "decoder.h"
+#include "decoderhandler.h"
 #include "playbackbox.h"
 #include "databasebox.h"
+#include "metaio.h"
 #include "mainvisual.h"
 #include "smartplaylist.h"
 #include "search.h"
 
+#define GET_REPO_ID(attrs) attrs->at(4)
+
 #ifndef USING_MINGW
 #include "cddecoder.h"
 #endif // USING_MINGW
@@ -104,8 +107,12 @@
     connect(volume_display_timer, SIGNAL(timeout()),
             this, SLOT(hideVolume()));
 
+    decoder_handler_progress_timer = new QTimer(this);
+    connect (decoder_handler_progress_timer, SIGNAL(timeout()), 
+             this, SLOT(operationProgressTimer()));
+    
+    // Figure out the shuffle mode
     setShuffleMode(gPlayer->getShuffleMode());
-
     resumemode = gPlayer->getResumeMode();
 
     setRepeatMode(gPlayer->getRepeatMode());
@@ -370,7 +377,7 @@
         }
         else if (action == "INFO")
             if (visualizer_status == 2) 
-                bannerToggle(curMeta);
+                bannerToggle(&displayMeta);
             else
                 showEditMetadataDialog();
         else if (action == "ESCAPE" && visualizer_status != 2)
@@ -546,6 +553,31 @@
     }
 }
 
+/*note:temporary fix for radio - Ideally, I'd like the metadata repo
+  add it's own stuff to the menu entry 
+*/
+void PlaybackBoxMusic::addRadioMenuEntries() 
+{
+    GenericTree *node = music_tree_list->getCurrentNode();
+
+    if (AllMusic::id_to_repo (node->getInt ()) != AllMusic::REPO_ID_RADIO) 
+        return;
+
+    playlist_popup->addButton(tr("Add Radio"), this,
+                              SLOT(showAddRadioStationDialog()));
+
+    if (node->isSelectable()) 
+        playlist_popup->addButton(tr("Remove Radio"), this,
+                                  SLOT(showRemoveRadioStationDialog()));
+
+    QLabel *splitter = playlist_popup->addLabel(" ", MythPopupBox::Small);
+    splitter->setLineWidth(2);
+    splitter->setFrameShape(QFrame::HLine);
+    splitter->setFrameShadow(QFrame::Sunken);
+    splitter->setMaximumHeight((int) (5 * hmult));
+    splitter->setMaximumHeight((int) (5 * hmult));
+}
+
 void PlaybackBoxMusic::handlePush(QString buttonname)
 {
     if (m_pushedButton)
@@ -592,6 +624,8 @@
     splitter->setMaximumHeight((int) (5 * hmult));
     splitter->setMaximumHeight((int) (5 * hmult));
 
+    addRadioMenuEntries();
+
     playlist_popup->addButton(tr("Search"), this,
                               SLOT(showSearchDialog()));
     playlist_popup->addButton(tr("From CD"), this,
@@ -600,16 +634,21 @@
                               SLOT(allTracks()));
     if (curMeta)
     {
-        playlist_popup->addButton(tr("Tracks by current Artist"), this,
-                                  SLOT(byArtist()));
-        playlist_popup->addButton(tr("Tracks from current Album"), this,
-                                  SLOT(byAlbum()));
-        playlist_popup->addButton(tr("Tracks from current Genre"), this,
-                                  SLOT(byGenre()));
-        playlist_popup->addButton(tr("Tracks from current Year"), this,
+        /*note: temporary fix for radio. Ideally, I'd like the
+          metadata repo add it's own stuff to the menur entry. 
+        */
+        if (curMeta->Format()!="cast") {
+            playlist_popup->addButton(tr("Tracks by current Artist"), this,
+                                      SLOT(byArtist()));
+            playlist_popup->addButton(tr("Tracks from current Album"), this,
+                                      SLOT(byAlbum()));
+            playlist_popup->addButton(tr("Tracks from current Genre"), this,
+                                      SLOT(byGenre()));
+            playlist_popup->addButton(tr("Tracks from current Year"), this,
                                   SLOT(byYear()));
-        playlist_popup->addButton(tr("Tracks with same Title"), this,
-                                  SLOT(byTitle()));
+            playlist_popup->addButton(tr("Tracks with same Title"), this,
+                                      SLOT(byTitle()));
+        }
     }
     
     playlist_popup->ShowPopup(this, SLOT(closePlaylistPopup()));
@@ -667,6 +706,22 @@
     }
 }
 
+void PlaybackBoxMusic::operationProgressTimer()
+{
+    operation_progress_count++;
+    decoderHandlerInfo(operation_name, 
+                       QString().fill('.', operation_progress_count));
+
+    // You get ten seconds...
+    if (operation_progress_count >= 10) 
+    {
+        gPlayer->stop();
+        MythPopupBox::showOkPopup(gContext->GetMainWindow(), 
+                                  statusString,
+                                  QString("Operation timed out"));
+    }
+}
+
 void PlaybackBoxMusic::showSearchDialog()
 {
    if (!playlist_popup)
@@ -686,6 +741,94 @@
     }
 }
 
+void PlaybackBoxMusic::showAddRadioStationDialog()
+{
+   if (!playlist_popup)
+        return;
+
+    closePlaylistPopup();
+
+    Metadata *meta = new Metadata ("http://", "", "", "", "");
+    meta->setArtist ("");
+    meta->setAlbum ("");
+    meta->setTitle ("");
+    meta->setGenre ("");
+    meta->setFormat("cast");
+
+    MythThemedDialog *dialog = meta->createEditorDialog ();
+    if (dialog->exec()) {
+        GenericTree *playlist_tree = gPlayer->getPlaylistTree();
+        gMusicData->all_music->addRadioTrack(meta);
+        /*note:temporary fix for radio
+
+          This is a really really poor way or inserting something into
+          the tree. It'd be way nicer to have a object managing
+          classes of metadata which could then do this.
+          
+         */
+        for (int ii = 0; ii < playlist_tree->childCount(); ii++)
+        {
+            GenericTree *t_node = playlist_tree->getChildAt(ii);
+
+            if (!t_node)
+                continue;
+
+            IntVector *attrs = t_node->getAttributes();
+            if (attrs && attrs->size() > 4) 
+            {
+                int counter = t_node->childCount();
+                QString title = meta->FormatArtist ();
+                title += " ~ ";
+                title += meta->Title();
+                meta->setAlbum(title);
+                GenericTree *sub = t_node->addNode(title, meta->ID(), true);
+                sub->setAttribute(0, 1);
+                sub->setAttribute(1, counter);
+                sub->setAttribute(2, rand());
+                sub->setAttribute(3, rand());
+                sub->setAttribute(4, 1);
+                t_node->sortByString();
+                music_tree_list->refresh();
+                break;
+            }
+        }
+    }
+
+    dialog->deleteLater ();
+}
+
+void PlaybackBoxMusic::showRemoveRadioStationDialog()
+{
+   if (!playlist_popup)
+        return;
+
+    closePlaylistPopup();
+
+    GenericTree *node = music_tree_list->getCurrentNode();
+    Metadata *meta = gMusicData->all_music->getMetadata(node->getInt());
+    
+    bool answer = MythPopupBox::showOkCancelPopup(gContext->GetMainWindow(), 
+                                                  QString("Remove Station"),
+                                                  QString("Do you want to remove\n"
+                                                          "%1 ~ %2?").
+                                                  arg(meta->Artist()).
+                                                  arg(meta->Title()),
+                                                  true);
+
+    if (!answer)
+        return;
+
+    meta->removeFromDatabase();    
+    
+    // Move away from the current node...
+    if (!music_tree_list->moveUp(false)) 
+        music_tree_list->moveDown(false);
+
+    node->getParent()->removeNode(node);
+    music_tree_list->refresh();
+}
+
+
 void PlaybackBoxMusic::byArtist()
 {
     if (!playlist_popup || !curMeta)
@@ -977,19 +1120,19 @@
 
 void PlaybackBoxMusic::showEditMetadataDialog()
 {
-    if (!curMeta)
-    {
+    GenericTree *node = music_tree_list->getCurrentNode();
+    Metadata *editMeta = 0;
+
+    /* note: temporary fix for radio, get the appropriate editor for repo */
+    editMeta = gMusicData->all_music->getMetadata(node->getInt());
+
+    if(!editMeta)
         return;
-    }
 
     // store the current track metadata in case the track changes
     // while we show the edit dialog
-    GenericTree *node = music_tree_list->getCurrentNode();
-    Metadata *editMeta = gMusicData->all_music->getMetadata( node->getInt() );
-
-    EditMetadataDialog editDialog(editMeta, gContext->GetMainWindow(),
-                      "edit_metadata", "music-", "edit metadata");
-    if (kDialogCodeRejected != editDialog.exec())
+    MythThemedDialog *editDialog = editMeta->createEditorDialog ();
+    if (kDialogCodeRejected != editDialog->exec())
     {
         MythBusyDialog *busy = new MythBusyDialog(
             QObject::tr("Rebuilding music tree"));
@@ -1000,9 +1143,11 @@
         // Get a reference to the current track
         QValueList <int> branches_to_current_node;
 
-        QValueList <int> *a_route;
-        a_route = music_tree_list->getRouteToActive();
-        branches_to_current_node = *a_route;
+        QValueList <int> *a_route (music_tree_list->getRouteToActive());
+        if (a_route)
+        {
+            branches_to_current_node = *a_route;
+        }
 
         // reload music
         gMusicData->all_music->save();
@@ -1044,6 +1189,8 @@
         busy->Close();
         busy->deleteLater();
     }
+
+    editDialog->deleteLater ();
 }
 
 void PlaybackBoxMusic::checkForPlaylists()
@@ -1277,9 +1424,7 @@
     if (gPlayer->isPlaying())
         gPlayer->stop();
 
-    if (curMeta)
-        playfile = curMeta->Filename();
-    else
+    if (!curMeta)
     {
         // Perhaps we can descend to something playable?
         wipeTrackInfo();
@@ -1293,24 +1438,8 @@
     }
 
     gPlayer->setCurrentNode(music_tree_list->getCurrentNode());
-    gPlayer->playFile(playfile);
+    gPlayer->play ();
 
-    currentTime = 0;
-
-    mainvisual->setDecoder(gPlayer->getDecoder());
-    mainvisual->setOutput(gPlayer->getOutput());
-    mainvisual->setMetadata(curMeta);
-
-    if (gPlayer->isPlaying())
-    {
-        if (resumemode == MusicPlayer::RESUME_EXACT && 
-                gContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
-        {
-            seek(gContext->GetNumSetting("MusicBookmarkPosition", 0));
-            gContext->SaveSetting("MusicBookmarkPosition", 0);
-        }
-    }
-
     bannerEnable(curMeta, show_album_art);
 }
 
@@ -1391,7 +1520,7 @@
     }
 }
 
-void PlaybackBoxMusic::setTrackOnLCD(Metadata *mdata)
+void PlaybackBoxMusic::setTrackOnLCD(const Metadata *mdata)
 {
     LCD *lcd = LCD::Get();
     if (!lcd || !mdata)
@@ -1412,8 +1541,8 @@
 {
     gPlayer->stop();
 
-    mainvisual->setDecoder(0);
-    mainvisual->setOutput(0);
+    mainvisual->setDecoder(NULL);
+    mainvisual->setOutput(NULL);
     mainvisual->deleteMetadata();
 
     QString time_string = getTimeString(maxTime, 0);
@@ -1611,6 +1740,8 @@
         music_tree_list->setVisualOrdering(1);
     music_tree_list->refresh();
 
+    // refreshing the music_tree causes the LCD to get redrawn
+    // with the tree, so force back to the music screen.
     if (gPlayer->isPlaying())
         setTrackOnLCD(curMeta);
 }
@@ -1977,10 +2108,15 @@
 
             break;
         }
+        case DecoderEvent::Decoding:
+        {
+            statusString = tr("Stream decoding.");
+            displayMeta = *curMeta;
+            break;
+        }
         case DecoderEvent::Stopped:
         {
             statusString = tr("Stream stopped.");
-
             break;
         }
         case DecoderEvent::Finished:
@@ -2007,6 +2143,54 @@
                                       .arg(*dxe->errorMessage()));
             break;
         }
+        case DecoderHandlerEvent::Ready:
+        {
+            decoderHandlerReady();
+            break;
+        }
+        case DecoderHandlerEvent::OperationStart:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            decoderHandlerOperationStart(*dxe->getMessage());
+            break;
+        }
+        case DecoderHandlerEvent::OperationStop:
+        {
+            decoderHandlerOperationStop();
+            break;
+        }
+        case DecoderHandlerEvent::Error:
+        {
+            statusString = tr("Input error.");
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            VERBOSE(VB_IMPORTANT, QString ("%1 %2").arg (statusString).arg(*dxe->getMessage()));
+            MythPopupBox::showOkPopup(gContext->GetMainWindow(), 
+                                      statusString,
+                                      QString("MythMusic has encountered the following error:\n%1")
+                                      .arg(*dxe->getMessage()));
+            stopAll();
+            break;
+        }
+        case DecoderHandlerEvent::Info:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            Metadata tmp (*curMeta);
+            tmp.setArtist("");
+            tmp.setTitle(*dxe->getMessage());            
+            updateTrackInfo(&tmp);
+            break;
+        }
+        case DecoderHandlerEvent::Meta:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            displayMeta = *dxe->getMetadata();
+            updateTrackInfo(&displayMeta);
+
+            if (visualizer_status > 0 && cycle_visualizer)
+                CycleVisualizer();
+
+            break;
+        }
     }
 
     QWidget::customEvent(event);
@@ -2051,6 +2235,7 @@
     }
 
     setTrackOnLCD(mdata);
+    bannerEnable(mdata);            
 }
 
 void PlaybackBoxMusic::showAlbumArtImage(Metadata *mdata)
@@ -2126,6 +2311,7 @@
             activenode = music_tree_list->getCurrentNode();
         }
 
+        VERBOSE(VB_PLAYBACK, QString("playing node %1").arg(node_int));
         curMeta = gMusicData->all_music->getMetadata(node_int);
 
         updateTrackInfo(curMeta);
@@ -2429,3 +2615,37 @@
 
     return time_string;
 }
+
+
+void PlaybackBoxMusic::decoderHandlerReady(void)
+{    
+    mainvisual->setMetadata(curMeta);
+    bannerEnable(curMeta, show_album_art);
+}
+
+void PlaybackBoxMusic::decoderHandlerInfo(const QString &message, 
+                                          const QString &submessage)
+{
+    Metadata tmp (*curMeta);
+
+    tmp.setArtist(submessage);
+    tmp.setTitle(message);
+
+    updateTrackInfo(&tmp);
+}
+
+void PlaybackBoxMusic::decoderHandlerOperationStart(const QString &op)
+{
+    operation_name = op;
+    operation_progress_count = 0;
+    decoderHandlerInfo(operation_name, 
+                       QString().fill('.', operation_progress_count));
+    decoder_handler_progress_timer->start(1000);
+}
+
+void PlaybackBoxMusic::decoderHandlerOperationStop()
+{
+    Metadata tmp (*curMeta);
+    updateTrackInfo(&tmp);
+    decoder_handler_progress_timer->stop();
+}
Index: mythplugins/mythmusic/mythmusic/mythmusic.pro
===================================================================
--- mythplugins/mythmusic/mythmusic/mythmusic.pro	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/mythmusic.pro	(working copy)
@@ -40,6 +40,7 @@
 HEADERS += editmetadata.h smartplaylist.h search.h genres.h
 HEADERS += treebuilders.h importmusic.h directoryfinder.h
 HEADERS += filescanner.h libvisualplugin.h musicplayer.h miniplayer.h
+HEADERS += shoutcast.h decoderhandler.h editradiometadata.h pls.h
 
 SOURCES += cddecoder.cpp cdrip.cpp decoder.cpp 
 SOURCES += flacdecoder.cpp flacencoder.cpp maddecoder.cpp main.cpp
@@ -56,6 +57,7 @@
 SOURCES += avfdecoder.cpp editmetadata.cpp smartplaylist.cpp search.cpp
 SOURCES += treebuilders.cpp importmusic.cpp directoryfinder.cpp
 SOURCES += filescanner.cpp libvisualplugin.cpp musicplayer.cpp miniplayer.cpp
+SOURCES += shoutcast.cpp decoderhandler.cpp editradiometadata.cpp pls.cpp 
 
 macx {
     SOURCES -= cddecoder.cpp
Index: mythplugins/mythmusic/mythmusic/decoderhandler.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/decoderhandler.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/decoderhandler.cpp	(revision 0)
@@ -0,0 +1,556 @@
+#include "decoderhandler.h"
+#include "decoder.h"
+#include "metadata.h"
+#include "streaminput.h"
+#include "shoutcast.h"
+
+#include <qapplication.h>
+#include <qurl.h>
+#include <qurloperator.h>
+
+#include <mythtv/httpcomms.h>
+#include <mythtv/mythcontext.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <assert.h>
+
+/**********************************************************************/
+
+DecoderHandlerEvent::DecoderHandlerEvent(const Metadata &m)
+    : MythEvent(Meta), m_msg(NULL), m_meta(NULL)
+{ 
+    m_meta = new Metadata(m);
+}
+
+DecoderHandlerEvent::~DecoderHandlerEvent()
+{
+    delete m_msg;
+    delete m_meta;
+}
+
+DecoderHandlerEvent* DecoderHandlerEvent::clone() 
+{ 
+    DecoderHandlerEvent *result = new DecoderHandlerEvent(*this);
+
+    if (m_msg)
+        result->m_msg = new QString(*m_msg);
+
+    if (m_meta)
+        result->m_meta = new Metadata(*m_meta);
+
+    return result;
+}
+
+/**********************************************************************/
+
+DecoderIOFactory::DecoderIOFactory(DecoderHandler *parent) 
+{
+    m_handler = parent;
+}
+
+DecoderIOFactory::~DecoderIOFactory()
+{
+}
+
+void DecoderIOFactory::doConnectDecoder(const QString &format) 
+{ 
+    m_handler->doOperationStop();
+    m_handler->doConnectDecoder(getUrl(), format); 
+}
+
+Decoder *DecoderIOFactory::getDecoder()
+{
+    return m_handler->getDecoder();
+}
+
+void DecoderIOFactory::doFailed(const QString &message) 
+{ 
+    m_handler->doOperationStop();
+    m_handler->doFailed(getUrl(), message); 
+}
+
+void DecoderIOFactory::doInfo(const QString &message) 
+{ 
+    m_handler->doInfo(message);
+}
+
+void DecoderIOFactory::doOperationStart(const QString &name)
+{
+    m_handler->doOperationStart(name);
+}
+
+void DecoderIOFactory::doOperationStop(void)
+{
+    m_handler->doOperationStop();
+}
+
+/**********************************************************************/
+
+DecoderIOFactoryFile::DecoderIOFactoryFile(DecoderHandler *parent) 
+    : DecoderIOFactory(parent), m_input (NULL)
+{
+}
+
+DecoderIOFactoryFile::~DecoderIOFactoryFile () 
+{
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryFile::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void 
+DecoderIOFactoryFile::start()
+{
+    QString sourcename = getMetadata().Filename();    
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Opening Local File %1").arg(sourcename));
+    m_input = new QFile(sourcename);
+    doConnectDecoder(getUrl());
+}
+
+
+/**********************************************************************/
+
+DecoderIOFactoryUrl::DecoderIOFactoryUrl(DecoderHandler *parent) : DecoderIOFactory(parent) 
+{
+    /* Yes, this is pretty lame, it doesn't throttle the download or
+       anything, but just blindly downloads the entire file. 
+       However, it supports whatever QUrlOperator supports and you should
+       see it as an example that you can enhance...
+    */
+    m_url_op = new QUrlOperator;
+    m_input = new QFile("/tmp/mythmusic.tmp");
+    m_input->open(IO_ReadOnly);
+    m_output = new QFile("/tmp/mythmusic.tmp");
+    m_output->open(IO_WriteOnly);
+    
+    connect(m_url_op, SIGNAL(finished(QNetworkOperation*)), 
+            this, SLOT(finished(QNetworkOperation*)));;
+    connect(m_url_op, SIGNAL(start(QNetworkOperation*)), 
+            this, SLOT(start(QNetworkOperation*)));;
+    connect(m_url_op, SIGNAL(data(const QByteArray&, QNetworkOperation*)), 
+            this, SLOT(data(const QByteArray&, QNetworkOperation*)));;
+}
+
+DecoderIOFactoryUrl::~DecoderIOFactoryUrl () 
+{
+    doClose();
+    
+    m_url_op->deleteLater();
+    m_output->remove();
+
+    delete m_output;
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryUrl::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void DecoderIOFactoryUrl::start()
+{
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Url %1").arg(getUrl().toString()));
+
+    m_started = false;
+
+    doOperationStart("Fetching remote file");
+    
+    m_url_op->get(getUrl());
+}
+
+void DecoderIOFactoryUrl::stop()
+{
+    doClose();
+}
+
+void DecoderIOFactoryUrl::finished(QNetworkOperation *op)
+{
+    m_output->close();
+
+    if (op->state() != QNetworkProtocol::StDone) 
+    {
+        doFailed("Cannot retrieve remote file.");
+        return;
+    }
+    
+    if (!m_started)
+        doStart();
+}
+
+void DecoderIOFactoryUrl::start(QNetworkOperation*)
+{
+}
+
+void DecoderIOFactoryUrl::data(const QByteArray & data, QNetworkOperation*)
+{
+    m_output->writeBlock(data);
+    if (!m_started && m_input->size () > DecoderIOFactory::DefaultPrebufferSize) 
+        doStart();
+}
+
+void DecoderIOFactoryUrl::doStart()
+{
+    doConnectDecoder(getUrl());
+    m_started = true;
+}
+
+void DecoderIOFactoryUrl::doClose()
+{
+    if (m_input->isOpen())
+        m_input->close();
+    if (m_output->isOpen())
+        m_output->close();
+}
+
+/**********************************************************************/
+
+/* I've left this here since it used to exist... Don't even know what
+ * mqp is, much less how to test it... 
+ */
+
+DecoderIOFactoryMqp::DecoderIOFactoryMqp(DecoderHandler *parent) : DecoderIOFactory(parent) 
+{
+    m_input = NULL;
+}
+
+DecoderIOFactoryMqp::~DecoderIOFactoryMqp () 
+{
+    delete m_input;
+}
+
+QIODevice* DecoderIOFactoryMqp::takeInput () 
+{
+    QIODevice *result = m_input;
+    m_input = NULL;
+    return result;
+}
+
+void DecoderIOFactoryMqp::start()
+{
+    VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Mqp %1").arg(getUrl().toString()));
+    StreamInput streaminput(getUrl());
+    streaminput.setup();
+    m_input = streaminput.socket();
+    doConnectDecoder(getUrl());
+}
+
+void DecoderIOFactoryMqp::stop()
+{
+    m_input->close();
+}
+
+/**********************************************************************/
+
+DecoderHandler::DecoderHandler() 
+    : m_state (STOPPED), 
+      m_io_factory (NULL), 
+      m_decoder (NULL), 
+      m_op (false), 
+      m_redirects (0)
+{
+}
+
+DecoderHandler::~DecoderHandler() 
+{
+    stop();
+}
+
+void DecoderHandler::start(Metadata *mdata) 
+{
+    m_state = LOADING;
+
+    m_playlist.clear();
+    m_meta = mdata;
+    m_playlist_pos = -1;
+    m_redirects = 0;
+    
+    QUrl url(mdata->Filename());
+    bool result = createPlaylist(url);
+    
+    if (m_state == LOADING && result) 
+    {
+        for (int ii = 0; ii < m_playlist.size(); ii++) 
+            VERBOSE(VB_PLAYBACK, QString("Track %1 = %2").
+                    arg(ii).
+                    arg(m_playlist.get(ii)->File()));
+        next();
+    } else {
+        if (m_state != STOPPED) {
+            doFailed(url, "Could not get playlist");
+        }
+    }
+}
+
+void DecoderHandler::error(const QString &e) 
+{
+    QString *str = new QString(e.utf8());
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, str);
+    dispatch(ev);
+}
+
+bool DecoderHandler::done() 
+{
+    if (m_state == STOPPED)
+        return true;
+
+    if (m_playlist_pos + 1 >= m_playlist.size()) 
+    {
+        m_state = STOPPED;
+        return true;
+    }
+
+    return false;
+}
+
+bool DecoderHandler::next() 
+{
+    if (done())
+        return false;
+
+    if (m_meta && m_meta->Format() == "cast") {
+        m_playlist_pos = rand () % m_playlist.size ();
+    } else {
+        m_playlist_pos++;
+    }
+
+    PlayListFileEntry *entry = m_playlist.get(m_playlist_pos);
+    QUrl url(entry->File());
+
+    VERBOSE(VB_PLAYBACK, QString("Now playing '%1'").arg(url.toString()));
+
+    deleteIOFactory();
+    createIOFactory(url);
+
+    if (! haveIOFactory()) 
+        return false;
+
+    getIOFactory()->addListener(this);
+    getIOFactory()->setUrl(url);
+    getIOFactory()->setMeta(m_meta);
+    getIOFactory()->start();
+    m_state = ACTIVE;
+
+    return true;
+}
+
+void DecoderHandler::stop()
+{
+    VERBOSE(VB_PLAYBACK, QString("Stopping decoder"));
+
+    if (m_decoder && m_decoder->running()) {
+        m_decoder->lock();
+        m_decoder->stop();
+        m_decoder->unlock();
+    }
+
+    if (m_decoder) {
+        m_decoder->lock();
+        m_decoder->cond()->wakeAll();
+        m_decoder->unlock();
+    }
+
+    if (m_decoder) {
+        m_decoder->wait();
+        delete m_decoder->input ();
+        delete m_decoder;        
+        m_decoder = NULL;
+    }
+
+    deleteIOFactory ();
+    doOperationStop();
+
+    m_state = STOPPED;
+}
+
+void DecoderHandler::customEvent(QCustomEvent *qevent)
+{
+    if (class DecoderHandlerEvent *event = dynamic_cast<DecoderHandlerEvent*>(qevent)) {
+        // Proxy all DecoderHandlerEvents
+        return dispatch(*event);    
+    }
+}
+
+bool DecoderHandler::createPlaylist(const QUrl &url)
+{
+    QString extension = url.fileName().right(4).lower();
+    VERBOSE (VB_NETWORK, QString ("File %1 has extension %2").arg (url.fileName()).arg(extension));
+    if (extension == ".pls" || extension == ".m3u")
+        if (url.isLocalFile()) 
+            return createPlaylistFromFile(url);
+        else
+            return createPlaylistFromRemoteUrl(url);
+    
+    return createPlaylistForSingleFile(url);
+}
+
+bool DecoderHandler::createPlaylistForSingleFile(const QUrl &url)
+{
+    PlayListFileEntry *entry = new PlayListFileEntry;
+
+    if (url.isLocalFile())
+        entry->setFile(url.dirPath()+'/'+url.fileName());
+    else
+        entry->setFile(url.toString());
+
+    m_playlist.add(entry);
+
+    return m_playlist.size() > 0;
+}
+
+bool DecoderHandler::createPlaylistFromFile(const QUrl &url)
+{
+    QFile f(url.dirPath() + "/" + url.fileName());
+    f.open(IO_ReadOnly);
+    QTextStream stream(&f);
+    
+    if (PlayListFile::parse(&m_playlist, &stream) < 0) 
+        return false;
+
+    return m_playlist.size() > 0;
+}
+
+bool DecoderHandler::createPlaylistFromRemoteUrl(const QUrl &url) 
+{
+    HttpComms comms;
+    QUrl _url(url);
+
+    if (url.hasUser() && url.hasPassword()) {
+       VERBOSE(VB_NETWORK, QString("Using usename and password"));
+       HttpComms::Credentials c(url.user(), url.password());
+       comms.setCredentials(c, HttpComms::CRED_WEB);
+    }
+    VERBOSE(VB_NETWORK, QString("Retrieving playlist from '%1'").arg(_url.toString()));
+
+    doOperationStart("Retrieving playlist");
+    comms.request(_url, 10000, false);    
+    while(!comms.isDone()) 
+    {
+        qApp->processEvents(500);
+    }
+    doOperationStop();
+
+    VERBOSE(VB_NETWORK, QString("Retrieving playlist from '%1' = %2 (%3)").
+            arg(_url.toString()).
+            arg(comms.getStatusCode ()).
+            arg(comms.isTimedout()?"timed out":"not timed out"));
+
+    if (comms.isTimedout()) 
+        return false;
+
+    if (comms.getStatusCode() == 302 ||
+        comms.getStatusCode() == 301) 
+    {
+        QString redir = comms.getRedirectedURL();
+        m_redirects++;
+        if (m_redirects > MaxRedirects) {
+            VERBOSE(VB_IMPORTANT, QString ("Too many redirections."));
+            return false;
+        }
+        return createPlaylistFromRemoteUrl(redir);
+    }
+
+    if (comms.getStatusCode() != 200)
+        return false;
+
+    QBuffer buffer(comms.getRawData());
+    buffer.open(IO_ReadOnly);
+    QTextStream stream(&buffer);
+    
+    bool result = PlayListFile::parse(&m_playlist, &stream) > 0;
+    return result;
+}
+
+void DecoderHandler::doConnectDecoder(const QUrl &url, const QString &format) 
+{
+    if (m_decoder && !m_decoder->factory()->supports(format)) {
+        delete m_decoder;
+        m_decoder = NULL;
+    }
+
+    if (! m_decoder) {
+        if ((m_decoder = Decoder::create(format, NULL, NULL, true)) == NULL) {
+            doFailed(url, "No decoder for this format");
+            return;
+        }
+    }
+
+    m_decoder->setInput(getIOFactory()->takeInput());
+    m_decoder->setFilename(url.toString());
+
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Ready);
+    dispatch(ev);
+}
+
+void DecoderHandler::doFailed(const QUrl &url, const QString &message)
+{
+    printf("mythmusic: unsupported fileformat: %s\n", url.toString().ascii ());
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Error, new QString (message));
+    dispatch(ev);
+}
+
+void DecoderHandler::doInfo(const QString &message)
+{
+    DecoderHandlerEvent ev(DecoderHandlerEvent::Info, new QString (message));
+    dispatch(ev);
+}
+
+void DecoderHandler::doOperationStart(const QString &name)
+{
+    m_op = true;
+    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStart, new QString (name));
+    dispatch(ev);
+}
+
+void DecoderHandler::doOperationStop(void)
+{
+    if (!m_op)
+        return;
+
+    m_op = false;
+    DecoderHandlerEvent ev(DecoderHandlerEvent::OperationStop);
+    dispatch(ev);
+}
+
+void DecoderHandler::createIOFactory(const QUrl &url)
+{
+    PlayListFile::test();
+
+    if (haveIOFactory()) 
+        deleteIOFactory();
+
+    if (url.isLocalFile()) {
+        m_io_factory = new DecoderIOFactoryFile(this);
+    }
+    else
+    {
+        if (url.protocol() == "mqp" && !url.host().isNull()) 
+            m_io_factory = new DecoderIOFactoryMqp(this);
+        else if (m_meta && m_meta->Format() == "cast")
+            m_io_factory = new DecoderIOFactoryShoutCast(this);
+        else
+            m_io_factory = new DecoderIOFactoryUrl(this);
+    }
+}
+
+void DecoderHandler::deleteIOFactory(void)
+{
+    if (! haveIOFactory())
+        return;
+
+    if (m_state == ACTIVE)
+        m_io_factory->stop ();
+
+    m_io_factory->removeListener(this);
+    m_io_factory->disconnect();
+    m_io_factory->deleteLater();
+    //delete m_io_factory;
+    m_io_factory = NULL;
+}
Index: mythplugins/mythmusic/mythmusic/editradiometadata.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/editradiometadata.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/editradiometadata.cpp	(revision 0)
@@ -0,0 +1,355 @@
+#include <mythtv/mythcontext.h>
+#include <mythtv/mythdbcon.h>
+#include <qdir.h>
+#include "editradiometadata.h"
+#include "metadata.h"
+#include "decoder.h"
+#include "genres.h"
+
+EditRadioMetadataDialog::EditRadioMetadataDialog(Metadata *source_metadata,
+                                                 MythMainWindow *parent,
+                                                 QString window_name,
+                                                 QString theme_filename,
+                                                 const char* name)
+    :MythThemedDialog(parent, window_name, theme_filename, name)
+{
+    // make a copy so we can abandon changes
+    m_metadata = new Metadata(*source_metadata);
+    m_sourceMetadata = source_metadata;
+    wireUpTheme();
+    fillWidgets();
+    assignFirstFocus();
+}
+
+void EditRadioMetadataDialog::fillWidgets()
+{
+    if (station_edit)
+    {
+        station_edit->setText(m_metadata->Artist());
+    }
+
+    if (channel_edit)
+    {
+        channel_edit->setText(m_metadata->Title());
+    }
+
+    if (url_edit)
+    {
+        url_edit->setText(m_metadata->Filename());
+    }
+
+    if (metaformat_edit)
+    {
+        metaformat_edit->setText(m_metadata->CompilationArtist());
+    }
+
+    if (genre_edit)
+    {
+        genre_edit->setText(m_metadata->Genre());
+    }
+
+    if (rating_image)
+    {
+        rating_image->setRepeat(m_metadata->Rating());
+    }
+}
+
+void EditRadioMetadataDialog::incRating(bool up_or_down)
+{
+    if (up_or_down)
+        m_metadata->incRating();
+    else
+        m_metadata->decRating();
+
+    fillWidgets();
+}
+
+void EditRadioMetadataDialog::keyPressEvent(QKeyEvent *e)
+{
+    bool handled = false;
+
+    QStringList actions;
+    gContext->GetMainWindow()->TranslateKeyPress("Video", e, actions);
+
+    for (unsigned int i = 0; i < actions.size() && !handled; i++)
+    {
+        QString action = actions[i];
+        handled = true;
+
+        if (action == "UP")
+            nextPrevWidgetFocus(false);
+        else if (action == "DOWN")
+            nextPrevWidgetFocus(true);
+        else if (action == "LEFT")
+        {
+            if (getCurrentFocusWidget() == rating_button)
+            {
+                rating_button->push();
+                incRating(false);
+            }
+        }
+        else if (action == "RIGHT")
+        {
+            if (getCurrentFocusWidget() == rating_button)
+            {
+                rating_button->push();
+                incRating(true);
+            }
+        }
+        else if (action == "SELECT")
+        {
+            activateCurrent();
+        }
+        else if (action == "0")
+        {
+            if (done_button)
+                done_button->push();
+        }
+        else if (action == "1")
+        {
+        }
+        else
+            handled = false;
+    }
+
+    if (!handled)
+        MythThemedDialog::keyPressEvent(e);
+}
+
+void EditRadioMetadataDialog::wireUpTheme()
+{
+    station_edit = getUIRemoteEditType("station_edit");
+    if (station_edit)
+    {
+        station_edit->createEdit(this);
+        connect(station_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    channel_edit = getUIRemoteEditType("channel_edit");
+    if (channel_edit)
+    {
+        channel_edit->createEdit(this);
+        connect(channel_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    url_edit = getUIRemoteEditType("url_edit");
+    if (url_edit)
+    {
+        url_edit->createEdit(this);
+        connect(url_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+    
+    metaformat_edit = getUIRemoteEditType("metaformat_edit");
+    if (metaformat_edit)
+    {
+        metaformat_edit->createEdit(this);
+        connect(metaformat_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+         
+    genre_edit = getUIRemoteEditType("genre_edit");
+    if (genre_edit)
+    {
+        genre_edit->createEdit(this);
+        connect(genre_edit, SIGNAL(loosingFocus()), this, SLOT(editLostFocus()));
+    }
+        
+    rating_image = getUIRepeatedImageType("rating_image");
+    
+    searchstation_button = getUIPushButtonType("searchstation_button");
+    if (searchstation_button)
+    {
+        connect(searchstation_button, SIGNAL(pushed()), this, SLOT(searchStation()));
+    }
+
+    searchgenre_button = getUIPushButtonType("searchgenre_button");
+    if (searchgenre_button)
+    {
+        connect(searchgenre_button, SIGNAL(pushed()), this, SLOT(searchGenre()));
+    }
+
+    done_button = getUITextButtonType("done_button");
+    if (done_button)
+    {
+        done_button->setText(tr("Done"));
+        connect(done_button, SIGNAL(pushed()), this, SLOT(showSaveMenu()));
+    }
+
+    rating_button = getUISelectorType("rating_button");
+    if (rating_button)
+    {
+        
+    }
+
+    buildFocusList();
+}
+
+void EditRadioMetadataDialog::editLostFocus()
+{
+    UIRemoteEditType *whichEditor = (UIRemoteEditType *) getCurrentFocusWidget();
+    
+    if (whichEditor == station_edit)
+    {
+        m_metadata->setArtist(station_edit->getText());
+    }
+    else if (whichEditor == channel_edit)
+    {
+        m_metadata->setTitle(channel_edit->getText());
+    }
+    else if (whichEditor == url_edit)
+    {
+        m_metadata->setFilename(url_edit->getText());
+    }
+    else if (whichEditor == metaformat_edit)
+    {
+        m_metadata->setCompilationArtist(metaformat_edit->getText());
+    }
+    else if (whichEditor == genre_edit)
+    {
+        m_metadata->setGenre(genre_edit->getText());
+    }
+}
+
+bool EditRadioMetadataDialog::showList(QString caption, QString &value)
+{
+    bool res = false;
+    
+    MythSearchDialog *searchDialog = new MythSearchDialog(gContext->GetMainWindow(), "");
+    searchDialog->setCaption(caption);
+    searchDialog->setSearchText(value);
+    searchDialog->setItems(searchList);
+    if (searchDialog->ExecPopup() == 0)
+    {
+        value = searchDialog->getResult();
+        res = true;
+    }
+    
+    searchDialog->deleteLater ();
+    setActiveWindow();
+    
+    return res;     
+}
+
+void EditRadioMetadataDialog::fillSearchList(QString field, QString table)
+{
+    searchList.clear();
+    
+    QString querystr;
+    querystr = QString("SELECT DISTINCT %1 FROM %2 ORDER BY %3").
+        arg(field).arg(table).arg(field);
+         
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.exec(querystr);
+
+    if (query.isActive() && query.size())
+    {
+        while (query.next())
+        {
+            searchList << QString::fromUtf8(query.value(0).toString());
+        }
+    }         
+}
+    
+void EditRadioMetadataDialog::searchStation()
+{
+    QString s;
+    
+    fillSearchList("station", "music_radios");
+    
+    s = m_metadata->Artist();
+    if (showList(tr("Select a Station"), s))
+    {
+        m_metadata->setArtist(s);
+        fillWidgets();
+    }
+}
+
+void EditRadioMetadataDialog::searchGenre()
+{
+    QString s;
+
+    // load genre list
+    searchList.clear();
+    for (int x = 0; x < genre_table_size; x++)
+        searchList.push_back(QString(genre_table[x]));
+    searchList.sort();
+
+    s = m_metadata->Genre();
+    if (showList(tr("Select a Genre"), s))
+    {
+        m_metadata->setGenre(s);
+        fillWidgets();
+    }
+   
+}
+
+void EditRadioMetadataDialog::closeDialog()
+{
+    cancelPopup();
+    done(0);  
+}
+
+void EditRadioMetadataDialog::showSaveMenu()
+{
+    popup = new MythPopupBox(gContext->GetMainWindow(), "Menu");
+
+    QLabel *label = popup->addLabel(tr("Save Changes?"), MythPopupBox::Large, false);
+    label->setAlignment(Qt::AlignCenter | Qt::WordBreak);
+
+    QButton *topButton = popup->addButton(tr("Save"), this,
+                         SLOT(saveToDatabase()));
+    popup->addButton(tr("Exit/Do Not Save"), this,
+                         SLOT(closeDialog()));
+    popup->addButton(tr("Cancel"), this, SLOT(cancelPopup()));
+
+    popup->ShowPopup(this, SLOT(cancelPopup()));
+
+    topButton->setFocus();
+}
+
+void EditRadioMetadataDialog::cancelPopup()
+{
+  if (!popup)
+      return;
+
+  popup->hide();
+  popup->deleteLater ();
+  popup = NULL;
+  setActiveWindow();
+}
+
+bool EditRadioMetadataDialog::verifyEntries()
+{
+    // TODO: check valid text in url and metaformat...
+    return true;
+}
+
+void EditRadioMetadataDialog::saveNewToDatabase()
+{
+    cancelPopup();
+
+    if (!verifyEntries()) 
+        return;
+
+    m_metadata->dumpToDatabase();        
+    *m_sourceMetadata = m_metadata;
+
+    done(1);
+}
+
+void EditRadioMetadataDialog::saveToDatabase()
+{
+    cancelPopup();
+
+    if (m_metadata->ID() == 0) 
+        return saveNewToDatabase();
+
+    m_metadata->dumpToDatabase();        
+    *m_sourceMetadata = m_metadata;
+
+    done(1);
+}
+
+EditRadioMetadataDialog::~EditRadioMetadataDialog()
+{
+    delete m_metadata;
+}
Index: mythplugins/mythmusic/mythmusic/music-ui.xml
===================================================================
--- mythplugins/mythmusic/mythmusic/music-ui.xml	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/music-ui.xml	(working copy)
@@ -849,6 +849,155 @@
       </container>
    </window>
 
+   <window name="edit_radiometadata">
+
+      <font name="title" face="Arial">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>24</size>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="labels" face="Arial">
+          <color>#ffff00</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+      <font name="display" face="Arial">
+          <color>#ffffff</color>
+          <dropcolor>#000000</dropcolor>
+          <size>18</size>
+          <shadow>3,3</shadow>
+          <bold>yes</bold>
+      </font>
+
+       <container name="edit_container">
+             <area>0,0,800,600</area>
+
+             <textarea name="title" draworder="1" align="center">
+                 <area>0,15,800,50</area>
+                 <font>title</font>
+                 <value>Track Information</value>
+             </textarea>
+
+             <!--
+                    Labels
+            -->
+
+
+             <textarea name="station_label" draworder="1" align="right">
+                 <area>15,70,170,30</area>
+                 <font>labels</font>
+                 <value>Station:</value>
+             </textarea>
+
+             <textarea name="channel_label" draworder="1" align="right">
+                 <area>15,110,170,30</area>
+                 <font>labels</font>
+                 <value>Channel:</value>
+             </textarea>
+
+             <textarea name="url_label" draworder="1" align="right">
+                 <area>15,150,170,30</area>
+                 <font>labels</font>
+                 <value>URL:</value>
+             </textarea>
+
+             <textarea name="metaformat_label" draworder="1" align="right">
+                 <area>15,190,170,30</area>
+                 <font>labels</font>
+                 <value>Meta Format:</value>
+             </textarea>
+
+             <textarea name="genre_label" draworder="1" align="right">
+                 <area>15,230,170,30</area>
+                 <font>labels</font>
+                 <value>Genre:</value>
+             </textarea>
+
+             <textarea name="rating_label" draworder="1" align="right">
+                 <area>15,350,170,30</area>
+                 <font>labels</font>
+                 <value>Rating:</value>
+             </textarea>
+
+             <!--
+                    edits
+            -->
+
+             <remoteedit name="station_edit" draworder="1" align="left">
+                 <area>195,70,525,35</area>
+                 <font>display</font>
+             </remoteedit> 
+
+             <pushbutton name="searchstation_button" draworder="2">
+                 <position>725,70</position>
+                 <image function="on" filename="blankbutton_on.png"> </image>
+                 <image function="off" filename="blankbutton_off.png"> </image>
+                 <image function="pushed" filename="blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <remoteedit name="channel_edit" draworder="1" align="left">
+                 <area>195,110,525,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="url_edit" draworder="1" align="left">
+                 <area>195,150,525,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="metaformat_edit" draworder="1" align="left">
+                 <area>195,190,525,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <remoteedit name="genre_edit" draworder="1" align="left">
+                 <area>195,230,525,35</area>
+                 <font>display</font>
+             </remoteedit>
+
+             <pushbutton name="searchgenre_button" draworder="2">
+                 <position>725,230</position>
+                 <image function="on" filename="blankbutton_on.png"> </image>
+                 <image function="off" filename="blankbutton_off.png"> </image>
+                 <image function="pushed" filename="blankbutton_pushed.png"> </image>
+             </pushbutton>
+
+             <repeatedimage name="rating_image" draworder="1" fleximage="no">
+                 <filename>mm_rating.png</filename>
+                 <position>190,360</position>
+                 <orientation>LeftToRight</orientation>
+             </repeatedimage>
+
+             <selector name="rating_button" draworder="0">
+                 <area>420,350,30,30</area>
+                 <font>display</font>
+                 <image function="on" filename="leftright_on.png"> </image>
+                 <image function="off" filename="leftright_off.png"> </image>
+                 <image function="pushed" filename="leftright_pushed.png"> </image>
+             </selector>
+
+
+             <!--
+                    Push buttons
+            -->
+
+             <textbutton name="done_button" draworder="0">
+                 <position>350,540</position>
+                 <font>display</font>
+                 <image function="on" filename="text_button_on.png"> </image>
+                 <image function="off" filename="text_button_off.png"> </image>
+                 <image function="pushed" filename="text_button_pushed.png"> </image>
+             </textbutton>
+
+       </container>
+    </window>
+
    <window name="cdripper">
 
        <font name="title" face="Arial">
Index: mythplugins/mythmusic/mythmusic/miniplayer.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/miniplayer.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/miniplayer.cpp	(working copy)
@@ -40,7 +40,7 @@
     if (gPlayer->getCurrentMetadata())
     {
         m_maxTime = gPlayer->getCurrentMetadata()->Length() / 1000;
-        updateTrackInfo(gPlayer->getCurrentMetadata());
+        updateTrackInfo(gPlayer->getDisplayMetadata());
 
         if (!gPlayer->isPlaying())
         {
@@ -309,7 +309,7 @@
             if (gPlayer->getCurrentMetadata())
             {
                 m_maxTime = gPlayer->getCurrentMetadata()->Length() / 1000;
-                updateTrackInfo(gPlayer->getCurrentMetadata());
+                updateTrackInfo(gPlayer->getDisplayMetadata());
             }
             break;
         }
@@ -398,6 +398,26 @@
         {
             break;
         }
+        case DecoderEvent::Decoding:
+        {
+            updateTrackInfo(gPlayer->getDisplayMetadata());
+            break;
+        }          
+        case DecoderHandlerEvent::Info:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            Metadata tmp (*gPlayer->getDisplayMetadata());
+            tmp.setArtist("");
+            tmp.setTitle(*dxe->getMessage());            
+            updateTrackInfo(&tmp);
+            break;
+        }
+        case DecoderHandlerEvent::Meta:
+        {
+            DecoderHandlerEvent *dxe = (DecoderHandlerEvent*)event;
+            updateTrackInfo(dxe->getMetadata());
+            break;
+        }
     }
     QObject::customEvent(event);
 }
@@ -587,7 +607,7 @@
 {
     m_showingInfo = false;
     LCD *lcd = LCD::Get();
-    Metadata * mdata = gPlayer->getCurrentMetadata();
+    Metadata * mdata = gPlayer->getDisplayMetadata();
 
     if (lcd && mdata)
     {
Index: mythplugins/mythmusic/mythmusic/aacdecoder.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/aacdecoder.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/aacdecoder.cpp	(working copy)
@@ -175,17 +175,14 @@
         }
     }
 
-
     //
     //  See if we can seek
     //
-
     if (!input()->at(0))
     {
-      error("couldn't seek in input");
+        error("couldn't seek in input");
         return false;
     }
-    
 
     //
     //  figure out if it's an mp4 file (aac in a mp4 wrapper a la Apple) or
@@ -357,9 +354,9 @@
 
     // If max_bitrate == avg_bitrate, then file is fixed bitrate
     if (mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) ==
-	mp4ff_get_max_bitrate(mp4_input_file, aac_track_number))
+        mp4ff_get_max_bitrate(mp4_input_file, aac_track_number))
     {
-	bitrate = mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) / 1000;
+        bitrate = mp4ff_get_avg_bitrate(mp4_input_file, aac_track_number) / 1000;
     }
     
     //
@@ -506,7 +503,7 @@
 
             if (output())
             {
-		output()->Drain();
+                output()->Drain();
             }
 
             done = TRUE;
@@ -547,7 +544,7 @@
                                                    );
             
                 sample_count = frame_info.samples;
-
+                
                 //
                 //  Munge the samples into the "right" format and send them
                 //  to the output (after checking we're not going to exceed
@@ -576,24 +573,24 @@
                     output_bytes += sample_count * 2;
                     if (output())
                     {
-			// If source is VBR, bitrate == 0
-			if (bitrate)
-			{
-			    output()->SetSourceBitrate(bitrate);
-			} 
+                        // If source is VBR, bitrate == 0
+                        if (bitrate)
+                        {
+                            output()->SetSourceBitrate(bitrate);
+                        } 
                         else 
                         {
-			    output()->SetSourceBitrate(
-				(int) ((float) (frame_info.bytesconsumed * 8) /
-				       (frame_info.samples / 
-				        frame_info.num_front_channels) 
-				* frame_info.samplerate) / 1000);
-			}
-
+                            output()->SetSourceBitrate(
+                                (int) ((float) (frame_info.bytesconsumed * 8) /
+                                       (frame_info.samples / 
+                                        frame_info.num_front_channels) 
+                                       * frame_info.samplerate) / 1000);
+                        }
+                        
                         flush();
                     }
                 }
-            
+                
                 if (buffer)
                 {
                     free(buffer);
Index: mythplugins/mythmusic/mythmusic/decoder.h
===================================================================
--- mythplugins/mythmusic/mythmusic/decoder.h	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/decoder.h	(working copy)
@@ -37,8 +37,7 @@
 
     ~DecoderEvent()
     {
-        if (error_msg)
-            delete error_msg;
+        delete error_msg;
     }
 
     const QString *errorMessage() const { return error_msg; }
Index: mythplugins/mythmusic/mythmusic/visualize.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/visualize.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/visualize.cpp	(working copy)
@@ -17,7 +17,6 @@
 #include <qpainter.h>
 #include <qpixmap.h>
 #include <qimage.h>
-#include <qdir.h>
 #include <qurl.h>
 
 // mythtv
@@ -452,7 +451,7 @@
         (void)pluginName;
         return new AlbumArt(parent);
     }
-}AlbumArtFactory;
+} AlbumArtFactory;
 
 Blank::Blank()
     : VisualBase(true)
@@ -511,6 +510,8 @@
 {
     number_of_squares = 16;
     fake_height = number_of_squares * analyzerBarWidth;
+    startHue = 220;
+    targetHue = 360;
 }
 
 Squares::~Squares()
@@ -524,6 +525,11 @@
     size = newsize;
 }
 
+bool Squares::process(VisualNode *node) {
+    rotateHues ();
+    return Spectrum::process (node);
+}
+
 void Squares::drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h) 
 {
     double r, g, b, per;
@@ -555,9 +561,22 @@
     g = clamp(g, 255.0, 0.0);
     b = clamp(b, 255.0, 0.0);
 
-    p->fillRect (x, y, w, h, QColor (int(r), int(g), int(b)));
+    p->fillRect (x, y, w, h, QColor((int)r, (int)g, (int)b));
 }
 
+void Squares::rotateHues () {
+    targetHue ++;
+    if (targetHue >= 360)
+        targetHue = 0;
+
+    startHue ++;
+    if (startHue >= 360)
+        startHue = 0;
+
+    startColor = QColor (startHue, 255, 255, QColor::Hsv);
+    targetColor = QColor (targetHue, 255, 255, QColor::Hsv);
+}
+
 bool Squares::draw(QPainter *p, const QColor &back)
 {
     p->fillRect (0, 0, size.width (), size.height (), back);
Index: mythplugins/mythmusic/mythmusic/mainvisual.h
===================================================================
--- mythplugins/mythmusic/mythmusic/mainvisual.h	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/mainvisual.h	(working copy)
@@ -115,6 +115,7 @@
     void showBanner(Metadata *meta, bool fullScreen, int visMode, int showTime = 8000);
     void hideBanner();
     bool bannerIsShowing(void) {return bannerTimer->isActive(); }
+	void clearInformation();
 
     static QStringList Visualizations();
 
@@ -158,6 +159,7 @@
     QString info;
     QPixmap info_pixmap;
     QRect   displayRect;
+	QColor  color;
 };
 
 class StereoScope : public VisualBase
Index: mythplugins/mythmusic/mythmusic/metadata.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/metadata.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/metadata.cpp	(working copy)
@@ -5,6 +5,7 @@
 #include <qregexp.h> 
 #include <qdatetime.h>
 #include <qdir.h>
+#include <qurl.h>
 
 using namespace std;
 
@@ -15,16 +16,39 @@
 
 // mythmusic
 #include "metadata.h"
+#include "editmetadata.h"
+#include "editradiometadata.h"
 #include "metaiotaglib.h"
 #include "treebuilders.h"
 #include "playlist.h"
 
+unsigned int AllMusic::REPO_ID_DB = 0;
+unsigned int AllMusic::REPO_ID_CD = 1;
+unsigned int AllMusic::REPO_ID_RADIO = 2;
 
 // this is the global MusicData object shared thoughout MythMusic
 MusicData  *gMusicData = NULL;
 
 static QString thePrefix = "the ";
 
+QVariant queryField (const QString &v, const QString &table, 
+                     const QString &q, const QVariant &qv)
+{
+    ;
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare(QString("SELECT %1 FROM %2 WHERE %3 = :Q;").arg(v).arg(table).arg(q));
+    query.bindValue(":Q", qv);
+    
+    if (!query.exec()) {
+        MythContext::DBError(QString("Get %1 from %2 for %3 = %4").arg(v).arg(table).arg(q).arg(qv.toString()), query);           
+    } else if (query.isActive() && query.size() > 0)
+        while (query.next()) {
+            return query.value(0);
+        }                
+    return QVariant ();
+}
+
 bool operator==(const Metadata& a, const Metadata& b)
 {
     if (a.Filename() == b.Filename())
@@ -78,7 +102,7 @@
     query.bindValue(":RATING", m_rating);
     query.bindValue(":PLAYCOUNT", m_playcount);
     query.bindValue(":LASTPLAY", m_lastplay);
-    query.bindValue(":ID", m_id);
+    query.bindValue(":ID", AllMusic::id_to_id(ID()));
 
     if (!query.exec())
         MythContext::DBError("music persist", query);
@@ -88,12 +112,7 @@
 {
     if (m_format == "cast") 
     {
-        int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
-
-        if (artist_cmp == 0) 
-            return Title().lower().localeAwareCompare(other->Title().lower());
-
-        return artist_cmp;
+        return compare_cast(other);
     }
     else
     {
@@ -106,6 +125,36 @@
     }
 }
 
+int Metadata::compare_cast(Metadata *other) 
+{
+    int artist_cmp = Artist().lower().localeAwareCompare(other->Artist().lower());
+    
+    if (artist_cmp == 0) 
+        return Title().lower().localeAwareCompare(other->Title().lower());
+    
+    return artist_cmp;
+}
+
+MythThemedDialog *Metadata::createEditorDialog(void) 
+{
+    /*note:temporary fix for radio*/
+    if (Format() == "cast")
+        return createEditorDialog_cast();
+
+    return new EditMetadataDialog(this, gContext->GetMainWindow(),
+                                  "edit_metadata", "music-", 
+                                  "edit metadata");
+    
+}
+
+MythThemedDialog *Metadata::createEditorDialog_cast(void) 
+{
+    return new EditRadioMetadataDialog(this, gContext->GetMainWindow(),
+                                       "edit_radiometadata", "music-", 
+                                       "edit metadata");
+    
+}
+
 bool Metadata::isInDatabase()
 {
     bool retval = false;
@@ -150,7 +199,7 @@
         m_year = query.value(5).toInt();
         m_tracknum = query.value(6).toInt();
         m_length = query.value(7).toInt();
-        m_id = query.value(8).toUInt();
+        setID(query.value(8).toUInt());
         m_rating = query.value(9).toInt();
         m_playcount = query.value(10).toInt();
         m_lastplay = query.value(11).toString();
@@ -165,6 +214,10 @@
 
 void Metadata::dumpToDatabase()
 {
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") 
+        return dumpToDatabase_cast();
+
     QString sqlfilepath(m_filename);
     if (!sqlfilepath.contains("://"))
     {
@@ -210,7 +263,7 @@
             m_directoryid = query.lastInsertId().toInt();
         }
     }
-
+    
     if (m_artistid < 0)
     {
         // Load the artist id
@@ -339,7 +392,7 @@
 
     // We have all the id's now. We can insert it.
     QString strQuery;
-    if (m_id < 1)
+    if (AllMusic::id_to_id(ID()) < 1)
     {
         strQuery = "INSERT INTO music_songs ( directory_id,"
                    " artist_id, album_id,  name,         genre_id,"
@@ -384,15 +437,15 @@
     query.bindValue(":FORMAT", m_format);
     query.bindValue(":DATE_MOD", QDateTime::currentDateTime());
 
-    if (m_id < 1)
+    if (AllMusic::id_to_id(ID()) < 1)
         query.bindValue(":DATE_ADD",  QDateTime::currentDateTime());
     else
-        query.bindValue(":ID", m_id);
+        query.bindValue(":ID", AllMusic::id_to_id(ID()));
 
     query.exec();
 
-    if (m_id < 1 && query.isActive() && 1 == query.numRowsAffected())
-        m_id = query.lastInsertId().toInt();
+    if (AllMusic::id_to_id(ID()) < 1 && query.isActive() && 1 == query.numRowsAffected())
+        setID (query.lastInsertId().toInt());
 
     if (! m_albumart.empty())
     {
@@ -402,7 +455,7 @@
             query.prepare("SELECT albumart_id FROM music_albumart WHERE "
                           "song_id=:SONGID AND imagetype=:TYPE;");
             query.bindValue(":TYPE", (*it).imageType);
-            query.bindValue(":SONGID", m_id);
+            query.bindValue(":SONGID", AllMusic::id_to_id(ID()));
             query.exec();
 
             if (query.next())
@@ -425,7 +478,7 @@
 
             query.bindValue(":FILENAME", (*it).description);
             query.bindValue(":TYPE", (*it).imageType);
-            query.bindValue(":SONGID", m_id);
+            query.bindValue(":SONGID", AllMusic::id_to_id(ID()));
             query.bindValue(":EMBED", 1);
 
             query.exec();
@@ -446,6 +499,60 @@
     }
 }
 
+void Metadata::dumpToDatabase_cast()
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("SELECT url FROM music_radios WHERE "
+                  "( ( station = :STATION ) AND "
+                  "( channel = :CHANNEL ) AND "
+                  "( format = :FORMAT ) AND "
+                  "( url = :URL ) ); ");
+    query.bindValue(":STATION", m_artist.utf8());
+    query.bindValue(":CHANNEL", m_title.utf8());
+    query.bindValue(":URL", m_filename);
+    query.bindValue(":FORMAT", m_format);
+    
+    if (query.exec() && query.isActive() && query.size() > 0)
+        return;
+    
+    QString strQuery;
+    if (AllMusic::id_to_id(ID()) < 1) {
+        strQuery = "INSERT INTO music_radios "
+                   "(station,    channel,    url,        genre, "
+                   " metaformat, format,     rating            )"
+                   " VALUES "
+                   "(:STATION,     :CHANNEL,  :URL,     :GENRE,   "
+                  " :METAFORMAT,  :FORMAT,   :RATING );";
+    } else {
+        strQuery = "UPDATE music_radios          "
+                   "SET station   = :STATION,    "
+                   "    channel   = :CHANNEL,    "
+                   "    url       = :URL,        "
+                   "    genre     = :GENRE,      "
+                   "    metaformat= :METAFORMAT, "
+                   "    rating    = :RATING,     " 
+                   "    format    = :FORMAT      " 
+                   "WHERE intid = :ID;";
+    }
+    query.prepare(strQuery);
+    query.bindValue(":STATION", m_artist.utf8());
+    query.bindValue(":CHANNEL", m_title.utf8());
+    query.bindValue(":URL", m_filename);
+    query.bindValue(":GENRE", m_genre.utf8());
+    query.bindValue(":METAFORMAT", m_compilation_artist);
+    query.bindValue(":RATING", m_rating);
+    query.bindValue(":FORMAT", m_format);                      
+
+    if (AllMusic::id_to_id(ID()) >= 1) {
+        query.bindValue(":ID", AllMusic::id_to_id(ID()));
+    }
+
+    query.exec();
+
+    if (AllMusic::id_to_id(ID()) < 1 && query.isActive() && 1 == query.numRowsAffected())
+        setID(query.lastInsertId().toInt());
+}
+
 // Default values for formats
 // NB These will eventually be customizable....
 QString Metadata::m_formatnormalfileartist      = "ARTIST";
@@ -526,7 +633,6 @@
         m_title = m_filename;
     if (m_genre == "")
         m_genre = QObject::tr("Unknown Genre");
-
 }
 
 inline void Metadata::setCompilationFormatting(bool cd)
@@ -585,7 +691,58 @@
     return m_formattedtitle;
 }
 
+QString Metadata::FormatInfo()
+{       
+    QString result;
+    if (Format() == "cast") {
+        result = QString("\"%1\"\n"
+                         "%2\n"
+                         "Now playing on %3").
+            arg(FormatTitle()).
+            arg(FormatArtist()).
+            arg(Album());
+    } else {
+        result = QString("\"%1\"\n"
+                         "%2 - %3").
+        arg(FormatTitle()).
+            arg(FormatArtist()).
+            arg(Album());
+        
+        if (Year() > 0) 
+            result += " (" + QString::number (Year()) + ")";
+    }
 
+    return result;
+}
+
+void Metadata::removeFromDatabase()
+{
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") 
+        return removeFromDatabase_cast();
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("DELETE FROM musicmetadata "
+                  "WHERE intid = :ID;");
+    query.bindValue(":ID", AllMusic::id_to_id(ID()));
+
+    if (!query.exec())
+         MythContext::DBError("Remove musicmetadata", query);
+}
+
+void Metadata::removeFromDatabase_cast()
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    query.prepare("DELETE FROM music_radios "
+                  "WHERE intid = :ID;");
+    query.bindValue(":ID", AllMusic::id_to_id(ID()));
+
+    if (!query.exec())
+         MythContext::DBError("Remove music_radios", query);
+}
+
 void Metadata::setField(const QString &field, const QString &data)
 {
     if (field == "artist")
@@ -630,6 +787,13 @@
         *data = FormatTitle();
     else if (field == "genre")
         *data = m_genre;
+    else if (field == "x-cast-logourl") 
+    {
+        /*note:temporary fix for radio*/
+        if (Format() == "cast") {
+            *data = queryField ("logourl", "music_radios", "intid", AllMusic::id_to_id(ID())).toString ();
+        } 
+    }
     else
     {
         VERBOSE(VB_IMPORTANT, QString("Something asked me to return data "
@@ -638,6 +802,23 @@
     }
 }
 
+void Metadata::setAlbumArt(const QByteArray &data) {
+    /*note:temporary fix for radio*/
+    if (Format() == "cast") {
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("UPDATE music_radios          "
+                      "SET logocached = :LOGO       "
+                      "WHERE intid = :ID;");
+        query.bindValue(":LOGO", data);
+        if (AllMusic::id_to_id(ID()) >= 1) {
+            query.bindValue(":ID", AllMusic::id_to_id(ID()));
+        }
+        
+        if (!query.exec())
+            MythContext::DBError("Store albumart", query);
+    }
+}
+
 void Metadata::decRating()
 {
     if (m_rating > 0)
@@ -728,6 +909,13 @@
 
 QImage Metadata::getAlbumArt(void)
 {
+    if (Format() == "cast") {
+        QVariant v = queryField ("logocached", "music_radios", "intid", AllMusic::id_to_id(ID()));
+        if (v.isValid()) {
+            return QImage (v.toByteArray());
+        }
+    }
+
     AlbumArtImages albumArt(this);
 
     QImage image;
@@ -762,6 +950,13 @@
 
     QImage image;
 
+    if (Format() == "cast") {
+        QVariant v = queryField ("logocached", "music_radios", "intid", AllMusic::id_to_id(ID()));
+        if (v.isValid()) {
+            image = QImage (v.toByteArray());
+        }
+    }
+    else
     if (albumArt.isImageAvailable(type))
     {
         AlbumArtImage albumart_image = albumArt.getImage(type);
@@ -775,10 +970,51 @@
     return image;
 }
 
-// static function to get a metadata object given a track id
+static Metadata* getRadioMetadataFromID (int id) {
+    QString the_query = 
+        "SELECT "
+        "url, station, channel, genre, intid, rating, format, metaformat "
+        "FROM music_radios "
+        "WHERE intid = :ID;";
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare (the_query);
+    query.bindValue (":ID", id);
+    query.exec ();
+    
+    if (query.isActive() && query.size() > 0) {
+        query.next ();
+        Metadata *md = new Metadata(QString::fromUtf8(query.value(0).toString()),
+                                    QString::fromUtf8(query.value(1).toString()),
+                                    "", "",
+                                    QString::fromUtf8(query.value(2).toString()),
+                                    QString::fromUtf8(query.value(3).toString()),
+                                    0, 0, 0,
+                                    query.value(4).toInt(), query.value(5).toInt());
+        
+            // abuse...
+        md->setCompilationArtist(QString::fromUtf8(query.value(7).toString()));            
+        md->setFormat(query.value(6).toString());
+        md->setID (id);
+        md->setRepoID (AllMusic::REPO_ID_RADIO);
+        md->setAlbum(QString("%1 ~ %2").arg(md->FormatArtist()).arg(md->FormatTitle()));
+        return md;
+    }
+    return NULL;
+}
+
+ // static function to get a metadata object given a track id
 // it's upto the caller to delete the returned object when finished
-Metadata *Metadata::getMetadataFromID(int id)
+Metadata *Metadata::getMetadataFromID(Metadata::IdType an_id)
 {
+    Metadata::IdType repo = AllMusic::id_to_repo (an_id);
+    Metadata::IdType id = AllMusic::id_to_id (an_id);
+    
+    VERBOSE(VB_PLAYBACK, QString("getMetadataFromID (%1) = %2/%3").arg (an_id).arg (repo).arg (id));
+
+    if (repo == AllMusic::REPO_ID_RADIO) {
+        return getRadioMetadataFromID (AllMusic::id_to_id (id));
+    }
     Metadata *meta = NULL;
 
     QString aquery = "SELECT music_songs.song_id, music_artists.artist_name, music_comp_artists.artist_name AS compilation_artist, "
@@ -837,6 +1073,7 @@
             query.value(12).toString(), //lastplay
             (query.value(13).toInt() > 0), //compilation
             query.value(14).toString()); //format
+        meta->setRepoID (AllMusic::REPO_ID_DB);
     }
     else
     {
@@ -883,13 +1120,14 @@
     startLoading();
 
     m_all_music.setAutoDelete(true);
-
+    m_all_radios.setAutoDelete(true);
     m_last_listed = -1;
 }
 
 AllMusic::~AllMusic()
 {
     m_all_music.clear();
+    m_all_radios.clear();
 
     delete m_root_node;
 
@@ -944,7 +1182,6 @@
 void AllMusic::resync()
 {
     m_done_loading = false;
-
     QString aquery = "SELECT music_songs.song_id, music_artists.artist_name, music_comp_artists.artist_name AS compilation_artist, "
                      "music_albums.album_name, music_songs.name, music_genres.genre, music_songs.year, "
                      "music_songs.track, music_songs.length, CONCAT_WS('/', "
@@ -960,7 +1197,6 @@
                      "ORDER BY music_songs.song_id;";
 
     QString filename, artist, album, title;
-
     MSqlQuery query(MSqlQuery::InitCon());
     query.exec(aquery);
 
@@ -974,28 +1210,16 @@
     {
         while (query.next())
         {
-            filename = QString::fromUtf8(query.value(9).toString());
+            QString filename = QString::fromUtf8(query.value(9).toString());
             if (!filename.contains("://"))
                 filename = m_startdir + filename;
 
-            artist = QString::fromUtf8(query.value(1).toString());
-            if (artist.isEmpty())
-                artist = QObject::tr("Unknown Artist");
-
-            album = QString::fromUtf8(query.value(3).toString());
-            if (album.isEmpty())
-                album = QObject::tr("Unknown Album");
-
-            title = QString::fromUtf8(query.value(4).toString());
-            if (title.isEmpty())
-                title = QObject::tr("Unknown Title");
-
             Metadata *temp = new Metadata(
                 filename,
-                artist,
+                QString::fromUtf8(query.value(1).toString()),
                 QString::fromUtf8(query.value(2).toString()),
-                album,
-                title,
+                QString::fromUtf8(query.value(3).toString()),
+                QString::fromUtf8(query.value(4).toString()),
                 QString::fromUtf8(query.value(5).toString()),
                 query.value(6).toInt(),
                 query.value(7).toInt(),
@@ -1006,10 +1230,11 @@
                 query.value(12).toString(), //lastplay
                 (query.value(13).toInt() > 0), //compilation
                 query.value(14).toString()); //format
-
             //  Don't delete temp, as PtrList now owns it
             m_all_music.append(temp);
 
+            temp->setRepoID (AllMusic::REPO_ID_DB);
+
             // compute max/min playcount,lastplay for all music
             if (query.at() == 0)
             { // first song
@@ -1044,7 +1269,7 @@
     music_map.clear();
     while ( (map_add = an_iterator.current()) != 0 )
     {
-        music_map[map_add->ID()] = map_add; 
+        music_map[AllMusic::id_to_id(map_add->ID())] = map_add; 
         ++an_iterator;
     }
 
@@ -1055,9 +1280,57 @@
     //printTree();
     sortTree();
     //printTree();
+
+    resync_radios();
+
     m_done_loading = true;
 }
 
+/*note:temporary fix for radio*/
+void AllMusic::resync_radios(void)
+{
+    m_radio_map.clear();
+    m_all_radios.clear();
+
+    QString the_query = 
+        "SELECT "
+        "url, station, channel, genre, intid, rating, format, metaformat "
+        "FROM "
+        "music_radios ORDER BY station, channel";
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    
+    if (query.exec(the_query) && query.isActive() && query.size() > 0) {
+        while (query.next()) {
+            Metadata *md = new Metadata(QString::fromUtf8(query.value(0).toString()),
+                                        QString::fromUtf8(query.value(1).toString()),
+                                        "", "",
+                                        QString::fromUtf8(query.value(2).toString()),
+                                        QString::fromUtf8(query.value(3).toString()),
+                                        0, 0, 0,
+                                        query.value(4).toInt(), query.value(5).toInt());
+
+            // abuse...
+            md->setCompilationArtist(QString::fromUtf8(query.value(7).toString()));            
+            md->setFormat(query.value(6).toString());
+            md->setRepoID (REPO_ID_RADIO);
+            addRadioTrack(md);
+        } 
+        m_all_radios.sort ();
+    }    
+}
+
+void AllMusic::addRadioTrack(Metadata *meta)
+{    
+    QString title = meta->FormatArtist ();
+    title += " ~ ";
+    title += meta->FormatTitle();
+    meta->setAlbum(title);
+
+    m_all_radios.append(meta);
+    m_radio_map[AllMusic::id_to_id(meta->ID())] = meta;
+}
+
 void AllMusic::sortTree()
 {
     m_root_node->sort();
@@ -1101,9 +1374,69 @@
     delete builder;
 }
 
+/*note:temporary fix for radio*/
+#define RADIOS_PR_STATION 1
 void AllMusic::writeTree(GenericTree *tree_to_write_to)
 {
     m_root_node->writeTree(tree_to_write_to, 0);
+
+    /*note:temporary fix for radio*/
+    {
+        int rcounter = 0;
+        GenericTree *radio_root = tree_to_write_to->addNode(QObject::tr("All My Radios"), 
+                                                            REPO_ID_RADIO << METADATA_BITS_REPO_LEFT_SHIFT, 
+                                                            false);
+        radio_root->setAttribute(0, 0);
+        radio_root->setAttribute(1, 0);
+        radio_root->setAttribute(2, 0);
+        radio_root->setAttribute(3, 0);
+        radio_root->setAttribute(4, 0);
+        radio_root->setAttribute(5, 0);
+        
+#if RADIOS_PR_STATION
+        QMap<QString,GenericTree*> radio_stations;
+#endif
+        QPtrListIterator<Metadata> iterator(m_all_radios);
+        Metadata *mdata;
+
+        while ((mdata = iterator.current()) != 0)
+        {
+#if RADIOS_PR_STATION
+            if (radio_stations[mdata->Artist()] == 0) {
+                GenericTree *sub = 
+                    radio_root->addNode(mdata->FormatArtist(), 
+                                        REPO_ID_RADIO << METADATA_BITS_REPO_LEFT_SHIFT, 
+                                        false);
+                radio_stations[mdata->Artist()] = sub;
+                sub->setAttribute(0, 1);
+                sub->setAttribute(1, rcounter);
+                sub->setAttribute(2, rand());
+                sub->setAttribute(3, rand());
+                sub->setAttribute(4, rcounter);
+                sub->setAttribute(5, rcounter);
+                rcounter++;
+            }
+#endif
+            QString title = mdata->Album();
+#if RADIOS_PR_STATION
+            title = mdata->Title();
+#endif
+                
+            GenericTree *insert_at = radio_root;
+#if RADIOS_PR_STATION 
+           insert_at = radio_stations[mdata->Artist()];
+#endif
+           GenericTree *sub = insert_at->addNode(title, mdata->ID(), true);            
+            sub->setAttribute(0, 1);
+            sub->setAttribute(1, rcounter);
+            sub->setAttribute(2, rand());
+            sub->setAttribute(3, rand());
+            sub->setAttribute(4, rcounter);
+            sub->setAttribute(5, rcounter);
+            ++rcounter;
+            ++iterator;
+        }        
+    }
 }
 
 bool AllMusic::putYourselfOnTheListView(TreeCheckItem *where)
@@ -1134,22 +1467,25 @@
     }
 }
 
-QString AllMusic::getLabel(int an_id, bool *error_flag)
+QString AllMusic::getLabel(Metadata::IdType an_id, bool *error_flag)
 {
     QString a_label = "";
-    if(an_id > 0)
+    Metadata::IdType id = id_to_id (an_id);
+    Metadata::IdType repo = id_to_repo (an_id);
+
+    if (repo == REPO_ID_DB)
     {
-   
-        if (!music_map.contains(an_id))
+        if (!music_map.contains(id))
         {
-            a_label = QString(QObject::tr("Missing database entry: %1")).arg(an_id);
+            a_label = QString(QObject::tr("Missing database entry: %1")).arg(id);
             *error_flag = true;
             return a_label;
         }
+
       
-        a_label += music_map[an_id]->FormatArtist();
+        a_label += music_map[id]->FormatArtist();
         a_label += " ~ ";
-        a_label += music_map[an_id]->FormatTitle();
+        a_label += music_map[id]->FormatTitle();
     
 
         if(a_label.length() < 1)
@@ -1163,12 +1499,20 @@
         }
         return a_label;
     }
-    else
+    else if (repo == REPO_ID_RADIO)
     {
+        a_label += m_radio_map[id]->FormatArtist();
+        a_label += " ~ ";
+        a_label += m_radio_map[id]->FormatTitle();
+        *error_flag = false;
+        return a_label;
+    }
+    else if (repo == REPO_ID_CD)
+    {
         ValueMetadata::iterator anit;
         for(anit = m_cd_data.begin(); anit != m_cd_data.end(); ++anit)
         {
-            if( (*anit).Track() == an_id * -1)
+            if( (Metadata::IdType)(*anit).Track() == id)
             {
                 a_label = QString("(CD) %1 ~ %2").arg((*anit).FormatArtist())
                                                  .arg((*anit).FormatTitle());
@@ -1183,21 +1527,31 @@
     return a_label;
 }
 
-Metadata* AllMusic::getMetadata(int an_id)
+Metadata* AllMusic::getMetadata(Metadata::IdType an_id)
 {
-    if(an_id > 0)
+    Metadata::IdType id = id_to_id (an_id);
+    Metadata::IdType repo = id_to_repo (an_id);
+    
+    if (repo == REPO_ID_RADIO) {
+        if (m_radio_map.contains(id))
+        {
+            return m_radio_map[id];    
+        }
+        return NULL;
+    } 
+    else if (repo == REPO_ID_DB)
     {
-        if (music_map.contains(an_id))
+        if (music_map.contains(id))
         {
-            return music_map[an_id];    
+            return music_map[id];    
         }
     }
-    else if(an_id < 0)
+    else if (repo == REPO_ID_CD)
     {
         ValueMetadata::iterator anit;
         for(anit = m_cd_data.begin(); anit != m_cd_data.end(); ++anit)
         {
-            if( (*anit).Track() == an_id * -1)
+            if( (Metadata::IdType)(*anit).Track() == id)
             {
                 return &(*anit);
             }
@@ -1206,9 +1560,12 @@
     return NULL;
 }
 
-bool AllMusic::updateMetadata(int an_id, Metadata *the_track)
+
+
+bool AllMusic::updateMetadata(Metadata::IdType an_id, Metadata *the_track)
 {
-    if(an_id > 0)
+    Metadata::IdType repo = id_to_repo (an_id);    
+    if(repo == REPO_ID_DB || repo == REPO_ID_RADIO)
     {
         Metadata *mdata = getMetadata(an_id);
         if (mdata)
@@ -1373,7 +1730,7 @@
                                   .arg(a_track->Track()).arg(a_track->Title());
         QString level_temp = QObject::tr("title");
         TreeCheckItem *new_item = new TreeCheckItem(current_parent, title_temp,
-                                                    level_temp, a_track->ID());
+                                                    level_temp, AllMusic::id_to_id(a_track->ID()));
         ++anit;
         new_item->setCheck(false); //  Avoiding -Wall     
     }  
@@ -1407,7 +1764,7 @@
     while( (a_track = anit.current() ) != 0)
     {
         QString title_temp = QString(QObject::tr("%1 - %2")).arg(a_track->Track()).arg(a_track->Title());
-        GenericTree *subsub_node = sub_node->addNode(title_temp, a_track->ID(), true);
+        GenericTree *subsub_node = sub_node->addNode(title_temp, AllMusic::id_to_id(a_track->ID()), true);
         subsub_node->setAttribute(0, 1);
         subsub_node->setAttribute(1, track_counter);    // regular order
         subsub_node->setAttribute(2, rand());           // random order
@@ -1538,8 +1895,8 @@
     : m_parent(metadata)
 {
     m_imageList.setAutoDelete(true);
-
-    findImages();
+    if (m_parent)
+        findImages();
 }
 
 void AlbumArtImages::findImages(void)
@@ -1549,7 +1906,7 @@
     if (m_parent == NULL)
         return;
 
-    int trackid = m_parent->ID();
+    int trackid = AllMusic::id_to_id(m_parent->ID());
 
     if (trackid == 0)
         return;
Index: mythplugins/mythmusic/mythmusic/metaio.h
===================================================================
--- mythplugins/mythmusic/mythmusic/metaio.h	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/metaio.h	(working copy)
@@ -16,6 +16,7 @@
     
     virtual bool write(Metadata* mdata, bool exclusive = false) = 0;
     virtual Metadata* read(QString filename) = 0;
+	virtual QByteArray getAlbumArt(const QString &filename);
 
     void readFromFilename(QString filename, QString &artist, QString &album, 
                           QString &title, QString &genre, int &tracknum);
Index: mythplugins/mythmusic/mythmusic/dbcheck.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/dbcheck.cpp	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/dbcheck.cpp	(working copy)
@@ -10,9 +10,663 @@
 #include "mythtv/mythdbcon.h"
 
 const QString currentDatabaseVersion = "1013";
+//----------------------------------------------------------------------------
+// The radio number is to seperate radios from the main. As long
+// as this patch isn't in the main svn trunk, it's a hassle to 
+// share the number. When and if in the svn trunk, use the next
+// db version number. This is a cut'n'paste of the code 
+// just to tweak to use another field in the settings table.
+const QString radioCurrentDatabaseVersion = "5";
+static void RadioUpdateDBVersionNumber(const QString &newnumber)
+{
+    MSqlQuery query(MSqlQuery::InitCon());
 
+    query.exec("DELETE FROM settings WHERE value='RadioMusicDBSchemaVer';");
+    query.exec(QString("INSERT INTO settings (value, data, hostname) "
+                          "VALUES ('RadioMusicDBSchemaVer', %1, NULL);")
+                         .arg(newnumber));
+}
+static void RadioPerformActualUpdate(const QString updates[], QString version,
+                                QString &dbver)
+{
+    VERBOSE(VB_IMPORTANT, QString("Upgrading to MythMusicRadio schema version ") + 
+            version);
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    int counter = 0;
+    QString thequery = updates[counter];
+
+    while (thequery != "")
+    {
+        query.exec(thequery);
+        counter++;
+        thequery = updates[counter];
+    }
+
+    RadioUpdateDBVersionNumber(version);
+    dbver = version;
+}
+static void RadioUpgrade() {
+    QString dbver = gContext->GetSetting("RadioMusicDBSchemaVer");
+    VERBOSE(VB_IMPORTANT, QString("RadioUpgrade dbversion='%1'").arg(dbver));
+    if (dbver == radioCurrentDatabaseVersion) return;
+    if (dbver == "") {
+        const QString updates[] = {
+            "DROP TABLE IF EXISTS music_radios;",
+            "CREATE TABLE music_radios ("
+            "    intid INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,"
+            "    station VARCHAR(128) NOT NULL,"
+            "    channel VARCHAR(128) NOT NULL,"
+            "    url VARCHAR(128) NOT NULL,"
+            "    logourl VARCHAR(128) NOT NULL,"
+            "    logocached BLOB,"
+            "    genre VARCHAR(128) NOT NULL,"
+            "    metaformat VARCHAR(128) NOT NULL,"
+            "    format VARCHAR(10) NOT NULL,"
+            "    rating INT UNSIGNED NOT NULL DEFAULT 5,"
+            "    INDEX (station),"
+            "    INDEX (channel)"
+            ");",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Space Station Soma\","
+            "    url=\"http://somafm.com/spacestation\","
+            "    logourl=\"http://img.somafm.com/img/sss.jpg\","
+            "    genre=\"Electronica\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Doomed\","
+            "    url=\"http://somafm.com/doomed\","
+            "    logourl=\"http://img.somafm.com/img/doomed.gif\","
+            "    genre=\"Darkwave\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Beatblender\","
+            "    url=\"http://somafm.com/beatblender\","
+            "    logourl=\"http://img.somafm.com/img/blender.gif\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Cliqhop idm\","
+            "    url=\"http://somafm.com/cliqhop\","
+            "    logourl=\"http://img.somafm.com/img/cliqhop.gif\","
+            "    genre=\"Dance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Indie Pop Rocks\","
+            "    url=\"http://somafm.com/indiepop\","
+            "    logourl=\"http://img.somafm.com/img/indychick.jpg\","
+            "    genre=\"Indie\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Tag's Trance Trip\","
+            "    url=\"http://somafm.com/tagstrance\","
+            "    logourl=\"http://img.somafm.com/img/tagstrancefract.jpg\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Drone Zone\","
+            "    url=\"http://somafm.com/dronezone\","
+            "    logourl=\"http://img.somafm.com/img/DroneZoneBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Secret Agent\","
+            "    url=\"http://somafm.com/secretagent\","
+            "    logourl=\"http://img.somafm.com/img/SecretAgentBox.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Groove Salad\","
+            "    url=\"http://somafm.com/groovesalad\","
+            "    logourl=\"http://img.somafm.com/img/GrooveSaladBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Illinois Street Lounge\","
+            "    url=\"http://somafm.com/illstreet\","
+            "    logourl=\"http://img.somafm.com/img/illstreet.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"trance\","
+            "    url=\"http://di.fm/mp3/trance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"vocaltrance\","
+            "    url=\"http://di.fm/mp3/vocaltrance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"chillout\","
+            "    url=\"http://di.fm/mp3/chillout.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Electronica\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"house\","
+            "    url=\"http://di.fm/mp3/house.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"harddance\","
+            "    url=\"http://di.fm/mp3/harddance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"eurodance\","
+            "    url=\"http://di.fm/mp3/eurodance.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"progressive\","
+            "    url=\"http://di.fm/mp3/progressive.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"goapsy\","
+            "    url=\"http://di.fm/mp3/goapsy.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"hardcore\","
+            "    url=\"http://di.fm/mp3/hardcore.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"djmixes\","
+            "    url=\"http://di.fm/mp3/djmixes.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"lounge\","
+            "    url=\"http://di.fm/mp3/lounge.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"ambient\","
+            "    url=\"http://di.fm/mp3/ambient.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"drumandbass\","
+            "    url=\"http://di.fm/mp3/drumandbass.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"classictechno\","
+            "    url=\"http://di.fm/mp3/classictechno.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"breaks\","
+            "    url=\"http://di.fm/mp3/breaks.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"gabber\","
+            "    url=\"http://di.fm/mp3/gabber.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Techno\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"classical\","
+            "    url=\"http://di.fm/mp3/classical.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Classical\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"newage\","
+            "    url=\"http://di.fm/mp3/newage.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Newage\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"guitar\","
+            "    url=\"http://di.fm/mp3/guitar.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Instrumental\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"smoothjazz\","
+            "    url=\"http://di.fm/mp3/smoothjazz.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"tophits\","
+            "    url=\"http://di.fm/mp3/tophits.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Pop\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"the80s\","
+            "    url=\"http://di.fm/mp3/the80s.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"rootsreggae\","
+            "    url=\"http://di.fm/mp3/rootsreggae.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Reggae\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"hit70s\","
+            "    url=\"http://di.fm/mp3/hit70s.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"country\","
+            "    url=\"http://di.fm/mp3/country.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Country\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"jazz\","
+            "    url=\"http://di.fm/mp3/jazz.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Jazz\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"d.i.\", "
+            "    channel = \"salsa\","
+            "    url=\"http://di.fm/mp3/salsa.pls\","
+            "    logourl=\"http://forums.di.fm/images/di/logo_blue.gif\","
+            "    genre=\"Salsa\","
+            "    metaformat=\"%t - %a\","
+            "    format=\"cast\";",
+
+            ""
+        };
+        RadioPerformActualUpdate(updates, "1", dbver);
+    }
+    if (dbver == "1") {
+        const QString updates[] = {
+            "ALTER TABLE music_radios ADD logocached BLOB;",
+            ""
+        };
+        RadioPerformActualUpdate(updates, "2", dbver);
+    }
+    if (dbver == "2") {
+        const QString updates[] = {
+            "DELETE FROM music_radios WHERE station = \"SomaFM\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Space Station Soma\","
+            "    url=\"http://somafm.com/spacestation.pls\","
+            "    logourl=\"http://img.somafm.com/img/sss.jpg\","
+            "    genre=\"Electronica\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Doomed\","
+            "    url=\"http://somafm.com/doomed.pls\","
+            "    logourl=\"http://img.somafm.com/img/doomed.gif\","
+            "    genre=\"Darkwave\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Beatblender\","
+            "    url=\"http://somafm.com/beatblender.pls\","
+            "    logourl=\"http://img.somafm.com/img/blender.gif\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Cliqhop idm\","
+            "    url=\"http://somafm.com/cliqhop.pls\","
+            "    logourl=\"http://img.somafm.com/img/cliqhop.gif\","
+            "    genre=\"Dance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Indie Pop Rocks\","
+            "    url=\"http://somafm.com/indiepop.pls\","
+            "    logourl=\"http://img.somafm.com/img/indychick.jpg\","
+            "    genre=\"Indie\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Tag's Trance Trip\","
+            "    url=\"http://somafm.com/tagstrance.pls\","
+            "    logourl=\"http://img.somafm.com/img/tagstrancefract.jpg\","
+            "    genre=\"Trance\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Drone Zone\","
+            "    url=\"http://somafm.com/dronezone.pls\","
+            "    logourl=\"http://img.somafm.com/img/DroneZoneBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Secret Agent\","
+            "    url=\"http://somafm.com/secretagent.pls\","
+            "    logourl=\"http://img.somafm.com/img/SecretAgentBox.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+        
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Groove Salad\","
+            "    url=\"http://somafm.com/groovesalad.pls\","
+            "    logourl=\"http://img.somafm.com/img/GrooveSaladBox.jpg\","
+            "    genre=\"Ambient\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Illinois Street Lounge\","
+            "    url=\"http://somafm.com/illstreet.pls\","
+            "    logourl=\"http://img.somafm.com/img/illstreet.jpg\","
+            "    genre=\"Lounge\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+
+            "INSERT INTO music_radios SET station = \"SomaFM\", "
+            "    channel = \"Boot Liquor\","
+            "    url=\"http://somafm.com/bootliquor.pls\","
+            "    logourl=\"http://img.somafm.com/img/bootliquor.gif\","
+            "    genre=\"Rock\","
+            "    metaformat=\"%a - %t\","
+            "    format=\"cast\";",
+            ""
+        };
+        
+        RadioPerformActualUpdate(updates, "3", dbver);
+    }
+    if (dbver == "3") {
+        const QString updates[] = {
+            "DELETE FROM music_radios WHERE station = \"d.i.\";",
+            "DELETE FROM music_radios WHERE station = \"di.fm\";",
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Electro House\"," 
+            "	url = \"http://di.fm/mp3/electro.pls\"," 
+            "	logourl = \"http://www.di.fm/images/chbuttons/electro.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Tribal House\"," 
+            "	url = \"http://di.fm/mp3/tribalhouse.pls\"," 
+            "	logourl = \"http://www.di.fm/images/chbuttons/tribalhouse.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Funky House\"," 
+            "	url = \"http://di.fm/mp3/funkyhouse.pls\"," 
+            "	logourl = \"http://www.di.fm/images/chbuttons/funkyhouse.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Minimal\"," 
+            "	url = \"http://di.fm/mp3/minimal.pls\"," 
+            "	logourl = \"http://www.di.fm/images/chbuttons/minimal.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Hardstyle\"," 
+            "	url = \"http://di.fm/mp3/hardstyle.pls\"," 
+            "	logourl = \"http://www.di.fm/images/chbuttons/hardstyle.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Trance\"," 
+            "	url = \"http://di.fm/mp3/trance.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Chillout\"," 
+            "	url = \"http://di.fm/mp3/chillout.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"House\"," 
+            "	url = \"http://di.fm/mp3/house.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Techno\"," 
+            "	url = \"http://di.fm/mp3/techno.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Progressive\"," 
+            "	url = \"http://di.fm/mp3/progressive.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Lounge\"," 
+            "	url = \"http://di.fm/mp3/lounge.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Ambient\"," 
+            "	url = \"http://di.fm/mp3/ambient.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Breaks\"," 
+            "	url = \"http://di.fm/mp3/breaks.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"di.fm\", "
+            "	channel = \"Gabber\"," 
+            "	url = \"http://di.fm/mp3/gabber.pls\"," 
+            "	logourl = \"\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+        
+            ""
+        };
+        
+        RadioPerformActualUpdate(updates, "4", dbver);
+    }
+    if (dbver == "4") {
+        const QString updates[] = {
+            "DELETE FROM music_radios WHERE station = \"SKY.fm\";",
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Love Music\"," 
+            "	url = \"http://www.sky.fm/mp3/lovemusic.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/lovemusic.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Beatles Tribute\"," 
+            "	url = \"http://www.sky.fm/mp3/beatles.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/beatles.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Smooth Jazz\"," 
+            "	url = \"http://www.sky.fm/mp3/smoothjazz.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/smoothjazz.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Uptempo/Smooth Jazz\"," 
+            "	url = \"http://www.sky.fm/mp3/uptemposmoothjazz.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/uptemposmoothjazz.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Top Hits Music\"," 
+            "	url = \"http://www.sky.fm/mp3/tophits.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/tophits.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"Best of the 80s\"," 
+            "	url = \"http://www.sky.fm/mp3/the80s.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/the80s.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            "INSERT INTO music_radios SET station = \"SKY.fm\", "
+            "	channel = \"All Hit 70s\"," 
+            "	url = \"http://www.sky.fm/mp3/hit70s.pls\"," 
+            "	logourl = \"http://www.sky.fm/images/chbuttons/hit70s.gif\"," 
+            "	genre = \"\"," 
+            "	metaformat = \"%a - %t\"," 
+            "	format = \"cast\";", 
+
+            
+        
+            ""
+        };
+        
+        RadioPerformActualUpdate(updates, "5", dbver);
+    }
+}
+//----------------------------------------------------------------------------
+
 static bool UpdateDBVersionNumber(const QString &newnumber)
-{   
+{
+    MSqlQuery query(MSqlQuery::InitCon());
 
     if (!gContext->SaveSettingOnHost("MusicDBSchemaVer",newnumber,NULL))
     {   
@@ -67,6 +721,10 @@
 bool UpgradeMusicDatabaseSchema(void)
 {
     QString dbver = gContext->GetSetting("MusicDBSchemaVer");
+
+    // Once in the main trunk, the radioupgrade code goes back in here, but as a patch,
+    // it's way easier to just have a call here.
+    RadioUpgrade();
     
     if (dbver == currentDatabaseVersion)
         return true;
@@ -351,7 +1009,6 @@
             return false;
     }
 
-
     if (dbver == "1005")
     {
         const QString updates[] = {
@@ -617,6 +1274,5 @@
 //"DROP TABLE musicmetadata;",
 //"DROP TABLE musicplaylist;",
 
-
     return true;
 }
Index: mythplugins/mythmusic/mythmusic/metadata.h
===================================================================
--- mythplugins/mythmusic/mythmusic/metadata.h	(revision 16380)
+++ mythplugins/mythmusic/mythmusic/metadata.h	(working copy)
@@ -1,6 +1,8 @@
 #ifndef METADATA_H_
 #define METADATA_H_
 
+#include <stdexcept>
+
 // qt
 #include <qstring.h>
 #include <qstringlist.h>
@@ -40,13 +42,18 @@
     bool      embedded;
 } AlbumArtImage;
 
+#define METADATA_BITS_FOR_REPO 4
+#define METADATA_BITS_REPO_LEFT_SHIFT ((sizeof (unsigned int) * CHAR_BIT) - METADATA_BITS_FOR_REPO)
+#define METADATA_BITS_REPO_RIGHT_SIDE (UINT_MAX >> METADATA_BITS_FOR_REPO)
 
 class Metadata
 {
   public:
+    typedef unsigned int IdType;
+  public:
     Metadata(QString lfilename = "", QString lartist = "", QString lcompilation_artist = "",
              QString lalbum = "", QString ltitle = "", QString lgenre = "",
-             int lyear = 0, int ltracknum = 0, int llength = 0, int lid = 0,
+             int lyear = 0, int ltracknum = 0, int llength = 0, IdType lid = 0,
              int lrating = 0, int lplaycount = 0, QString llastplay = "",
              bool lcompilation = false, QString lformat="")
                 : m_artist(lartist),
@@ -110,8 +117,9 @@
 
     QString FormatArtist();
     QString FormatTitle();
+	QString FormatInfo();
 
-    QString Genre() { return m_genre; }
+    QString Genre() const { return m_genre; }
     void setGenre(const QString &lgenre) { m_genre = lgenre; }
 
     void setDirectoryId(int ldirectoryid) { m_directoryid = ldirectoryid; }
@@ -139,14 +147,27 @@
     void setPlaycount(int lplaycount) { m_playcount = lplaycount; }
 
     unsigned int ID() const { return m_id; }
-    void setID(int lid) { m_id = lid; }
+    void setID(int lid) { 
+        if (lid >= (1 << METADATA_BITS_REPO_LEFT_SHIFT))
+            throw std::logic_error ("ID is too big");
+        m_id = (repoID() << METADATA_BITS_REPO_LEFT_SHIFT) + lid; 
+	}
 
+	unsigned int repoID () const { return m_id >> METADATA_BITS_REPO_LEFT_SHIFT; }
+	void setRepoID (unsigned int rid) { 
+        if (rid >= (1 << METADATA_BITS_FOR_REPO))
+            throw std::logic_error ("ID is too big");
+        m_id = (rid << METADATA_BITS_REPO_LEFT_SHIFT) + ID(); 
+    }
+
     QString Filename() const { return m_filename; }
     void setFilename(const QString &lfilename) { m_filename = lfilename; }
 
     QString Format() const { return m_format; }
     void setFormat(const QString &lformat) { m_format = lformat; }
 
+	void setAlbumArt(const QByteArray &data);
+
     int Rating() const { return m_rating; }
     void decRating();
     void incRating();
@@ -174,18 +195,21 @@
 
     bool isInDatabase(void);
     void dumpToDatabase(void);
+	void removeFromDatabase(void);
     void setField(const QString &field, const QString &data);
     void getField(const QString& field, QString *data);
     void persist();
     bool hasChanged() {return m_changed;}
     int compare (Metadata *other);
+
+    MythThemedDialog *createEditorDialog (void);
+
     static void setArtistAndTrackFormats();
-
     static void SetStartdir(const QString &dir);
     static QString GetStartdir() { return m_startdir; }
 
     static QStringList fillFieldList(QString field);
-    static Metadata *getMetadataFromID(int id);
+    static Metadata *getMetadataFromID(Metadata::IdType an_id);
 
     // this looks for any image available - preferring a front cover if available
     QImage getAlbumArt(void);
@@ -219,7 +243,7 @@
     bool m_compilation;
     QValueList<struct AlbumArtImage> m_albumart;
 
-    unsigned int m_id;
+    IdType m_id;
     QString m_filename;
     bool    m_changed;
 
@@ -237,6 +261,13 @@
     static QString m_formatcompilationfiletrack;
     static QString m_formatcompilationcdartist;
     static QString m_formatcompilationcdtrack;
+
+    /*note:temporary fix for radio. These should be split out by subclassing Metadata */
+    int compare_cast (Metadata *other);
+    MythThemedDialog *createEditorDialog_cast (void);
+    void dumpToDatabase_cast();
+    void updateDatabase_cast();
+    void removeFromDatabase_cast();
 };
 
 bool operator==(const Metadata& a, const Metadata& b);
@@ -340,15 +371,17 @@
     AllMusic(QString path_assignment, QString a_startdir);
     ~AllMusic();
 
-    QString     getLabel(int an_id, bool *error_flag);
-    Metadata*   getMetadata(int an_id);
-    bool        updateMetadata(int an_id, Metadata *the_track);
+    QString     getLabel(unsigned int an_id, bool *error_flag);
+    Metadata*   getMetadata(unsigned int an_id);
+    bool        updateMetadata(unsigned int an_id, Metadata *the_track);
     int         count() const { return m_numPcs; }
     int         countLoaded() const { return m_numLoaded; } 
     void        save();
     bool        startLoading(void);
     void        resync();   //  After a CD rip, for example
+    void        resync_radios(void); /*note:temporary fix for radio*/
     void        clearCDData();
+    void        addRadioTrack(Metadata *the_track); /*note:temporary fix for radio*/
     void        addCDTrack(Metadata *the_track);
     bool        checkCDTrack(Metadata *the_track);
     bool        getCDMetadata(int m_the_track, Metadata *some_metadata);
@@ -371,6 +404,8 @@
   private:
   
     MetadataPtrList     m_all_music;
+    MetadataPtrList     m_all_radios; /*note:temporary fix for radio*/
+    QMap<Metadata::IdType,Metadata*> m_radio_map;  /*note:temporary fix for radio*/
     MusicNode           *m_root_node;
     
     int m_numPcs;
@@ -381,7 +416,7 @@
     //  so they still reference the correct data
     //  If, however, you create or delete metadata
     //  you NEED to clear and rebuild the map
-    typedef QMap<int, Metadata*> MusicMap;
+    typedef QMap<Metadata::IdType, Metadata*> MusicMap;
     MusicMap music_map;
     
     typedef QValueList<Metadata>  ValueMetadata;
@@ -401,6 +436,13 @@
     double                   m_lastplayMin;
     double                   m_lastplayMax;
 
+public:
+    static unsigned int REPO_ID_DB;
+    static unsigned int REPO_ID_RADIO;
+    static unsigned int REPO_ID_CD;
+
+    static unsigned int id_to_id (unsigned int id) { return id & METADATA_BITS_REPO_RIGHT_SIDE; }
+    static unsigned int id_to_repo (unsigned int id) { return id >> METADATA_BITS_REPO_LEFT_SHIFT; }
 };
 
 //----------------------------------------------------------------------------
@@ -449,6 +491,7 @@
 
     Metadata                *m_parent;
     QPtrList<AlbumArtImage>  m_imageList;
+
 };
 
 #endif
Index: mythplugins/mythmusic/mythmusic/pls.cpp
===================================================================
--- mythplugins/mythmusic/mythmusic/pls.cpp	(revision 0)
+++ mythplugins/mythmusic/mythmusic/pls.cpp	(revision 0)
@@ -0,0 +1,406 @@
+/*
+  playlistfile (.pls) parser
+  Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
+*/
+
+#include "pls.h"
+
+#include <qpair.h>
+#include <qvaluelist.h>
+#include <qmap.h>
+
+#include <assert.h>
+
+using namespace std;
+
+class CfgReader 
+{
+  public:
+    CfgReader() 
+    {
+    }
+    ~CfgReader() 
+    {
+    }
+
+    typedef QPair<QString,QString> KeyValue;
+    typedef QValueList<KeyValue> KeyValueList;
+    typedef QMap<QString, KeyValueList> ConfigMap;
+
+    void parse(const char *d, int l) 
+    {
+        const char *ptr = d;
+        int line = 1;
+        bool done = l <= 0;
+
+        QString current_section = "";
+        KeyValueList keyvals;
+
+        while(!done) 
+        {
+            switch(*ptr) 
+            {
+            case '\0':
+                done = true;
+                break;
+            case '#': 
+            {
+                char *end = strchr(ptr, '\n');
+                if (!end) done = true;
+                ptr = end;
+                break;
+            }
+            case '\n':
+                ptr ++;
+                line ++;
+                break;
+            case '[': 
+            {
+                ptr ++;
+                const char *nl = strchr(ptr, '\n');
+                const char *end = strchr(ptr, ']');
+
+                if (!nl) nl = d + l;
+
+                if (!end || nl < end) 
+                {
+                    fprintf(stderr, "Badly formatted section, line %d\n", line);
+                    done = true;
+                }
+
+                if (current_section.length() > 0) 
+                {
+                    cfg[current_section] = keyvals;
+                    keyvals = KeyValueList();
+                }
+
+                current_section = std::string(ptr, end - ptr);
+                if (current_section.length() == 0) 
+                {
+                    fprintf(stderr, "Badly formatted section, line %d\n", line);
+                    done = true;
+                }
+                ptr = end + 1;
+                break;
+            }
+            default:
+                if (current_section.length() > 0) 
+                {
+                    const char *eq = strchr(ptr, '=');
+                    const char *nl = strchr(ptr, '\n');
+
+                    if (!nl) nl = d + l;
+
+                    if (!eq || nl < eq) 
+                    {
+                        fprintf(stderr, "Badly formatted line %d\n", line);
+                        done = true;
+                    } 
+                    else 
+                    {
+                        QString key = string(ptr, eq - ptr);
+                        QString val = string(eq + 1, nl - eq - 1);
+                        keyvals.push_back(KeyValue(key, val));
+                        ptr = nl;
+                    }
+                } 
+                else 
+                {
+                    fprintf(stderr, "Badly formatted line %d\n", line);
+                    done = true;
+                }
+                break;
+            }
+
+            if (ptr - d == l) 
+                done = true;
+        }
+
+        if (current_section.length() > 0) 
+            cfg[current_section] = keyvals;
+    }
+
+    QValueList<QString> getSections(void) 
+    {
+        QValueList<QString> res;
+        for (ConfigMap::iterator it = cfg.begin(); it != cfg.end(); it++) 
+            res.push_back(it.key());
+        return res;
+    }
+
+    QValueList<QString> getKeys(const QString &section) 
+    {
+        KeyValueList keylist = cfg[section];
+        QValueList<QString> res;
+        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++)
+            res.push_back((*it).first);
+        return res; 
+    }
+
+    QString getStrVal(const QString &section, const QString &key, const QString &def="") 
+    {
+        KeyValueList keylist = cfg[section];
+        QString res = def;
+        for (KeyValueList::iterator it = keylist.begin(); it != keylist.end(); it++) 
+        {
+            if ((*it).first == key) 
+            {
+                res =(*it).second;
+                break;
+            }
+        }
+        return res;
+    }
+
+    int getIntVal(const QString &section, const QString &key, int def=0) 
+    {
+        QString def_str;
+        def_str.setNum (def);
+        return getStrVal(section, key, def_str).toInt();
+    }
+    
+    // very simple unit test, only tests parsing a wellformed file...
+    static void test(void)
+    {
+        const char *sample1 =
+            "# Sample config file\n"
+           