Advertising
- Miscellany
- Friday, August 3rd, 2007 at 11:34:38pm UTC
- Index: myththemes/MythCenter/music-ui.xml
- ===================================================================
- --- myththemes/MythCenter/music-ui.xml (revision 0)
- +++ myththemes/MythCenter/music-ui.xml (revision 0)
- @@ -0,0 +1,156 @@
- +<mythuitheme>
- +
- + <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,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:</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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_leftright_on.png"></image>
- + <image function="off" filename="mm_leftright_off.png"></image>
- + <image function="pushed" filename="mm_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>
- +
- +</mythuitheme>
- Index: myththemes/Retro/music-ui.xml
- ===================================================================
- --- myththemes/Retro/music-ui.xml (revision 0)
- +++ myththemes/Retro/music-ui.xml (revision 0)
- @@ -0,0 +1,156 @@
- +<mythuitheme>
- +
- + <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,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:</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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_leftright_on.png"></image>
- + <image function="off" filename="mm_leftright_off.png"></image>
- + <image function="pushed" filename="mm_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>
- +
- +</mythuitheme>
- Index: myththemes/MythCenter-wide/music-ui.xml
- ===================================================================
- --- myththemes/MythCenter-wide/music-ui.xml (revision 0)
- +++ myththemes/MythCenter-wide/music-ui.xml (revision 0)
- @@ -0,0 +1,156 @@
- +<mythuitheme>
- +
- + <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="mm_blankbutton_on.png"> </image>
- + <image function="off" filename="mm_blankbutton_off.png"> </image>
- + <image function="pushed" filename="mm_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="mm_blankbutton_on.png"> </image>
- + <image function="off" filename="mm_blankbutton_off.png"> </image>
- + <image function="pushed" filename="mm_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="mm_leftright_on.png"> </image>
- + <image function="off" filename="mm_leftright_off.png"> </image>
- + <image function="pushed" filename="mm_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>
- +
- +</mythuitheme>
- Index: mythtv/themes/G.A.N.T./music-ui.xml
- ===================================================================
- --- mythtv/themes/G.A.N.T./music-ui.xml (revision 13855)
- +++ mythtv/themes/G.A.N.T./music-ui.xml (working copy)
- @@ -652,4 +652,154 @@
- </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:</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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_blankbutton_on.png"></image>
- + <image function="off" filename="mm_blankbutton_off.png"></image>
- + <image function="pushed" filename="mm_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="mm_leftright_on.png"></image>
- + <image function="off" filename="mm_leftright_off.png"></image>
- + <image function="pushed" filename="mm_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>
- +
- </mythuitheme>
- Index: mythtv/themes/blue/music-ui.xml
- ===================================================================
- --- mythtv/themes/blue/music-ui.xml (revision 13855)
- +++ mythtv/themes/blue/music-ui.xml (working copy)
- @@ -585,5 +585,153 @@
- </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="mm_blankbutton_on.png"> </image>
- + <image function="off" filename="mm_blankbutton_off.png"> </image>
- + <image function="pushed" filename="mm_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="mm_blankbutton_on.png"> </image>
- + <image function="off" filename="mm_blankbutton_off.png"> </image>
- + <image function="pushed" filename="mm_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="mm_leftright_on.png"> </image>
- + <image function="off" filename="mm_leftright_off.png"> </image>
- + <image function="pushed" filename="mm_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>
- </mythuitheme>
- Index: mythtv/libs/libmyth/output.cpp
- ===================================================================
- --- mythtv/libs/libmyth/output.cpp (revision 13855)
- +++ 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: mythplugins/mythmusic/mythmusic/playbackbox.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/playbackbox.cpp (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/playbackbox.cpp (working copy)
- @@ -19,15 +19,18 @@
- // MythMusic includes
- #include "metadata.h"
- #include "constants.h"
- -#include "streaminput.h"
- #include "decoder.h"
- #include "cddecoder.h"
- #include "playbackbox.h"
- #include "databasebox.h"
- +#include "metaio.h"
- #include "mainvisual.h"
- #include "smartplaylist.h"
- #include "search.h"
- +#include "decoderhandler.h"
- +#define GET_REPO_ID(attrs) attrs->at(4)
- +
- PlaybackBoxMusic::PlaybackBoxMusic(MythMainWindow *parent, QString window_name,
- QString theme_filename,
- PlaylistsContainer *the_playlists,
- @@ -38,9 +41,9 @@
- {
- // A few internal variable defaults
- - input = NULL;
- output = NULL;
- decoder = NULL;
- + decoderHandler = NULL;
- mainvisual = NULL;
- visual_mode_timer = NULL;
- lcd_update_timer = NULL;
- @@ -112,6 +115,10 @@
- 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
- QString playmode = gContext->GetSetting("PlayMode", "none");
- @@ -278,6 +285,14 @@
- gContext->SaveSetting("RepeatMode", "all");
- else
- gContext->SaveSetting("RepeatMode", "none");
- +
- + if (decoderHandler)
- + {
- + decoderHandler->removeListener(this);
- + decoderHandler->deleteLater();
- + decoderHandler = NULL;
- + }
- +
- if (class LCD *lcd = LCD::Get())
- lcd->switchToTime();
- }
- @@ -420,7 +435,7 @@
- }
- else if (action == "INFO")
- if (visualizer_status == 2)
- - bannerToggle(curMeta);
- + bannerToggle(&displayMeta);
- else
- showEditMetadataDialog();
- else
- @@ -565,6 +580,32 @@
- MythThemedDialog::keyPressEvent(e);
- }
- +/*note:temporary fix for radio - Ideally, I'd like the metadata repo
- + add it's own stuff to the menur entry
- +*/
- +void PlaybackBoxMusic::addRadioMenuEntries()
- +{
- + GenericTree *node = music_tree_list->getCurrentNode();
- + IntVector *attrs = node->getAttributes();
- +
- + if (GET_REPO_ID(attrs) != 1)
- + 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)
- @@ -611,6 +652,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,
- @@ -619,14 +662,19 @@
- 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->ShowPopup(this, SLOT(closePlaylistPopup()));
- @@ -681,6 +729,92 @@
- }
- }
- +void PlaybackBoxMusic::decoderHandlerReady(void)
- +{
- + decoder = decoderHandler->getDecoder();
- +
- + if (decoder->getFilename().contains("cda") == 1)
- + dynamic_cast<CdDecoder*>(decoder)->setDevice(m_CDdevice);
- +
- + decoder->setOutput(output);
- + decoder->setBlockSize(globalBlockSize);
- + decoder->addListener(this);
- +
- + currentTime = 0;
- +
- + mainvisual->setDecoder(decoder);
- + mainvisual->setOutput(output);
- +
- + if (decoder->initialize())
- + {
- + if (output)
- + output->Reset();
- +
- + decoder->start();
- +
- + if (resumemode == RESUME_EXACT && gContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
- + {
- + seek(gContext->GetNumSetting("MusicBookmarkPosition", 0));
- + gContext->SaveSetting("MusicBookmarkPosition", 0);
- + }
- +
- + isplaying = true;
- +
- + // hmm, it'd be more fair to only inc playcount and set lastplay
- + // when it's played at least x seconds of the track.
- + curMeta->setLastPlay();
- + curMeta->incPlayCount();
- +
- + bannerEnable(curMeta, show_album_art);
- + } else {
- + VERBOSE(VB_PLAYBACK, QString ("Cannot initialise decoder for %1").
- + arg (decoder->getFilename ()));
- + }
- +}
- +
- +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();
- +}
- +
- +void PlaybackBoxMusic::operationProgressTimer()
- +{
- + operation_progress_count++;
- + decoderHandlerInfo(operation_name,
- + QString().fill('.', operation_progress_count));
- +
- + // You get ten seconds...
- + if (operation_progress_count >= 10)
- + {
- + decoderHandler->stop();
- + MythPopupBox::showOkPopup(gContext->GetMainWindow(),
- + statusString,
- + QString("Operation timed out"));
- + }
- +}
- +
- void PlaybackBoxMusic::showSearchDialog()
- {
- if (!playlist_popup)
- @@ -700,6 +834,93 @@
- }
- }
- +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()) {
- + 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;
- + }
- + }
- + }
- +
- + delete dialog;
- +}
- +
- +void PlaybackBoxMusic::showRemoveRadioStationDialog()
- +{
- + if (!playlist_popup)
- + return;
- +
- + closePlaylistPopup();
- +
- + GenericTree *node = music_tree_list->getCurrentNode();
- + Metadata *meta = all_music->getRadioMetadata(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)
- @@ -979,31 +1200,49 @@
- void PlaybackBoxMusic::showEditMetadataDialog()
- {
- - if (!curMeta)
- - {
- + MythThemedDialog *editDialog = 0;
- + GenericTree *node = music_tree_list->getCurrentNode();
- + IntVector *attrs = node->getAttributes();
- + Metadata *editMeta = 0;
- +
- + /* note: temporary fix for radio, get the appropriate editor for repo */
- + if (GET_REPO_ID(attrs) == 1)
- + editMeta = all_music->getRadioMetadata(node->getInt());
- + else
- + editMeta = all_music->getMetadata(node->getInt());
- +
- + if(!editMeta)
- return;
- - }
- - // store the current track metadata in case the track changes
- - // while we show the edit dialog
- - Metadata *editMeta = curMeta;
- - GenericTree *node = music_tree_list->getCurrentNode();
- + editDialog = editMeta->createEditorDialog ();
- - EditMetadataDialog editDialog(editMeta, gContext->GetMainWindow(),
- - "edit_metadata", "music-", "edit metadata");
- - if (editDialog.exec())
- + if (editDialog->exec())
- {
- // update the metadata copy stored in all_music
- - if (all_music->updateMetadata(editMeta->ID(), editMeta))
- + if (all_music->updateMetadata(GET_REPO_ID(attrs), editMeta->ID(), editMeta))
- {
- // update the displayed track info
- if (node)
- {
- - bool errorFlag;
- - node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
- + /* note: temporary fix for radio, get the label editor for repo */
- + if (GET_REPO_ID(attrs) == 1)
- + {
- + QString title = editMeta->FormatArtist ();
- + title += " ~ ";
- + title += editMeta->FormatTitle();
- + editMeta->setAlbum(title);
- + node->setString(editMeta->Album());
- + }
- + else
- + {
- + bool errorFlag;
- + node->setString(all_music->getLabel(editMeta->ID(), &errorFlag));
- + }
- +
- + node->getParent()->sortByString();
- music_tree_list->refresh();
- - // make sure the track hasn't changed
- + // make sure the track hasn't changed
- if (curMeta->ID() == editMeta->ID())
- {
- *curMeta = editMeta;
- @@ -1019,6 +1258,8 @@
- }
- }
- }
- +
- + delete editDialog;
- }
- void PlaybackBoxMusic::checkForPlaylists()
- @@ -1202,9 +1443,6 @@
- return;
- }
- - QUrl sourceurl(playfile);
- - QString sourcename(playfile);
- -
- if (!output)
- openOutputDevice();
- @@ -1214,70 +1452,11 @@
- return;
- }
- - if (!sourceurl.isLocalFile())
- - {
- - StreamInput streaminput(sourceurl);
- - streaminput.setup();
- - input = streaminput.socket();
- - }
- - else
- - input = new QFile(playfile);
- -
- - if (decoder && !decoder->factory()->supports(sourcename))
- - {
- - decoder->removeListener(this);
- - decoder = 0;
- - }
- + if (!decoderHandler)
- + setupDecoderHandler();
- - if (!decoder)
- - {
- - decoder = Decoder::create(sourcename, input, output);
- -
- - if (!decoder)
- - {
- - printf("mythmusic: unsupported fileformat\n");
- - stopAll();
- - return;
- - }
- - if (sourcename.contains("cda") == 1)
- - dynamic_cast<CdDecoder*>(decoder)->setDevice(m_CDdevice);
- -
- - decoder->setBlockSize(globalBlockSize);
- - decoder->addListener(this);
- - }
- - else
- - {
- - decoder->setInput(input);
- - decoder->setFilename(sourcename);
- - decoder->setOutput(output);
- - }
- -
- - currentTime = 0;
- -
- - mainvisual->setDecoder(decoder);
- - mainvisual->setOutput(output);
- mainvisual->setMetadata(curMeta);
- -
- - if (decoder->initialize())
- - {
- - if (output)
- - {
- - output->Reset();
- - }
- -
- - decoder->start();
- -
- - if (resumemode == RESUME_EXACT && gContext->GetNumSetting("MusicBookmarkPosition", 0) > 0)
- - {
- - seek(gContext->GetNumSetting("MusicBookmarkPosition", 0));
- - gContext->SaveSetting("MusicBookmarkPosition", 0);
- - }
- -
- - bannerEnable(curMeta, show_album_art);
- - isplaying = true;
- - curMeta->setLastPlay();
- - curMeta->incPlayCount();
- - }
- + decoderHandler->start(curMeta);
- }
- void PlaybackBoxMusic::visEnable()
- @@ -1358,7 +1537,7 @@
- }
- -void PlaybackBoxMusic::setTrackOnLCD(Metadata *mdata)
- +void PlaybackBoxMusic::setTrackOnLCD(const Metadata *mdata)
- {
- LCD *lcd = LCD::Get();
- if (!lcd)
- @@ -1389,22 +1568,13 @@
- void PlaybackBoxMusic::stopDecoder(void)
- {
- - if (decoder && decoder->running())
- - decoder->stop();
- -
- - if (decoder)
- - {
- - decoder->lock();
- - decoder->cond()->wakeAll();
- - decoder->unlock();
- - }
- -
- - if (decoder)
- - decoder->wait();
- + if (decoderHandler)
- + decoderHandler->stop();
- }
- void PlaybackBoxMusic::stop(void)
- {
- + decoder_handler_progress_timer->stop();
- stopDecoder();
- if (output)
- @@ -1420,9 +1590,6 @@
- mainvisual->setOutput(0);
- mainvisual->deleteMetadata();
- - delete input;
- - input = 0;
- -
- QString time_string;
- int maxh = maxTime / 3600;
- int maxm = (maxTime / 60) % 60;
- @@ -1503,10 +1670,15 @@
- isplaying = false;
- - if (repeatmode == REPEAT_TRACK)
- - play();
- - else
- - next();
- + if (! decoderHandler->done())
- + decoderHandler->next();
- + else if (curMeta->Format() != "cast")
- + {
- + if (repeatmode == REPEAT_TRACK)
- + play();
- + else
- + next();
- + }
- }
- void PlaybackBoxMusic::seekforward()
- @@ -1623,6 +1795,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 (isplaying)
- setTrackOnLCD(curMeta);
- }
- @@ -1978,10 +2152,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 +2186,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);
- @@ -2040,6 +2267,7 @@
- album_text->SetText(mdata->Album());
- setTrackOnLCD(mdata);
- + bannerEnable(mdata);
- }
- void PlaybackBoxMusic::openOutputDevice(void)
- @@ -2062,6 +2290,12 @@
- output->addVisual(mainvisual);
- }
- +void PlaybackBoxMusic::setupDecoderHandler()
- +{
- + decoderHandler = new DecoderHandler();
- + decoderHandler->addListener(this);
- +}
- +
- void PlaybackBoxMusic::handleTreeListSignals(int node_int, IntVector *attributes)
- {
- if (attributes->size() < 4)
- @@ -2075,16 +2309,17 @@
- {
- // It's a track
- - curMeta = all_music->getMetadata(node_int);
- - if (title_text)
- - title_text->SetText(curMeta->FormatTitle());
- - if (artist_text)
- - artist_text->SetText(curMeta->FormatArtist());
- - if (album_text)
- - album_text->SetText(curMeta->Album());
- + /*note: temporary fix for radio.
- + Ideally, I'd like for each metadata to carry a fifth
- + attribute, identifying the repository it came from, such as
- + CD, harddrive, iPod etc.
- + */
- + if (GET_REPO_ID(attributes) == 1)
- + curMeta = all_music->getRadioMetadata(node_int);
- + else
- + curMeta = all_music->getMetadata(node_int);
- - setTrackOnLCD(curMeta);
- -
- + updateTrackInfo(curMeta);
- maxTime = curMeta->Length() / 1000;
- QString time_string;
- Index: mythplugins/mythmusic/mythmusic/mythmusic.pro
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/mythmusic.pro (revision 13855)
- +++ 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
- +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
- +SOURCES += shoutcast.cpp decoderhandler.cpp editradiometadata.cpp pls.cpp
- macx {
- SOURCES -= cddecoder.cpp
- @@ -67,3 +69,4 @@
- #QMAKE_LFLAGS += -flat_namespace -undefined suppress
- QMAKE_LFLAGS += -flat_namespace -undefined error
- }
- +
- Index: mythplugins/mythmusic/mythmusic/decoderhandler.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/decoderhandler.cpp (revision 0)
- +++ mythplugins/mythmusic/mythmusic/decoderhandler.cpp (revision 0)
- @@ -0,0 +1,539 @@
- +#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(m_url, format);
- +}
- +
- +Decoder *DecoderIOFactory::getDecoder()
- +{
- + return m_handler->getDecoder();
- +}
- +
- +void DecoderIOFactory::doFailed(const QString &message)
- +{
- + m_handler->doOperationStop();
- + m_handler->doFailed(m_url, 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 = m_meta->Filename();
- + VERBOSE(VB_PLAYBACK, QString("DecoderIOFactory: Opening Local File %1").arg(sourcename));
- + m_input = new QFile(sourcename);
- + doConnectDecoder(m_url);
- +}
- +
- +
- +/**********************************************************************/
- +
- +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(m_url.toString()));
- +
- + m_started = false;
- +
- + doOperationStart("Fetching remote file");
- +
- + m_url_op->get(m_url);
- +}
- +
- +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(m_url);
- + 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(m_url.toString()));
- + StreamInput streaminput(m_url);
- + streaminput.setup();
- + m_input = streaminput.socket();
- + doConnectDecoder(m_url);
- +}
- +
- +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;
- +
- + 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()
- +{
- + if (m_decoder) {
- + 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 ();
- + m_decoder->setInput (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.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);
- +
- + 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)
- + {
- + 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)) {
- + m_decoder = NULL;
- + }
- +
- + if (! m_decoder) {
- + if ((m_decoder = Decoder::create(format, NULL, NULL)) == 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,356 @@
- +#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;
- + }
- +
- + delete searchDialog;
- + 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();
- +
- + delete popup;
- + 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/decoder.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/decoder.h (revision 13855)
- +++ 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/aacdecoder.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/aacdecoder.cpp (revision 13855)
- +++ 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
- @@ -352,9 +349,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;
- }
- //
- @@ -501,7 +498,7 @@
- if (output())
- {
- - output()->Drain();
- + output()->Drain();
- }
- done = TRUE;
- @@ -542,7 +539,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
- @@ -571,24 +568,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/visualize.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/visualize.cpp (revision 13855)
- +++ 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
- @@ -447,7 +446,7 @@
- (void)pluginName;
- return new AlbumArt(parent);
- }
- -}AlbumArtFactory;
- +} AlbumArtFactory;
- Blank::Blank()
- : VisualBase(true)
- @@ -506,6 +505,8 @@
- {
- number_of_squares = 16;
- fake_height = number_of_squares * analyzerBarWidth;
- + startHue = 220;
- + targetHue = 360;
- }
- Squares::~Squares()
- @@ -519,6 +520,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;
- @@ -550,9 +556,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 13855)
- +++ mythplugins/mythmusic/mythmusic/mainvisual.h (working copy)
- @@ -73,6 +73,7 @@
- {
- public:
- VisFactory() {m_pNextVisFactory = g_pVisFactories; g_pVisFactories = this;}
- + virtual ~VisFactory() { }
- const VisFactory* next() const {return m_pNextVisFactory;}
- virtual const QString &name(void) const = 0;
- virtual VisualBase* create(MainVisual *parent, long int winid,
- @@ -114,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();
- @@ -157,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 13855)
- +++ mythplugins/mythmusic/mythmusic/metadata.cpp (working copy)
- @@ -3,6 +3,7 @@
- #include <qregexp.h>
- #include <qdatetime.h>
- #include <qdir.h>
- +#include <qurl.h>
- using namespace std;
- @@ -11,10 +12,30 @@
- #include <mythtv/mythdbcon.h>
- #include "metadata.h"
- +#include "editmetadata.h"
- +#include "editradiometadata.h"
- #include "treebuilders.h"
- 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,12 +99,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
- {
- @@ -96,6 +112,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;
- @@ -155,6 +201,10 @@
- void Metadata::dumpToDatabase()
- {
- + /*note:temporary fix for radio*/
- + if (Format() == "cast")
- + return dumpToDatabase_cast();
- +
- QString sqlfilepath(m_filename);
- if (!sqlfilepath.contains("://"))
- {
- @@ -200,7 +250,7 @@
- m_directoryid = query.lastInsertId().toInt();
- }
- }
- -
- +
- if (m_artistid < 0)
- {
- // Load the artist id
- @@ -426,6 +476,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 (m_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 (m_id >= 1) {
- + query.bindValue(":ID", m_id);
- + }
- +
- + query.exec();
- +
- + if (m_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";
- @@ -506,7 +610,6 @@
- m_title = m_filename;
- if (m_genre == "")
- m_genre = QObject::tr("Unknown Genre");
- -
- }
- inline void Metadata::setCompilationFormatting(bool cd)
- @@ -565,7 +668,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", m_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", m_id);
- +
- + if (!query.exec())
- + MythContext::DBError("Remove music_radios", query);
- +}
- +
- void Metadata::setField(const QString &field, const QString &data)
- {
- if (field == "artist")
- @@ -610,6 +764,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", m_id).toString ();
- + }
- + }
- else
- {
- VERBOSE(VB_IMPORTANT, QString("Something asked me to return data "
- @@ -618,6 +779,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 (m_id >= 1) {
- + query.bindValue(":ID", m_id);
- + }
- +
- + if (!query.exec())
- + MythContext::DBError("Store albumart", query);
- + }
- +}
- +
- void Metadata::decRating()
- {
- if (m_rating > 0)
- @@ -712,6 +890,13 @@
- QImage image;
- + if (Format() == "cast") {
- + QVariant v = queryField ("logocached", "music_radios", "intid", m_id);
- + if (v.isValid()) {
- + image = QImage (v.toByteArray());
- + }
- + }
- + else
- if (albumArt.isImageAvailable(type))
- {
- AlbumArtImage albumart_image = albumArt.getImage(type);
- @@ -759,13 +944,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;
- @@ -820,7 +1006,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('/', "
- @@ -836,7 +1021,6 @@
- "ORDER BY music_songs.song_id;";
- QString filename, artist, album, title;
- -
- MSqlQuery query(MSqlQuery::InitCon());
- query.exec(aquery);
- @@ -850,28 +1034,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(),
- @@ -882,7 +1054,6 @@
- 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);
- @@ -931,9 +1102,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());
- +
- + 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[meta->ID()] = meta;
- +}
- +
- void AllMusic::sortTree()
- {
- m_root_node->sort();
- @@ -977,9 +1196,64 @@
- 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"), 0);
- + radio_root->setAttribute(0, 0);
- + radio_root->setAttribute(1, 0);
- + radio_root->setAttribute(2, 0);
- + radio_root->setAttribute(3, 0);
- + radio_root->setAttribute(4, 1);
- +
- +#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(), 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, 1);
- + 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, 1);
- + ++rcounter;
- + ++iterator;
- + }
- + }
- }
- bool AllMusic::putYourselfOnTheListView(TreeCheckItem *where)
- @@ -1015,13 +1289,13 @@
- QString a_label = "";
- if(an_id > 0)
- {
- -
- if (!music_map.contains(an_id))
- {
- a_label = QString(QObject::tr("Missing database entry: %1")).arg(an_id);
- *error_flag = true;
- return a_label;
- }
- +
- a_label += music_map[an_id]->FormatArtist();
- a_label += " ~ ";
- @@ -1082,11 +1356,32 @@
- return NULL;
- }
- -bool AllMusic::updateMetadata(int an_id, Metadata *the_track)
- +
- +Metadata* AllMusic::getRadioMetadata(int an_id)
- {
- if(an_id > 0)
- {
- - Metadata *mdata = getMetadata(an_id);
- + if (m_radio_map.contains(an_id))
- + {
- + return m_radio_map[an_id];
- + }
- + }
- + return NULL;
- +}
- +
- +bool AllMusic::updateMetadata(int repo_id, int an_id, Metadata *the_track)
- +{
- + if(an_id > 0)
- + {
- + Metadata *mdata = NULL;
- +
- + /* temporary fix for radios */
- + if(repo_id == 0) {
- + mdata = getMetadata(an_id);
- + } else if(repo_id == 1) {
- + mdata = getRadioMetadata(an_id);
- + }
- +
- if (mdata)
- {
- *mdata = the_track;
- @@ -1412,8 +1707,8 @@
- : m_parent(metadata)
- {
- m_imageList.setAutoDelete(true);
- -
- - findImages();
- + if (m_parent)
- + findImages();
- }
- void AlbumArtImages::findImages(void)
- Index: mythplugins/mythmusic/mythmusic/metaio.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/metaio.h (revision 13855)
- +++ 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 13855)
- +++ mythplugins/mythmusic/mythmusic/dbcheck.cpp (working copy)
- @@ -10,9 +10,379 @@
- #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 = "3";
- +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.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 = \"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\";",
- +
- + ""
- + };
- + //performActualUpdate(updates, "1008", dbver);
- + RadioPerformActualUpdate(updates, "1", dbver);
- + }
- + if (dbver == "1") {
- + const QString updates[] = {
- + "ALTER TABLE music_radios ADD logocached BLOB;",
- + ""
- + };
- + RadioPerformActualUpdate(updates, "2", dbver);
- + }
- +}
- +//----------------------------------------------------------------------------
- +
- static bool UpdateDBVersionNumber(const QString &newnumber)
- -{
- +{
- + MSqlQuery query(MSqlQuery::InitCon());
- if (!gContext->SaveSettingOnHost("MusicDBSchemaVer",newnumber,NULL))
- {
- @@ -67,6 +437,9 @@
- bool UpgradeMusicDatabaseSchema(void)
- {
- QString dbver = gContext->GetSetting("MusicDBSchemaVer");
- +
- + //Once in the main trun, the radioupgrade code back in here...
- + RadioUpgrade();
- if (dbver == currentDatabaseVersion)
- return true;
- @@ -351,7 +724,6 @@
- return false;
- }
- -
- if (dbver == "1005")
- {
- const QString updates[] = {
- @@ -617,6 +989,5 @@
- //"DROP TABLE musicmetadata;",
- //"DROP TABLE musicplaylist;",
- -
- return true;
- }
- Index: mythplugins/mythmusic/mythmusic/metadata.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/metadata.h (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/metadata.h (working copy)
- @@ -108,8 +108,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; }
- @@ -145,6 +146,8 @@
- 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();
- @@ -172,13 +175,16 @@
- 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; }
- @@ -231,6 +237,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);
- @@ -332,13 +345,16 @@
- QString getLabel(int an_id, bool *error_flag);
- Metadata* getMetadata(int an_id);
- - bool updateMetadata(int an_id, Metadata *the_track);
- + Metadata* getRadioMetadata(int an_id); /*note:temporary fix for radio*/
- + bool updateMetadata(int repo_id, 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);
- @@ -361,6 +377,8 @@
- private:
- MetadataPtrList m_all_music;
- + MetadataPtrList m_all_radios; /*note:temporary fix for radio*/
- + QMap<int,Metadata*> m_radio_map; /*note:temporary fix for radio*/
- MusicNode *m_root_node;
- int m_numPcs;
- 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 §ion)
- + {
- + 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 §ion, 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 §ion, 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"
- + "\n"
- + "[foo]\n"
- + "key1=value1\n"
- + "key2=value2\n"
- + "key3=value3\n"
- + "\n"
- + "key4=value4\n"
- + "[bar]\n"
- + "key5=value5\n"
- + "key6=value5\n"
- + "key7=7";
- +
- + CfgReader cfg;
- + cfg.parse(sample1, strlen(sample1));
- + QValueList<QString> sections = cfg.getSections();
- + QValueList<QString> keys;
- + QValueList<QString>::iterator key_it;
- + QValueList<QString>::iterator section_it;
- + section_it = sections.begin();
- +
- + assert((*section_it) == "bar");
- + keys = cfg.getKeys((*section_it));
- + key_it = keys.begin();
- + assert((*key_it) == "key5");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value5");
- + key_it++;
- + assert((*key_it) == "key6");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value5");
- + key_it++;
- + assert((*key_it) == "key7");
- + assert(cfg.getIntVal(*section_it, *key_it) == 7);
- + key_it++;
- + assert(key_it == keys.end());
- + section_it++;
- +
- + assert((*section_it) == "foo");
- + keys = cfg.getKeys((*section_it));
- + key_it = keys.begin();
- + assert((*key_it) == "key1");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value1");
- + key_it++;
- + assert((*key_it) == "key2");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value2");
- + key_it++;
- + assert((*key_it) == "key3");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value3");
- + key_it++;
- + assert((*key_it) == "key4");
- + assert(cfg.getStrVal(*section_it, *key_it) == "value4");
- + key_it++;
- + assert(key_it == keys.end());
- + section_it++;
- +
- + assert(section_it == sections.end());
- + }
- +
- + private:
- + ConfigMap cfg;
- +};
- +
- +/****************************************************************************/
- +
- +PlayListFile::PlayListFile()
- +{
- + entries.setAutoDelete(true);
- +}
- +
- +PlayListFile::~PlayListFile()
- +{
- +}
- +
- +int PlayListFile::parse(PlayListFile *pls, QTextStream *stream)
- +{
- + int parsed = 0;
- + QString d = stream->read();
- + CfgReader cfg;
- + cfg.parse(d.ascii(), d.length());
- +
- + int num_entries = cfg.getIntVal("playlist", "numberofentries", -1);
- +
- + // Some pls files have "numberofentries", some has "NumberOfEntries".
- + if (num_entries == -1)
- + num_entries = cfg.getIntVal("playlist", "NumberOfEntries", -1);
- +
- + for (int n = 1; n <= num_entries; n++)
- + {
- + PlayListFileEntry *e = new PlayListFileEntry();
- + QString t_key = QString("Title%1").arg(n);
- + QString f_key = QString("File%1").arg(n);
- + QString l_key = QString("Length%1").arg(n);
- +
- + e->setFile(cfg.getStrVal("playlist", f_key));
- + e->setTitle(cfg.getStrVal("playlist", t_key));
- + e->setLength(cfg.getIntVal("playlist", l_key));
- +
- + pls->add(e);
- + parsed++;
- + }
- +
- + return parsed;
- +}
- +
- +void PlayListFile::test(void)
- +{
- + CfgReader::test();
- +
- + {
- + // test reading an empty string
- + PlayListFile parser;
- + QString a1 = "";
- + QTextIStream aa (&a1);
- + assert (PlayListFile::parse (&parser, &aa) == 0);
- + }
- + {
- + // test reading an empty playlist
- + PlayListFile parser;
- + QString b1 = "[playlist]\n";
- + b1 += "numberofentries=0\n";
- + b1 += "version=2\n";
- + QTextIStream bb (&b1);
- + assert (parser.PlayListFile::parse(&parser, &bb) == 0);
- + }
- + {
- + // test reading a playlist w/1 item
- + PlayListFile parser;
- + QString c1 = "[playlist]\n";
- + c1 += "numberofentries=1\n";
- + c1 += "File1=file_1\n";
- + c1 += "Title1=title 1\n";
- + c1 += "Length1=1\n";
- + c1 += "version=2\n";
- + QTextIStream cc (&c1);
- + assert (PlayListFile::parse(&parser, &cc) == 1);
- + assert (parser.get (0)->File () == "file_1");
- + assert (parser.get (0)->Title () == "title 1");
- + assert (parser.get (0)->Length () == 1);
- + assert (parser.get (1) == 0);
- + }
- +
- + {
- + // test reading a playlist w/1 item but more after first item
- + PlayListFile parser;
- + QString d1 = "[playlist]\n";
- + d1 += "numberofentries=1\n";
- + d1 += "File1=file_1\n";
- + d1 += "Title1=title 1\n";
- + d1 += "Length1=1\n";
- + d1 += "File2=file_2\n";
- + d1 += "Title2=title 2\n";
- + d1 += "Length2=2\n";
- + d1 += "File3=file_3\n";
- + d1 += "Title3=title 3\n";
- + d1 += "Length3=3\n";
- + d1 += "version=2\n";
- + QTextIStream dd (&d1);
- + assert (PlayListFile::parse(&parser, &dd) == 1);
- + assert (parser.get (0)->File () == "file_1");
- + assert (parser.get (0)->Title () == "title 1");
- + assert (parser.get (0)->Length () == 1);
- + assert (parser.get (1) == 0);
- + }
- +
- + {
- + // test reading a playlist w/3 items
- + PlayListFile parser;
- + QString e1 = "[playlist]\n";
- + e1 += "numberofentries=3\n";
- + e1 += "File1=file_1\n";
- + e1 += "Title1=title 1\n";
- + e1 += "Length1=1\n";
- + e1 += "File2=file_2\n";
- + e1 += "Title2=title 2\n";
- + e1 += "Length2=2\n";
- + e1 += "File3=file_3\n";
- + e1 += "Title3=title 3\n";
- + e1 += "Length3=3\n";
- + e1 += "version=2\n";
- + QTextIStream ee (&e1);
- + assert (PlayListFile::parse(&parser, &ee) == 3);
- + assert (parser.get (0)->File () == "file_1");
- + assert (parser.get (0)->Title () == "title 1");
- + assert (parser.get (0)->Length () == 1);
- + assert (parser.get (1)->File () == "file_2");
- + assert (parser.get (1)->Title () == "title 2");
- + assert (parser.get (1)->Length () == 2);
- + assert (parser.get (2)->File () == "file_3");
- + assert (parser.get (2)->Title () == "title 3");
- + assert (parser.get (2)->Length () == 3);
- + assert (parser.get (3) == 0);
- + }
- +
- + {
- + // test reading a playlist w/2 items but more after second
- + PlayListFile parser;
- + QString f1 = "[playlist]\n";
- + f1 += "numberofentries=2\n";
- + f1 += "File1=file_1\n";
- + f1 += "Title1=title 1\n";
- + f1 += "Length1=1\n";
- + f1 += "File2=file_2\n";
- + f1 += "Title2=title 2\n";
- + f1 += "Length2=2\n";
- + f1 += "File3=file_3\n";
- + f1 += "Title3=title 3\n";
- + f1 += "Length3=3\n";
- + f1 += "version=2\n";
- + QTextIStream ff (&f1);
- + assert (PlayListFile::parse(&parser, &ff) == 2);
- + assert (parser.get (0)->File () == "file_1");
- + assert (parser.get (0)->Title () == "title 1");
- + assert (parser.get (0)->Length () == 1);
- + assert (parser.get (1)->File () == "file_2");
- + assert (parser.get (1)->Title () == "title 2");
- + assert (parser.get (1)->Length () == 2);
- + assert (parser.get (2) == 0);
- + }
- + {
- + PlayListFile parser;
- + QString f1 = "[playlist]\n";
- + f1 += "NumberOfEntries=2\n";
- + f1 += "File1=http://64.236.34.97:80/stream/1035\n";
- + f1 += "Title1=D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!\n";
- + f1 += "Length1=-1\n";
- + f1 += "File2=http://64.236.34.97:5190/stream/1035\n";
- + f1 += "Title2=D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!\n";
- + f1 += "Length2=-1\n";
- + f1 += "Version=2\n";
- + QTextIStream ff (&f1);
- + assert (PlayListFile::parse(&parser, &ff) == 2);
- + assert (parser.size () == 2);
- + assert (parser.get (0)->File () == "http://64.236.34.97:80/stream/1035");
- + assert (parser.get (0)->Title () == "D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!");
- + assert (parser.get (0)->Length () == -1);
- + assert (parser.get (1)->File () == "http://64.236.34.97:5190/stream/1035");
- + assert (parser.get (1)->Title () == "D I G I T A L L Y - I M P O R T E D - Chillout - ambient psy chillout, check out our tripy flavors!");
- + assert (parser.get (1)->Length () == -1);
- + assert (parser.get (2) == 0);
- + }
- +}
- +
- Index: mythplugins/mythmusic/mythmusic/inlines.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/inlines.h (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/inlines.h (working copy)
- @@ -11,6 +11,64 @@
- // *fast* convenience functions
- +static inline void smoothen_mono(short *in_data, short *out_data, int len, int new_len)
- +{
- + int step = new_len/len;
- + int dii = 0;
- + for (int ii = 0; ii < len; ii++)
- + {
- + out_data[dii] = in_data[ii];
- +
- + short smooth = 0;
- +
- + if (ii < len-1)
- + smooth = in_data[ii+1] - in_data[ii];
- +
- + if (smooth)
- + smooth /= step;
- +
- + for (int cc = 1; cc < step; cc++) {
- + out_data[dii+cc] = out_data[dii+cc-1] + smooth;
- + }
- +
- + dii += step;
- + }
- +}
- +
- +static inline void smoothen_stereo(short *l_in_data, short *l_out_data,
- + short *r_in_data, short *r_out_data,
- + int len, int new_len)
- +{
- + int step = new_len/len;
- + int dii = 0;
- + for (int ii = 0; ii < len; ii++)
- + {
- + l_out_data[dii] = l_in_data[ii];
- + r_out_data[dii] = r_in_data[ii];
- +
- + short l_smooth = 0;
- + short r_smooth = 0;
- +
- + if (ii < len-1) {
- + l_smooth = l_in_data[ii+1] - l_in_data[ii];
- + r_smooth = r_in_data[ii+1] - r_in_data[ii];
- + }
- +
- + if (l_smooth)
- + l_smooth /= step;
- +
- + if (r_smooth)
- + r_smooth /= step;
- +
- + for (int cc = 1; cc < step; cc++) {
- + l_out_data[dii+cc] = l_out_data[dii+cc-1] + l_smooth;
- + r_out_data[dii+cc] = r_out_data[dii+cc-1] + r_smooth;
- + }
- +
- + dii += step;
- + }
- +}
- +
- static inline void stereo16_from_stereopcm8(register short *l,
- register short *r,
- register uchar *c,
- Index: mythplugins/mythmusic/mythmusic/shoutcast.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/shoutcast.h (revision 0)
- +++ mythplugins/mythmusic/mythmusic/shoutcast.h (revision 0)
- @@ -0,0 +1,153 @@
- +/*
- + Shoutcast decoder for MythTV.
- + Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
- +*/
- +
- +#ifndef SHOUTCAST_H_
- +#define SHOUTCAST_H_
- +
- +#include "config.h"
- +#include "decoder.h"
- +#include "decoderhandler.h"
- +
- +#include <qobject.h>
- +#include <qsocketdevice.h>
- +#include <qbuffer.h>
- +#include <qurl.h>
- +#include <qdns.h>
- +#include <qmutex.h>
- +
- +#include <mythtv/httpcomms.h>
- +
- +#include <sys/time.h>
- +#include <time.h>
- +
- +
- +class ShoutCastRequest;
- +class ShoutCastResponse;
- +class ShoutCastLogoGrabber;
- +class ShoutCastBuffer;
- +
- +typedef QMap<QString,QString> ShoutCastMetaMap;
- +
- +class ShoutCastIODevice : public QObject, public QIODevice {
- + Q_OBJECT
- +public:
- + enum State {
- + NOT_CONNECTED,
- + RESOLVING,
- + CONNECTING,
- + CANT_RESOLVE,
- + CANT_CONNECT,
- + CONNECTED,
- + WRITING_HEADER,
- + READING_HEADER,
- + PLAYING,
- + STREAMING,
- + STREAMING_META,
- + STOPPED
- + };
- + static const char* stateString (const State &s);
- +
- + ShoutCastIODevice ();
- + ~ShoutCastIODevice ();
- +
- + void connectToUrl (const QUrl &url);
- + bool open(int);
- + void close();
- + void flush();
- +
- + Q_ULONG size() const;
- + Offset at () const { return 0; }
- + bool at (Offset) { return false; }
- + Q_ULONG bytesAvailable () const;
- +
- + Q_LONG readBlock(char *data, Q_ULONG sz);
- + Q_LONG writeBlock(const char *data, Q_ULONG sz);
- +
- + int getch();
- + int putch(int c);
- + int ungetch(int);
- +
- + bool getResponse(ShoutCastResponse &response);
- +
- +signals:
- + void meta(const QString &metadata);
- + void changedState(ShoutCastIODevice::State newstate);
- +
- +private slots:
- + void socketHostFound ();
- + void socketConnected ();
- + void socketConnectionClosed ();
- + void socketReadyRead ();
- + void socketBytesWritten (int);
- + void socketError (int);
- +
- +private:
- + void switchToState(const State &s);
- + int parseHeader(const char *data, Q_ULONG len);
- + int parseStream(char *data, Q_ULONG maxlen);
- + bool parseMeta(void);
- + void doKbPerSecond(int bytes_read);
- + bool waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed = 1);
- +
- + // Our tools
- + ShoutCastBuffer *m_buffer;
- + ShoutCastResponse *m_response;
- + int m_redirects;
- + QSocket *m_socket;
- +
- + // Our scratchpad
- + QByteArray m_scratchpad;
- + Q_ULONG m_scratchpad_pos;
- +
- + // Our state info
- + QUrl m_url;
- + Q_ULONG m_bytes_till_next_meta;
- + State m_state;
- + QString m_last_metadata;
- + struct timeval m_sample_tv;
- + uint m_bytes_downloaded;
- + bool m_response_gotten;
- +
- + //
- + QWaitCondition m_cond;
- +};
- +
- +class DecoderIOFactoryShoutCast : public DecoderIOFactory
- +{
- + Q_OBJECT
- + public:
- + DecoderIOFactoryShoutCast(DecoderHandler *parent);
- + ~DecoderIOFactoryShoutCast();
- +
- + void start();
- + void stop();
- + QIODevice *takeInput(void);
- +
- + protected slots:
- + void periodicallyCheckResponse(void);
- + void periodicallyCheckBuffered(void);
- + void shoutcastMeta(const QString &metadata);
- + void shoutcastChangedState(ShoutCastIODevice::State newstate);
- + void checkLogoGrabber ();
- +
- + private:
- + void socketConnected(void);
- + void socketClosed(void);
- + void socketError(int);
- +
- + int checkResponseOK();
- +
- + void makeIODevice();
- + void closeIODevice();
- + QTimer *m_timer;
- +
- + ShoutCastIODevice *m_input;
- + uint m_prebuffer;
- +
- + HttpComms m_logo_grabber;
- + QTimer *m_logo_grabber_timer;
- +};
- +
- +#endif /* SHOUTCAST_H_ */
- Index: mythplugins/mythmusic/mythmusic/pls.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/pls.h (revision 0)
- +++ mythplugins/mythmusic/mythmusic/pls.h (revision 0)
- @@ -0,0 +1,89 @@
- +/*
- + playlistfile (.pls) parser
- + Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
- +*/
- +
- +#ifndef PLS_H_
- +#define PLS_H_
- +
- +#include <qstring.h>
- +#include <qbuffer.h>
- +#include <qptrlist.h>
- +#include <qtextstream.h>
- +
- +/** \brief Class for representing entries in a pls file
- + */
- +class PlayListFileEntry
- +{
- + public:
- + PlayListFileEntry() {}
- + ~PlayListFileEntry() {}
- +
- + QString File(void) { return file; }
- + QString Title(void) { return title; }
- + int Length(void) { return length; }
- +
- + void setFile(const QString &f) { file = f; }
- + void setTitle(const QString &t) { title = t; }
- + void setLength(int l) { length = l; }
- +
- + private:
- + QString file;
- + QString title;
- + int length;
- +};
- +
- +/** \brief Class for containing the info of a pls file
- + */
- +class PlayListFile
- +{
- +public:
- + PlayListFile ();
- + ~PlayListFile ();
- +
- + /** \brief Get the number of entries in the pls file
- +
- + This returns the number of actual parsed entries, not the
- + <tt>numberofentries</tt> field.
- +
- + \returns the number of entries
- + */
- + int size(void) { return entries.count(); }
- +
- + /** \brief Get a pls file entry
- + \param i which entry to get, between 0 and the value returned by calling \p PlayListParser::size
- + \returns a pointer to a \p PlayListEntry
- + */
- + PlayListFileEntry* get(int i) { return entries.at(i); }
- +
- + /** \brief Version of the parsed pls file
- +
- + Returns the version number specified in the <tt>version</tt>
- + field of the pls file.
- +
- + \returns the version number
- + */
- + int version(void) { return _version ; }
- +
- + /** Add a entry to the playlist
- + \param e a \p PlayListFileEntry object
- + */
- + void add(PlayListFileEntry *e) { entries.append(e); }
- +
- + /** Clear out all the entries */
- + void clear(void) { entries.clear(); }
- +
- + /** Perform internal unittest */
- + static void test(void);
- +
- + /** \brief Parse a pls file.
- + \param stream the playlist file in a \p QTextStream
- + \returns the number of entries parsed
- + */
- + static int parse(PlayListFile *pls, QTextStream *stream);
- + private:
- + QPtrList<PlayListFileEntry> entries;
- + int _version;
- +};
- +
- +#endif /* PLS_H_ */
- Index: mythplugins/mythmusic/mythmusic/playbackbox.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/playbackbox.h (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/playbackbox.h (working copy)
- @@ -12,11 +12,11 @@
- #include "mainvisual.h"
- #include "metadata.h"
- #include "playlist.h"
- -#include "editmetadata.h"
- #include "databasebox.h"
- class Output;
- class Decoder;
- +class DecoderHandler;
- class PlaybackBoxMusic : public MythThemedDialog
- {
- @@ -81,6 +81,8 @@
- void occasionallyCheckCD();
- + void operationProgressTimer();
- +
- // popup menu
- void showMenu();
- void closePlaylistPopup();
- @@ -92,6 +94,8 @@
- void fromCD();
- void showSmartPlaylistDialog();
- void showSearchDialog();
- + void showAddRadioStationDialog();
- + void showRemoveRadioStationDialog();
- bool getInsertPLOptions(InsertPLOption &insertOption,
- PlayPLOption &playOption, bool &bRemoveDups);
- @@ -107,9 +111,10 @@
- void doUpdatePlaylist(QString whereClause);
- void CycleVisualizer(void);
- void updatePlaylistFromCD(void);
- - void setTrackOnLCD(Metadata *mdata);
- + void setTrackOnLCD(const Metadata *mdata);
- void updateTrackInfo(Metadata *mdata);
- void openOutputDevice(void);
- + void setupDecoderHandler(void);
- void postUpdate();
- void playFirstTrack();
- void bannerEnable(QString text, int millis);
- @@ -117,12 +122,22 @@
- void bannerToggle(Metadata *mdata);
- void savePosition(uint position);
- void restorePosition(void);
- + void addRadioMenuEntries();
- + void decoderHandlerReady(void);
- + void decoderHandlerInfo(const QString&, const QString&);
- + void decoderHandlerOperationStart(const QString &);
- + void decoderHandlerOperationStop();
- +
- + QString operation_name;
- + int operation_progress_count;
- + QTimer *decoder_handler_progress_timer;
- +
- void pushButton(UIPushButtonType *button);
- - QIODevice *input;
- AudioOutput *output;
- Decoder *decoder;
- + DecoderHandler *decoderHandler;
- QString playfile;
- QString statusString;
- @@ -156,8 +171,8 @@
- bool scrollingDown;
- Metadata *curMeta;
- + Metadata displayMeta;
- -
- unsigned int shufflemode;
- unsigned int repeatmode;
- unsigned int resumemode;
- Index: mythplugins/mythmusic/mythmusic/mainvisual.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/mainvisual.cpp (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/mainvisual.cpp (working copy)
- @@ -34,7 +34,6 @@
- // fast inlines
- #include "inlines.h"
- -
- using namespace std;
- VisFactory* VisFactory::g_pVisFactories = 0;
- @@ -203,6 +202,33 @@
- else
- len = 0;
- + if (len < 512)
- + {
- + short *new_l = 0;
- + short *new_r = 0;
- +
- + if (c == 1)
- + {
- + new_l = new short[512];
- + smoothen_mono(l, new_l, len, 512);
- + delete[] l;
- + l = new_l;
- + }
- +
- + if (c == 2)
- + {
- + new_l = new short[512];
- + new_r = new short[512];
- + smoothen_stereo(l, new_l, r, new_r, len, 512);
- + delete[] r;
- + delete[] l;
- + r = new_r;
- + l = new_l;
- + }
- +
- + len = 512;
- + }
- +
- nodes.append(new VisualNode(l, r, len, w));
- }
- @@ -342,7 +368,7 @@
- }
- InfoWidget::InfoWidget(QWidget *parent)
- - : QWidget( parent)
- + : QWidget( parent), color (0x03, 0x27, 0x44)
- {
- hide();
- }
- @@ -416,11 +442,11 @@
- y += displayRect.y();
- // only show the text box if the visualiser is actually fullscreen
- if (visMode == 2)
- - p.fillRect(displayRect, QColor ("darkblue"));
- + p.fillRect(displayRect, color);
- }
- else
- {
- - p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), QColor ("darkblue"));
- + p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), color);
- if (! albumArt.isNull())
- {
- @@ -480,7 +506,7 @@
- int x = indent;
- int y = indent;
- - p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), QColor ("darkblue"));
- + p.fillRect(0, 0, info_pixmap.width(), info_pixmap.height(), color);
- QString info_copy = info;
- for (int offset = 0; offset < textHeight; offset += fm.height())
- @@ -533,10 +559,10 @@
- long s, indexTo;
- double *magnitudesp = magnitudes.data();
- double valL, valR, tmpL, tmpR;
- - double index, step = 512.0 / size.width();
- if (node) {
- - index = 0;
- + double step = (node->length < 512 ? node->length : 512.0) / size.width();
- + double index = 0;
- for ( i = 0; i < size.width(); i++) {
- indexTo = (int)(index + step);
- if (indexTo == (int)(index))
- @@ -732,11 +758,10 @@
- double *magnitudesp = magnitudes.data();
- double val, tmp;
- - double index, step = 512.0 / size.width();
- -
- if (node)
- {
- - index = 0;
- + double step = (node->length < 512 ? node->length : 512.0) / size.width();
- + double index = 0;
- for ( i = 0; i < size.width(); i++)
- {
- indexTo = (int)(index + step);
- Index: mythplugins/mythmusic/mythmusic/decoderhandler.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/decoderhandler.h (revision 0)
- +++ mythplugins/mythmusic/mythmusic/decoderhandler.h (revision 0)
- @@ -0,0 +1,206 @@
- +#ifndef DECODERHANDLER_H_
- +#define DECODERHANDLER_H_
- +
- +#include <qobject.h>
- +#include <qiodevice.h>
- +#include <qfile.h>
- +#include <qhttp.h>
- +#include <qurl.h>
- +
- +#include <mythtv/mythobservable.h>
- +
- +#include "pls.h"
- +
- +class QUrl;
- +class QUrlOperator;
- +
- +class Decoder;
- +class Metadata;
- +class DecoderIOFactory;
- +class DecoderHandler;
- +
- +/** \brief Events sent by the \p DecoderHandler and it's helper classes.
- + */
- +class DecoderHandlerEvent : public MythEvent
- +{
- + public:
- + enum Type { Ready = (QEvent::User + 300), Meta, Info, OperationStart, OperationStop, Error};
- +
- + DecoderHandlerEvent(Type t)
- + : MythEvent(t), m_msg(0), m_meta(0)
- + { ; }
- +
- + DecoderHandlerEvent(Type t, QString *e)
- + : MythEvent(t), m_msg(e), m_meta(0)
- + { ; }
- +
- + DecoderHandlerEvent(const Metadata &m);
- + ~DecoderHandlerEvent();
- +
- + const QString *getMessage() const { return m_msg; }
- + const Metadata *getMetadata() const { return m_meta; }
- +
- + virtual DecoderHandlerEvent *clone();
- +
- +private:
- + QString *m_msg;
- + Metadata *m_meta;
- +};
- +
- +/** \brief Class for starting stream decoding.
- +
- + This class handles starting the \p Decoder for the \p
- + PlaybackBox via DecoderIOFactorys.
- +
- + It operates on a playlist, either created with a single file, by
- + loading a .pls or downloading it, and for each entry creates an
- + appropriate DecoderIOFactory. The creator is simply a intermediate
- + class that translates the next URL in the playlist to
- + QIODevice. Ie. the DecoderIOFactoryFile just returns a QFile,
- + whereas the DecoderIOFactoryShoutcast returns a QSocket subclass,
- + where reads do the necessary translation of the shoutcast stream.
- + */
- +class DecoderHandler : public QObject, public MythObservable
- +{
- + Q_OBJECT
- + friend class DecoderIOFactory;
- + public:
- + typedef enum {
- + ACTIVE,
- + LOADING,
- + STOPPED
- + } State;
- +
- + DecoderHandler();
- + virtual ~DecoderHandler();
- +
- + Decoder *getDecoder(void) { return m_decoder; }
- + void start(Metadata *mdata);
- +
- + void stop(void);
- + void customEvent(QCustomEvent*);
- + bool done();
- + bool next(void);
- + void error(const QString &msg);
- +
- + protected:
- + void doOperationStart(const QString &name);
- + void doOperationStop(void);
- + void doConnectDecoder(const QUrl &url, const QString &format);
- + void doFailed(const QUrl &url, const QString &message);
- + void doInfo(const QString &message);
- +
- + private:
- + int m_state;
- + int m_playlist_pos;
- + PlayListFile m_playlist;
- + DecoderIOFactory *m_io_factory;
- + Decoder *m_decoder;
- + Metadata *m_meta;
- + bool m_op;
- + uint m_redirects;
- +
- + static const uint MaxRedirects = 3;
- +
- + bool createPlaylist(const QUrl &url);
- + bool createPlaylistForSingleFile(const QUrl &url);
- + bool createPlaylistFromFile(const QUrl &url);
- + bool createPlaylistFromRemoteUrl(const QUrl &url);
- +
- + bool haveIOFactory(void) { return m_io_factory != 0; }
- + DecoderIOFactory *getIOFactory(void) { return m_io_factory; }
- + void createIOFactory(const QUrl &url);
- + void deleteIOFactory(void);
- +};
- +
- +/** \brief The glue between the DecoderHandler and the Decoder
- +
- + The DecoderIOFactory is responsible for opening the QIODevice that
- + is given to the Decoder....
- + */
- +class DecoderIOFactory : public QObject, public MythObservable
- +{
- + public:
- + DecoderIOFactory(DecoderHandler *parent);
- + virtual ~DecoderIOFactory();
- +
- + virtual void start() = 0;
- + virtual void stop() = 0;
- + virtual QIODevice *takeInput(void) = 0;
- +
- + void setUrl (const QUrl &url) { m_url = url; }
- + void setMeta (Metadata *meta) { m_meta = meta; }
- +
- + static const uint DefaultPrebufferSize = 128 * 1024;
- + static const uint MaxRedirects = 3;
- +
- + protected:
- + void doConnectDecoder(const QString &format);
- + Decoder *getDecoder(void);
- + void doFailed(const QString &message);
- + void doInfo(const QString &message);
- + void doOperationStart(const QString &name);
- + void doOperationStop(void);
- +
- + QUrl m_url;
- + Metadata *m_meta;
- +
- + private:
- + DecoderHandler *m_handler;
- +};
- +
- +class DecoderIOFactoryFile : public DecoderIOFactory
- +{
- + Q_OBJECT
- + public:
- + DecoderIOFactoryFile(DecoderHandler *parent);
- + ~DecoderIOFactoryFile();
- + void start();
- + void stop() {}
- + QIODevice *takeInput(void);
- +
- + private:
- + QIODevice *m_input;
- +};
- +
- +class DecoderIOFactoryUrl : public DecoderIOFactory
- +{
- + Q_OBJECT
- + public:
- + DecoderIOFactoryUrl(DecoderHandler *parent);
- + ~DecoderIOFactoryUrl();
- +
- + void start();
- + void stop();
- + QIODevice *takeInput(void);
- +
- + protected slots:
- + void finished(QNetworkOperation *op);
- + void start(QNetworkOperation *op);
- + void data(const QByteArray & data, QNetworkOperation * op);
- +
- + private:
- + void doStart(void);
- + void doClose(void);
- +
- + bool m_started;
- + QUrlOperator *m_url_op;
- + QFile *m_output;
- + QFile *m_input;
- +};
- +
- +class DecoderIOFactoryMqp : public DecoderIOFactory
- +{
- + Q_OBJECT
- + public:
- + DecoderIOFactoryMqp(DecoderHandler *parent);
- + ~DecoderIOFactoryMqp();
- + void start();
- + void stop();
- + QIODevice *takeInput(void);
- +
- + private:
- + QIODevice *m_input;
- +};
- +
- +#endif /* DECODERHANDLER_H_ */
- Index: mythplugins/mythmusic/mythmusic/editradiometadata.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/editradiometadata.h (revision 0)
- +++ mythplugins/mythmusic/mythmusic/editradiometadata.h (revision 0)
- @@ -0,0 +1,74 @@
- +#ifndef EDITRADIOMETADATA_H_
- +#define EDITRADIOMETADATA_H_
- +
- +#include <iostream>
- +using namespace std;
- +
- +#include <mythtv/mythdialogs.h>
- +
- +#include "editmetadata.h"
- +
- +class UIPhoneEntry;
- +
- +class EditRadioMetadataDialog : public MythThemedDialog
- +{
- +
- + Q_OBJECT
- +
- + public:
- +
- + EditRadioMetadataDialog(Metadata *source_metadata,
- + MythMainWindow *parent,
- + QString window_name,
- + QString theme_filename,
- + const char* name = 0);
- + ~EditRadioMetadataDialog();
- +
- + void keyPressEvent(QKeyEvent *e);
- + void wireUpTheme(void);
- + void fillWidgets(void);
- +
- + public slots:
- +
- + void closeDialog(void);
- + void searchStation(void);
- + void searchGenre(void);
- + void incRating(bool up_or_down);
- + void showSaveMenu(void);
- + bool verifyEntries();
- + void saveNewToDatabase();
- + void saveToDatabase();
- + void cancelPopup();
- + void editLostFocus();
- +
- + private:
- +
- + void fillSearchList(QString field, QString table);
- + bool showList(QString caption, QString &value);
- +
- + Metadata *m_metadata;
- + Metadata *m_sourceMetadata ;
- + MythPopupBox *popup;
- +
- + //
- + // GUI stuff
- + //
- +
- + UIRemoteEditType *station_edit;
- + UIRemoteEditType *channel_edit;
- + UIRemoteEditType *url_edit;
- + UIRemoteEditType *metaformat_edit;
- + UIRemoteEditType *genre_edit;
- +
- + UIRepeatedImageType *rating_image;
- +
- + UIPushButtonType *searchstation_button;
- + UIPushButtonType *searchgenre_button;
- + UIPushButtonType *rating_button;
- +
- + UITextButtonType *done_button;
- +
- + QStringList searchList;
- +};
- +
- +#endif
- Index: mythplugins/mythmusic/mythmusic/shoutcast.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/shoutcast.cpp (revision 0)
- +++ mythplugins/mythmusic/mythmusic/shoutcast.cpp (revision 0)
- @@ -0,0 +1,1142 @@
- +/*
- + Shoutcast decoder for MythTV.
- + Eskil Heyn Olsen, 2005, distributed under the GPL as part of mythtv.
- + */
- +
- +#include <qapplication.h>
- +#include <qregexp.h>
- +#include <qsocket.h>
- +#include <mythtv/mythcontext.h>
- +#include "shoutcast.h"
- +#include "metadata.h"
- +#include <assert.h>
- +#include <algorithm>
- +
- +/****************************************************************************/
- +
- +#define WAIT_FOR_MS 1500
- +#define MAX_ALLOWED_META_SIZE 1024 * 100
- +#define MAX_REDIRECTS 3
- +#define PREBUFFER_SECS 5
- +
- +/****************************************************************************/
- +
- +struct timeval& operator-= (struct timeval &lhs, const struct timeval &rhs) {
- + lhs.tv_sec -= rhs.tv_sec;
- + if (lhs.tv_usec >= rhs.tv_usec) {
- + lhs.tv_usec -= rhs.tv_usec;
- + } else {
- + lhs.tv_sec --;
- + lhs.tv_usec = 1000000 - (rhs.tv_usec - lhs.tv_usec);
- + }
- + return lhs;
- +}
- +
- +bool operator!=(const struct timeval &lhs, const struct timeval &rhs) {
- + if (lhs.tv_sec == rhs.tv_sec)
- + return lhs.tv_usec != rhs.tv_usec;
- + return true;
- +}
- +
- +bool operator==(const struct timeval &lhs, const struct timeval &rhs) {
- + if (lhs.tv_sec == rhs.tv_sec)
- + return lhs.tv_usec == rhs.tv_usec;
- + return false;
- +}
- +
- +bool operator<(const struct timeval &lhs, const struct timeval &rhs) {
- + if (lhs.tv_sec == rhs.tv_sec)
- + return lhs.tv_usec < rhs.tv_usec;
- + return lhs.tv_sec < rhs.tv_sec;
- +}
- +
- +bool operator<=(const struct timeval &lhs, const struct timeval &rhs) {
- + return lhs == rhs || lhs < rhs;
- +}
- +
- +bool operator>(const struct timeval &lhs, const struct timeval &rhs) {
- + if (lhs.tv_sec == rhs.tv_sec)
- + return lhs.tv_usec > rhs.tv_usec;
- + return lhs.tv_sec > rhs.tv_sec;
- +}
- +
- +bool operator>=(const struct timeval &lhs, const struct timeval &rhs) {
- + return lhs == rhs || lhs > rhs;
- +}
- +
- +void test_timeval_operators () {
- + struct timeval a, b;
- + a.tv_sec = 1174197435;
- + a.tv_usec = 279159;
- + b.tv_sec = 1174197435;
- + b.tv_usec = 208796;
- +
- + assert (a == a);
- + assert (b == b);
- +
- + assert (a != b);
- +
- + assert (a > b);
- + assert (a >= b);
- + assert (a >= a);
- +
- + assert (b < a);
- + assert (b <= a);
- + assert (b <= b);
- +
- + a -= b;
- + assert (a.tv_sec == 0);
- + assert (a.tv_usec == 70363);
- +
- + a.tv_sec = 1174197735;
- + a.tv_usec = 40493;
- + b.tv_sec = 1174197735;
- + b.tv_usec = 29043;
- + a -= b;
- + assert (a.tv_sec == 0);
- + assert (a.tv_usec == 11450);
- +}
- +
- +/****************************************************************************/
- +
- +const char* ShoutCastIODevice::stateString (const State &s) {
- +#define TO_STRING(a) case a: return #a
- + switch (s) {
- + TO_STRING (NOT_CONNECTED);
- + TO_STRING (RESOLVING);
- + TO_STRING (CONNECTING);
- + TO_STRING (CANT_RESOLVE);
- + TO_STRING (CANT_CONNECT);
- + TO_STRING (CONNECTED);
- + TO_STRING (WRITING_HEADER);
- + TO_STRING (READING_HEADER);
- + TO_STRING (PLAYING);
- + TO_STRING (STREAMING);
- + TO_STRING (STREAMING_META);
- + TO_STRING (STOPPED);
- + default:
- + return "unknown state";
- + }
- +#undef TO_STRING
- +}
- +
- +int moveFromByteArray(QByteArray &array, char *data, Q_ULONG maxlen) {
- + Q_ULONG consumed = array.size ();
- +
- + if (consumed > maxlen)
- + consumed = maxlen;
- + if (data)
- + memcpy (data, array.data(), consumed);
- + if (consumed != array.size ())
- + memmove (array.data(), array.data() + consumed, array.size() - consumed);
- + array.resize(array.size() - consumed);
- +
- + return consumed;
- +}
- +
- +/****************************************************************************/
- +
- +/** \brief Class to download shoutcast station logos
- +
- + Runs a HttpComms request in a thread until success/failure or
- + stop_transfer is called.
- + */
- +class ShoutCastLogoGrabber : public QThread {
- +public:
- + ShoutCastLogoGrabber(const Metadata *m) : m_meta(*m), m_stop(false) { }
- +
- + void stop_transfer() {
- + m_stop = true;
- + }
- +
- + void run() {
- + HttpComms *comms = new HttpComms;
- +
- + QString urlstr;
- + m_meta.getField("x-cast-logourl", &urlstr);
- +
- + QUrl url(urlstr);
- + comms->request(url, 10000, false);
- +
- + while (!m_stop && !comms->isDone())
- + usleep(500);
- +
- + if (m_stop)
- + return;
- +
- + if (comms->isTimedout() || comms->getStatusCode() != 200) {
- + comms->deleteLater();
- + comms = NULL;
- + return;
- + }
- +
- + m_meta.setAlbumArt(comms->getRawData());
- + comms->deleteLater();
- + comms = NULL;
- + }
- +
- +private:
- + Metadata m_meta;
- + bool m_stop;
- +};
- +
- +/****************************************************************************/
- +
- +/** \brief A buffer class
- + The intent with this it can be replaced with may RingBuffer ?
- + */
- +class ShoutCastBuffer {
- +public:
- + ShoutCastBuffer () : m_pos (0), m_size (0) { }
- + ~ShoutCastBuffer () { }
- +
- + Q_ULONG Read (char *data, Q_ULONG max) {
- + QMutexLocker holder (&m_socket_mutex);
- + const QByteArray &next_buffer = m_buffers.front();
- + const char *next_data = next_buffer.data () + m_pos;
- + uint next_size = next_buffer.size () - m_pos;
- +
- + if (max > m_size)
- + max = m_size;
- +
- + if (max > next_size)
- + max = next_size;
- +
- + memcpy (data, next_data, max);
- +
- + m_pos += max;
- + m_size -= max;
- +
- + if (max == next_size) {
- + m_pos = 0;
- + m_buffers.pop_front();
- + }
- +
- + return max;
- + }
- +
- + /** \brief Add data to the buffer
- + \param data the bytes to add, will be owned by the ShoutCastBuffer
- + \param sz the size of data
- + */
- + void Write (char *data, uint sz) {
- + if (sz == 0)
- + return;
- +
- + QMutexLocker holder (&m_socket_mutex);
- + QByteArray array (sz);
- + array.assign (data, sz);
- + m_buffers.push_back (array);
- + m_size += sz;
- + }
- +
- + /** \brief Add data to the buffer
- + \param arr the byte array to add
- + */
- + void Write (QByteArray &array) {
- + if (array.size () == 0)
- + return;
- + QMutexLocker holder (&m_socket_mutex);
- + m_buffers.push_back (array);
- + m_size += array.size ();
- + }
- +
- + Q_ULONG ReadBufAvail () const {
- + return m_size;
- + }
- +
- + static void selfTest () {
- + VERBOSE(VB_IMPORTANT, "Selftesting ShoutCastBuffer");
- + ShoutCastBuffer iobuf;
- + char *data_1 = strdup ("aaaaa");
- + char *data_2 = strdup ("bbbbb");
- + QByteArray arr (5);
- + arr.duplicate ("ccccc", 5);
- +
- + iobuf.Write (data_1, strlen (data_1));
- + assert (iobuf.ReadBufAvail() == 5);
- + iobuf.Write (data_2, strlen (data_2));
- + assert (iobuf.ReadBufAvail() == 10);
- + iobuf.Write (arr);
- + assert (iobuf.ReadBufAvail() == 15);
- +
- + char *data = (char*)alloca(iobuf.ReadBufAvail());
- + assert (iobuf.Read (data, 3) == 3);
- + assert (iobuf.ReadBufAvail() == 12);
- + assert (memcmp (data ,"aaa", 3) == 0);
- +
- + assert (iobuf.Read (data, 3) == 2);
- + assert (iobuf.ReadBufAvail() == 10);
- + assert (memcmp (data ,"aa", 2) == 0);
- +
- + assert (iobuf.Read (data, 10) == 5);
- + assert (iobuf.ReadBufAvail() == 5);
- + assert (memcmp (data ,"bbbbb", 5) == 0);
- +
- + assert (iobuf.Read (data, 5) == 5);
- + assert (iobuf.ReadBufAvail() == 0);
- + assert (memcmp (data ,"ccccc", 5) == 0);
- + }
- +
- +private:
- + QValueList<QByteArray> m_buffers;
- + uint m_pos;
- + Q_ULONG m_size;
- + QMutex m_socket_mutex;
- +};
- +
- +
- +/****************************************************************************/
- +
- +class ShoutCastRequest
- +{
- +public:
- + ShoutCastRequest() { }
- + ShoutCastRequest(const QUrl &url) { setUrl(url); }
- + ~ShoutCastRequest() { }
- + const char *data(void) { return m_data.data(); }
- + uint size(void) { return m_data.size(); }
- +
- +private:
- + void setUrl(const QUrl &url) {
- + QString hdr;
- + hdr = QString("GET %1 HTTP/1.1\r\n"
- + "Host: %2\r\n"
- + "User-Agent: mythmusic/svn\r\n"
- + "Keep-Alive:\r\n"
- + "Connection: TE, Keep-Alive\r\n"
- + "TE: trailers\r\n"
- + "icy-metadata:1\r\n"
- + "\r\n").arg(url.path()).arg(url.host());
- +
- + m_data.duplicate(hdr.ascii(), hdr.length());
- + }
- +
- + QByteArray m_data;
- +};
- +
- +
- +/****************************************************************************/
- +
- +
- +class ShoutCastResponse
- +{
- + public:
- + ShoutCastResponse() { }
- + ~ShoutCastResponse() { }
- +
- + int getMetaint(void) { return getInt("icy-metaint"); }
- + int getBitrate(void) { return getInt("icy-br"); }
- + QString getGenre(void) { return getString("icy-genre"); }
- + QString getName(void) { return getString("icy-name"); }
- + int getStatus(void) { return getInt("status"); }
- + bool isICY(void) { return QString(m_data["protocol"]).left(3) == "ICY"; }
- + QString getContent(void) { return getString("content-type"); }
- + QString getLocation(void) { return getString("location"); }
- +
- + QString getString(const QString &key) { return m_data[key]; }
- + int getInt(const QString &key) { return m_data[key].toInt(); }
- +
- + int fillResponse(const char *data, int len);
- + private:
- + QMap<QString,QString> m_data;
- +};
- +
- +/** \brief Consume bytes and parse shoutcast header
- + \returns number of bytes consumed
- +*/
- +int ShoutCastResponse::fillResponse(const char *s, int l)
- +{
- + QCString d(s, l);
- + int result = 0;
- + // check each line
- + for (;;)
- + {
- + int pos = d.find("\r");
- +
- + if (pos <= 0)
- + break;
- +
- + // Extract the line
- + QCString snip(d.data(), pos + 1);
- + d.remove(0, pos + 2);
- + result += pos + 2;
- +
- + if (snip.left(4) == "ICY ")
- + {
- + int space = snip.find(' ');
- + m_data["protocol"] = "ICY";
- + QString tmp = snip.mid(space).simplifyWhiteSpace();
- + int second_space = tmp.find(' ');
- + if (second_space > 0) {
- + m_data["status"] = tmp.left(second_space);
- + } else {
- + m_data["status"] = tmp;
- + }
- + }
- + else if (snip.left(7) == "HTTP/1.")
- + {
- + int space = snip.find(' ');
- + m_data["protocol"] = snip.left(space);
- + QString tmp = snip.mid(space).simplifyWhiteSpace();
- + int second_space = tmp.find(' ');
- + if (second_space > 0) {
- + m_data["status"] = tmp.left(second_space);
- + } else {
- + m_data["status"] = tmp;
- + }
- + }
- + else if (snip.left(9).lower() == "location:")
- + {
- + m_data["location"] = snip.mid(9).stripWhiteSpace();
- + }
- + else if (snip.left(13).lower() == "content-type:")
- + {
- + m_data["content-type"] = snip.mid(13).stripWhiteSpace();
- + }
- + else if (snip.left(4) == "icy-")
- + {
- + int pos = snip.find(':');
- + QString key = snip.left(pos);
- + m_data[key.ascii()] = snip.mid(pos+1).stripWhiteSpace();
- + }
- + }
- +
- + return result;
- +}
- +
- +/****************************************************************************/
- +
- +class ShoutCastMetaParser
- +{
- + public:
- + ShoutCastMetaParser() { }
- + ~ShoutCastMetaParser() { }
- +
- + void setMetaFormat(const QString &metaformat);
- + ShoutCastMetaMap parseMeta(QString meta);
- +
- + private:
- + QString m_meta_format;
- + int m_meta_artist_pos;
- + int m_meta_title_pos;
- + int m_meta_album_pos;
- +};
- +
- +void ShoutCastMetaParser::setMetaFormat(const QString &metaformat)
- +{
- +/*
- + We support these metatags :
- + %a - artist
- + %t - track
- + %b - album
- + %r - random bytes
- + */
- + m_meta_format = metaformat;
- +
- + m_meta_artist_pos = 0;
- + m_meta_title_pos = 0;
- + m_meta_album_pos = 0;
- +
- + int assign_index = 1;
- + int pos = 0;
- +
- + pos = m_meta_format.find("%", pos);
- + while (pos >= 0) {
- + pos++;
- + QChar ch = m_meta_format.at(pos);
- +
- + if (ch == '%') {
- + pos++;
- + }
- + else if (ch == 'r' || ch == 'a' || ch == 'b' || ch == 't')
- + {
- + if (ch == 'a')
- + m_meta_artist_pos = assign_index;
- +
- + if (ch == 'b')
- + m_meta_album_pos = assign_index;
- +
- + if (ch == 't')
- + m_meta_title_pos = assign_index;
- +
- + assign_index++;
- + } else
- + fprintf(stderr, "CastDecoder: malformed metaformat '%s'\n", m_meta_format.ascii());
- +
- + pos = m_meta_format.find("%", pos);
- + }
- +
- + m_meta_format.replace("%a", "(.*)");
- + m_meta_format.replace("%t", "(.*)");
- + m_meta_format.replace("%b", "(.*)");
- + m_meta_format.replace("%r", "(.*)");
- + m_meta_format.replace("%%", "%");
- +}
- +
- +ShoutCastMetaMap ShoutCastMetaParser::parseMeta(QString meta)
- +{
- + QCString metastring(meta);
- + ShoutCastMetaMap result;
- + int title_begin_pos = metastring.find("StreamTitle='");
- + int title_end_pos;
- +
- + if (title_begin_pos >= 0)
- + {
- + title_begin_pos += 13;
- + title_end_pos = metastring.find("';", title_begin_pos);
- + QCString title = metastring.mid(title_begin_pos,
- + title_end_pos - title_begin_pos);
- + QRegExp rx;
- + rx.setPattern(m_meta_format);
- + if (rx.search(title) != -1)
- + {
- + VERBOSE(VB_PLAYBACK, QString("ShoutCast: Meta : '%1'").
- + arg(meta));
- + VERBOSE(VB_PLAYBACK, QString("ShoutCast: Parsed as: '%1' by '%2'").
- + arg(rx.cap(m_meta_title_pos)).
- + arg(rx.cap(m_meta_artist_pos)));
- +
- + if (m_meta_title_pos > 0)
- + result["title"] = rx.cap(m_meta_title_pos);
- +
- + if (m_meta_artist_pos > 0)
- + result["artist"] = rx.cap(m_meta_artist_pos);
- +
- + if (m_meta_album_pos > 0)
- + result["album"] = rx.cap(m_meta_album_pos);
- + }
- + }
- +
- + return result;
- +}
- +
- +/****************************************************************************/
- +
- +ShoutCastIODevice::ShoutCastIODevice ()
- + : m_redirects (0),
- + m_scratchpad_pos (0),
- + m_state (NOT_CONNECTED)
- +{
- + // Run some quick unittests. Put this here since there's no
- + // 'formal' unittest harness blabla...
- + ShoutCastBuffer::selfTest();
- + test_timeval_operators ();
- + // done testing...
- +
- + m_buffer = new ShoutCastBuffer;
- + m_socket = new QSocket;
- + m_response = new ShoutCastResponse;
- +
- + connect (m_socket, SIGNAL (hostFound ()), SLOT (socketHostFound ()));
- + connect (m_socket, SIGNAL (connected ()), SLOT (socketConnected ()));
- + connect (m_socket, SIGNAL (connectionClosed ()), SLOT (socketConnectionClosed ()));
- + connect (m_socket, SIGNAL (readyRead ()), SLOT (socketReadyRead ()));
- + connect (m_socket, SIGNAL (error (int)), SLOT (socketError (int)));
- +
- + switchToState (NOT_CONNECTED);
- +
- + setFlags (IO_Direct | IO_Async | IO_ReadOnly);
- +
- + m_sample_tv.tv_sec = 0;
- + m_sample_tv.tv_usec = 0;
- +}
- +
- +ShoutCastIODevice::~ShoutCastIODevice () {
- + delete m_buffer;
- + delete m_response;
- + m_socket->close ();
- + m_socket->disconnect (this);
- + m_socket->deleteLater ();
- +}
- +
- +void ShoutCastIODevice::connectToUrl (const QUrl &url) {
- + m_url = url;
- + switchToState (RESOLVING);
- + setMode(IO_ReadOnly);
- + setState(IO_Open);
- + return m_socket->connectToHost (m_url.host (), m_url.port ());
- +}
- +
- +bool ShoutCastIODevice::open(int) {
- + VERBOSE(VB_NETWORK, "ShoutCastIODevice: open");
- + return true;
- +}
- +
- +void ShoutCastIODevice::close() {
- + return m_socket->close();
- +}
- +
- +void ShoutCastIODevice::flush() {
- + return m_socket->flush();
- +}
- +
- +Q_ULONG ShoutCastIODevice::size() const {
- + return m_buffer->ReadBufAvail();
- +}
- +
- +bool ShoutCastIODevice::waitForData (Q_ULONG millisecs, Q_ULONG bytes_needed) {
- + struct timeval tv_begin;
- + gettimeofday (&tv_begin, 0);
- +
- + do {
- + if (m_state != STOPPED && m_buffer->ReadBufAvail() < bytes_needed) {
- + m_cond.wait (millisecs);
- + }
- +
- + if (m_buffer->ReadBufAvail() >= bytes_needed)
- + return true;
- +
- + struct timeval tv_delta;
- + gettimeofday (&tv_delta, 0);
- + tv_delta -= tv_begin;
- + Q_ULONG delta = (tv_delta.tv_sec * 1000) + (tv_delta.tv_usec / 1000);
- + if (delta > millisecs)
- + millisecs = 0;
- + else
- + millisecs -= delta;
- +
- + VERBOSE(VB_PLAYBACK, QString ("slept for %1 ms, adjust to %2, avail is %3/%4").
- + arg((tv_delta.tv_sec * 1000) + (tv_delta.tv_usec / 1000)).
- + arg(millisecs).arg(m_buffer->ReadBufAvail()).arg(bytes_needed));
- + } while (millisecs > 0);
- +
- + if (m_buffer->ReadBufAvail() < bytes_needed)
- + return false;
- +
- + return true;
- +}
- +
- +Q_LONG ShoutCastIODevice::readBlock(char *data, Q_ULONG maxlen) {
- + int result = 0;
- +
- + if (waitForData (WAIT_FOR_MS, 1024) == false) {
- + VERBOSE(VB_PLAYBACK, "Waited for data in stream, got none");
- + switchToState(STOPPED);
- + }
- +
- + if (m_state == STREAMING_META && parseMeta())
- + switchToState(STREAMING);
- +
- + if (m_state == STREAMING)
- + {
- + if (waitForData (WAIT_FOR_MS) == false) {
- + VERBOSE(VB_PLAYBACK, "Waited for in stream again, got none");
- + switchToState(STOPPED);
- + } else {
- + result = parseStream(data, maxlen);
- + if (m_bytes_till_next_meta == 0)
- + switchToState(STREAMING_META);
- + }
- + }
- +
- + if (m_state != STOPPED)
- + VERBOSE(VB_NETWORK, QString("ShoutCastIO2: %1 kb in buffer, btnm=%2/%3 state=%4, rb=%5/%6").
- + arg(m_buffer->ReadBufAvail () / 1024, 4).
- + arg(m_bytes_till_next_meta, 4).
- + arg(m_response->getMetaint()).
- + arg(stateString (m_state)).
- + arg(result).
- + arg(maxlen));
- + else
- + VERBOSE(VB_NETWORK, QString("ShoutCastIO2: stopped"));
- +
- + return result;
- +}
- +
- +Q_LONG ShoutCastIODevice::writeBlock(const char *data, Q_ULONG sz) {
- + return m_socket->writeBlock (data, sz);
- +}
- +
- +Q_ULONG ShoutCastIODevice::bytesAvailable () const {
- + return m_buffer->ReadBufAvail ();
- +}
- +
- +int ShoutCastIODevice::getch() {
- + assert (0);
- + return -1;
- +}
- +int ShoutCastIODevice::putch(int) {
- + assert (0);
- + return -1;
- +}
- +int ShoutCastIODevice::ungetch(int) {
- + assert (0);
- + return -1;
- +}
- +
- +void ShoutCastIODevice::socketHostFound () {
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Host Found");
- + switchToState (CONNECTING);
- +}
- +
- +void ShoutCastIODevice::socketConnected () {
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Connected");
- + switchToState (CONNECTED);
- +
- + ShoutCastRequest request (m_url);
- + Q_ULONG written = m_socket->writeBlock (request.data (), request.size ());
- + VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Sending Request, %1 of %1 bytes").arg(written).arg(request.size()));
- +
- + if (written != request.size ()) {
- + m_scratchpad.duplicate (request.data () + written, request.size () - written);
- + m_scratchpad_pos = 0;
- + connect (m_socket, SIGNAL (bytesWritten (int)), SLOT (socketBytesWritten (int)));
- + switchToState (WRITING_HEADER);
- + } else {
- + switchToState (READING_HEADER);
- + }
- +}
- +
- +void ShoutCastIODevice::socketConnectionClosed () {
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Connection Closed");
- + switchToState (STOPPED);
- +}
- +
- +void ShoutCastIODevice::socketReadyRead () {
- + Q_ULONG sz = m_socket->bytesAvailable();
- +
- + //VERBOSE(VB_IMPORTANT, QString ("ShoutCastIO2: %1 bytes readable").arg(sz));
- +
- + char *data = (char*)malloc (sz);
- + Q_ULONG actual_sz = m_socket->readBlock (data, sz);
- + if (actual_sz < sz)
- + data = (char*)realloc (data, actual_sz);
- +
- + if (m_state == READING_HEADER) {
- + if (parseHeader (data, actual_sz) == 1) {
- + if (m_response->isICY () && m_response->getStatus () == 200) {
- + switchToState (PLAYING);
- +
- + m_response_gotten = true;
- +
- + // debug, collect kb/s info
- + gettimeofday (&m_sample_tv, NULL);
- + m_bytes_downloaded = 0;
- + m_bytes_till_next_meta = m_response->getMetaint();
- +
- + // whatever's left in the scratch pad, toss into m_buffer
- + free (data);
- + m_buffer->Write (m_scratchpad);
- + m_cond.wakeOne ();
- +
- + switchToState (STREAMING);
- + } else if (m_response->getStatus () == 302) {
- + if (++m_redirects > MAX_REDIRECTS) {
- + VERBOSE (VB_NETWORK, QString ("Too many redirects"));
- + switchToState (STOPPED);
- + } else {
- + VERBOSE (VB_NETWORK, QString ("Redirect to %1").arg(m_response->getLocation()));
- + connectToUrl(m_url);
- + }
- + } else {
- + VERBOSE (VB_NETWORK, QString ("Unknown response status %1").arg (m_response->getStatus ()));
- + switchToState (STOPPED);
- + }
- +
- + }
- + } else {
- + m_buffer->Write (data, actual_sz);
- + m_cond.wakeOne ();
- + doKbPerSecond(actual_sz);
- + }
- +}
- +
- +void ShoutCastIODevice::socketBytesWritten (int) {
- + Q_ULONG written = m_socket->writeBlock (m_scratchpad.data () + m_scratchpad_pos,
- + m_scratchpad.size () - m_scratchpad_pos);
- + VERBOSE(VB_NETWORK, QString ("ShoutCastIO: %1 bytes written").arg(written));
- +
- + m_scratchpad_pos += written;
- + if (m_scratchpad_pos == m_scratchpad.size ()) {
- + m_scratchpad.truncate (0);
- + disconnect (m_socket, SIGNAL (bytesWritten (int)), this, 0);
- + switchToState (READING_HEADER);
- + }
- +}
- +
- +void ShoutCastIODevice::socketError (int error) {
- + VERBOSE(VB_NETWORK, QString ("ShoutCastIO: Socket Error %1").arg(error));
- +
- + switch (error) {
- + case QSocket::ErrConnectionRefused:
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Connection Refused");
- + switchToState(CANT_CONNECT);
- + break;
- + case QSocket::ErrHostNotFound:
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Host Not Found");
- + switchToState(CANT_RESOLVE);
- + break;
- + case QSocket::ErrSocketRead:
- + VERBOSE(VB_NETWORK, "ShoutCastIO2: Error Socket Read");
- + switchToState (STOPPED);
- + break;
- + }
- +}
- +
- +void ShoutCastIODevice::switchToState(const State &state)
- +{
- + switch (state)
- + {
- + case PLAYING:
- + VERBOSE(VB_PLAYBACK, QString ("Playing %1 (%2) at %3 kbps").
- + arg(m_response->getName()).
- + arg(m_response->getGenre()).
- + arg(m_response->getBitrate()));
- + break;
- + case STREAMING:
- + if (m_state == STREAMING_META)
- + m_bytes_till_next_meta = m_response->getMetaint();
- + break;
- + case STOPPED:
- + m_socket->close ();
- + m_cond.wakeAll ();
- + break;
- + default:
- + break;
- + }
- +
- + m_state = state;
- + emit changedState(m_state);
- +}
- +
- +int ShoutCastIODevice::parseHeader(const char *data, Q_ULONG len) {
- + // Pad the read data to the end of m_response_buf
- + int old_buf_size = m_scratchpad.size();
- + m_scratchpad.resize(old_buf_size + len);
- + memcpy(m_scratchpad.data() + old_buf_size, data, len);
- +
- + int consumed = m_response->fillResponse(m_scratchpad.data(), m_scratchpad.size());
- + VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header, %1 bytes").arg(consumed));
- + {
- + QString tmp;
- + tmp.setAscii (m_scratchpad.data (), consumed);
- + VERBOSE(VB_NETWORK, QString ("ShoutCastIO2: Receiving header %1").arg(tmp));
- + }
- + moveFromByteArray (m_scratchpad, 0, consumed);
- +
- + if (m_scratchpad.size() >= 2 &&
- + m_scratchpad[0] == '\r' &&
- + m_scratchpad[1] == '\n')
- + {
- + moveFromByteArray (m_scratchpad, 0, 2);
- + return 1;
- + }
- +
- + return 0;
- +}
- +
- +bool ShoutCastIODevice::getResponse(ShoutCastResponse &response) {
- + if (! m_response_gotten)
- + return false;
- +
- + response = *m_response;
- + return true;
- +}
- +
- +int ShoutCastIODevice::parseStream(char *data, Q_ULONG maxlen) {
- + if (maxlen > m_bytes_till_next_meta)
- + maxlen = m_bytes_till_next_meta;
- +
- + /*
- + // throttle... by leaving bytes, we can delay actually having to
- + // make readBlock block the caller waiting for more bytes, thereby
- + // making the decoder be more responsive to ie. stop requests.
- + if (maxlen > m_buffer->ReadBufAvail ()) {
- + VERBOSE(VB_NETWORK, QString ("throttling, %1 becomes %2").arg(maxlen).arg(m_buffer->ReadBufAvail()/2));
- + maxlen = m_buffer->ReadBufAvail () / 3;
- + if (maxlen < 128) {
- + maxlen = 128;
- + waitForData (2000, maxlen);
- + }
- + }
- + */
- +
- + int result = m_buffer->Read (data, maxlen);
- + m_bytes_till_next_meta -= result;
- +
- + doKbPerSecond(result);
- + return result;
- +}
- +
- +bool ShoutCastIODevice::parseMeta() {
- + unsigned char ch;
- + m_buffer->Read (reinterpret_cast<char*>(&ch), 1);
- +
- + Q_ULONG meta_size = 16 * ch;
- + if (meta_size == 0)
- + return true;
- +
- + // FIXME: in case the stream is f*cked, we don't want to allocate too much
- + if (meta_size > MAX_ALLOWED_META_SIZE) {
- + VERBOSE(VB_PLAYBACK, QString ("Error in stream, got a meta size of %1").arg(meta_size));
- + switchToState (STOPPED);
- + return false;
- + }
- +
- + VERBOSE(VB_NETWORK, QString("ShoutCast: Reading %1 bytes of meta").arg(meta_size));
- +
- + if (waitForData (WAIT_FOR_MS, meta_size) == false) {
- + VERBOSE(VB_PLAYBACK, "Stream seems to have stopped");
- + switchToState(STOPPED);
- + return false;
- + }
- +
- + // Read in a loop until we have all of meta_size (but no more)
- + QByteArray metadata (meta_size);
- + Q_ULONG bytes_read = 0;
- + do {
- + Q_ULONG len = m_buffer->Read (metadata.data () + bytes_read, meta_size - bytes_read);
- + if (len <= 0) {
- + VERBOSE(VB_PLAYBACK, QString ("Error in metadata, expected %1 bytes of meta, got %1").
- + arg(meta_size).arg(len));
- + switchToState(STOPPED);
- + return false;
- + }
- + bytes_read += len;
- + } while (bytes_read < meta_size);
- + m_bytes_downloaded += bytes_read;
- +
- + // Avoid sending signals if the data hasn't changed
- + QString metadata_string (metadata);
- + if (m_last_metadata == metadata_string)
- + return true;
- +
- + m_last_metadata = metadata_string;
- + emit meta(metadata_string);
- +
- + return true;
- +}
- +
- +void ShoutCastIODevice::doKbPerSecond(int bytes_read)
- +{
- + // debug, collect kb/s info
- + m_bytes_downloaded += bytes_read;
- + struct timeval tv = {0, 0};
- + gettimeofday(&tv, NULL);
- + int msecs =((tv.tv_sec * 1000000 + tv.tv_usec) -
- + (m_sample_tv.tv_sec * 1000000 + m_sample_tv.tv_usec)) / 1000;
- +
- + if (msecs > 5000)
- + {
- + VERBOSE(VB_NETWORK, QString("ShoutCast: download speed, %1 kb in %2 s = %3 kb/s").
- + arg(m_bytes_downloaded/1024.0, 1, 'f', 1).
- + arg(msecs/1000.0, 1, 'f', 1).
- + arg((m_bytes_downloaded/1024)/(msecs/1000.0), 3, 'f', 1));
- + m_sample_tv = tv;
- + m_bytes_downloaded = 0;
- + }
- +}
- +
- +/****************************************************************************/
- +
- +DecoderIOFactoryShoutCast::DecoderIOFactoryShoutCast(DecoderHandler *parent)
- + : DecoderIOFactory(parent), m_timer(NULL), m_input(NULL)
- +{
- + m_timer = new QTimer(this);
- +}
- +
- +DecoderIOFactoryShoutCast::~DecoderIOFactoryShoutCast()
- +{
- + closeIODevice();
- + if (!m_logo_grabber.isDone ()) {
- + m_logo_grabber.stop ();
- + }
- +}
- +
- +QIODevice* DecoderIOFactoryShoutCast::takeInput ()
- +{
- + QIODevice *result = m_input;
- + m_input = 0;
- + return result;
- +}
- +
- +void DecoderIOFactoryShoutCast::makeIODevice()
- +{
- + closeIODevice();
- +
- + m_input = new ShoutCastIODevice();
- +
- + connect(m_input, SIGNAL(meta(const QString&)),
- + this, SLOT(shoutcastMeta(const QString&)));
- + connect(m_input, SIGNAL(changedState(ShoutCastIODevice::State)),
- + this, SLOT(shoutcastChangedState(ShoutCastIODevice::State)));
- +}
- +
- +void DecoderIOFactoryShoutCast::closeIODevice()
- +{
- + if (m_input) {
- + m_input->disconnect();
- + if (m_input->isOpen()) {
- + m_input->close();
- + }
- + m_input->deleteLater();
- + m_input = NULL;
- + VERBOSE(VB_PLAYBACK, "DecoderIOFactoryShoutCast m_input is now 0");
- + }
- +}
- +
- +
- +void DecoderIOFactoryShoutCast::start()
- +{
- + VERBOSE(VB_PLAYBACK, QString("DecoderIOFactoryShoutCast %1").arg(m_url.toString()));
- + doOperationStart("Connecting");
- +
- + makeIODevice();
- + m_input->connectToUrl(m_url);
- +
- + QString urlstr;
- + m_meta->getField ("x-cast-logourl", &urlstr);
- + QUrl url (urlstr);
- + m_logo_grabber.request (url, 10000, false);
- + m_logo_grabber_timer = new QTimer (this);
- + connect (m_logo_grabber_timer, SIGNAL(timeout()), this, SLOT(checkLogoGrabber()));
- + m_logo_grabber_timer->start (1000);
- +}
- +
- +void DecoderIOFactoryShoutCast::checkLogoGrabber () {
- + VERBOSE(VB_PLAYBACK, QString("Checking logograbber %1 %2 %3").
- + arg(m_logo_grabber.isDone ()?"done":"busy").
- + arg(m_logo_grabber.isTimedout ()?"timedout":"not-timedout").
- + arg(m_logo_grabber.getStatusCode()));
- + if (!m_logo_grabber.isDone ())
- + return;
- +
- + m_logo_grabber_timer->stop ();
- + m_logo_grabber_timer->disconnect ();
- +
- + if (m_logo_grabber.isTimedout() || m_logo_grabber.getStatusCode() != 200)
- + return;
- +
- + m_meta->setAlbumArt(m_logo_grabber.getRawData());
- +}
- +
- +void DecoderIOFactoryShoutCast::stop()
- +{
- + if (m_timer)
- + m_timer->disconnect ();
- +
- + doOperationStop();
- +
- + Metadata mdata(*m_meta);
- + mdata.setTitle("Stopped");
- + mdata.setArtist("");
- + mdata.setLength(-1);
- + DecoderHandlerEvent ev(mdata);
- + dispatch(ev);
- +}
- +
- +void DecoderIOFactoryShoutCast::periodicallyCheckResponse(void)
- +{
- + int res = checkResponseOK();
- + if (res == 0)
- + {
- + ShoutCastResponse response;
- + m_input->getResponse (response);
- + m_prebuffer = PREBUFFER_SECS * response.getBitrate () * 125; // 125 = 1000/8 (kilo, bits...)
- + VERBOSE(VB_NETWORK, QString ("kbps is %1, prebuffering %2 secs = %3 kb").
- + arg(response.getBitrate()).arg(PREBUFFER_SECS).arg(m_prebuffer/1024));
- + m_timer->stop();
- + m_timer->disconnect();
- + connect(m_timer, SIGNAL(timeout()), this, SLOT(periodicallyCheckBuffered()));
- + m_timer->start(500);
- + }
- + else if (res < 0)
- + {
- + m_timer->stop();
- + doFailed("Cannot parse this stream");
- + }
- +}
- +
- +void DecoderIOFactoryShoutCast::periodicallyCheckBuffered(void)
- +{
- + VERBOSE(VB_NETWORK, QString("DecoderIOFactoryShoutCast: prebuffered %1/%2KB").
- + arg(m_input->bytesAvailable()/1024).arg(m_prebuffer/1024));
- +
- + if (m_input->bytesAvailable() < m_prebuffer)
- + return;
- +
- + ShoutCastResponse response;
- + m_input->getResponse (response);
- + VERBOSE(VB_PLAYBACK, QString ("contents '%1'").arg (response.getContent()));
- + if (response.getContent () == "audio/mpeg") {
- + doConnectDecoder("create-mp3-decoder.mp3");
- + } else if (response.getContent () == "audio/aacp") {
- + doConnectDecoder("create-aac-decoder.m4a");
- + } else {
- + doFailed (QObject::tr ("Unsupported content type for ShoutCast stream: %1").
- + arg (response.getContent ()));
- + }
- +
- + m_timer->disconnect();
- + m_timer->stop();
- +}
- +
- +void DecoderIOFactoryShoutCast::shoutcastMeta(const QString &metadata)
- +{
- + ShoutCastMetaParser parser;
- + parser.setMetaFormat(m_meta->CompilationArtist());
- +
- + ShoutCastMetaMap meta_map = parser.parseMeta(metadata);
- +
- + Metadata mdata(*m_meta);
- + mdata.setTitle(meta_map["title"]);
- + mdata.setArtist(meta_map["artist"]);
- + mdata.setAlbum(m_meta->Album()); // meta_map["album"]
- + mdata.setLength(-1);
- +
- + DecoderHandlerEvent ev(mdata);
- + dispatch(ev);
- +}
- +
- +void DecoderIOFactoryShoutCast::shoutcastChangedState(ShoutCastIODevice::State state)
- +{
- + //VERBOSE(VB_PLAYBACK, QString ("ShoutCast changed state to %1").arg(ShoutCastIODevice::stateString (state)));
- + if (state == ShoutCastIODevice::RESOLVING)
- + doOperationStart("Finding radio");
- + if (state == ShoutCastIODevice::CANT_RESOLVE)
- + doFailed (QObject::tr ("Cannot find radio.\nCheck the URL is correct."));
- +
- + if (state == ShoutCastIODevice::CONNECTING)
- + doOperationStart("Connecting to radio");
- + if (state == ShoutCastIODevice::CANT_CONNECT)
- + doFailed (QObject::tr ("Cannot connect to radio.\nCheck the URL is correct."));
- + if (state == ShoutCastIODevice::CONNECTED) {
- + doOperationStart("Connected to radio");
- + m_timer->stop();
- + m_timer->disconnect();
- + connect(m_timer, SIGNAL(timeout()),
- + this, SLOT(periodicallyCheckResponse()));
- + m_timer->start(300);
- + }
- + if (state == ShoutCastIODevice::PLAYING) {
- + doOperationStart("Buffering");
- + }
- +
- + if (state == ShoutCastIODevice::STOPPED)
- + stop();
- +}
- +
- +int DecoderIOFactoryShoutCast::checkResponseOK()
- +{
- + ShoutCastResponse response;
- +
- + if (!m_input->getResponse(response))
- + return 0;
- +
- + if (! response.isICY() &&
- + response.getStatus() == 302 &&
- + ! response.getLocation().isEmpty())
- + {
- + // restart with new location...
- + setUrl(response.getLocation());
- + start();
- + return 1;
- + }
- +
- + if (! response.isICY() || response.getStatus() != 200)
- + return -1;
- +
- + return 0;
- +}
- +
- Index: mythplugins/mythmusic/mythmusic/metaio.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/metaio.cpp (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/metaio.cpp (working copy)
- @@ -2,6 +2,8 @@
- #include "metaio.h"
- #include "metadata.h"
- +#include <qdir.h>
- +#include <qurl.h>
- #include <mythtv/mythcontext.h>
- //==========================================================================
- @@ -105,3 +107,37 @@
- return retdata;
- }
- +
- +//==========================================================================
- +/*!
- + * \brief Retrieve the albumart.
- + *
- + * The default implementation picks a random image file from the
- + * directory in which the file is.
- + *
- + * \param filename The filename to try and determin metadata for.
- + * \returns A QByteArray that can be passed to QImage or such.
- + */
- +QByteArray MetaIO::getAlbumArt(const QString &filename)
- +{
- + QString curdir = QUrl(filename).dirPath();
- + QString namefilter = gContext->GetSetting("AlbumArtFilter",
- + "*.png;*.jpg;*.jpeg;*.gif;*.bmp");
- + // Get directory contents based on filter
- + QDir folder(curdir, namefilter, QDir::Name | QDir::IgnoreCase,
- + QDir::Files | QDir::Hidden);
- +
- + if (!folder.count())
- + return QByteArray();
- +
- + QString result = folder[rand() % folder.count()];
- + result.prepend("/");
- + result.prepend(curdir);
- +
- + QFile file(result);
- + file.open(IO_ReadOnly);
- + if (!file.isOpen())
- + return QByteArray();
- +
- + return file.readAll();
- +}
- Index: mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/goom/mythgoom.cpp (working copy)
- @@ -78,19 +78,28 @@
- return true;
- int numSamps = 512;
- + int step = 1;
- +
- if (node->length < 512)
- - numSamps = node->length;
- + step = 512 / node->length;
- signed short int data[2][512];
- int i = 0;
- - for (i = 0; i < numSamps; i++)
- + for (i = 0; i < numSamps; i += step)
- {
- data[0][i] = node->left[i];
- if (node->right)
- data[1][i] = node->right[i];
- else
- data[1][i] = data[0][i];
- +
- + if (step > 1)
- + for (int j = 1; j < step; j++)
- + {
- + data[0][i+j] = data[0][i];
- + data[1][i+j] = data[1][i];
- + }
- }
- for (; i < 512; i++)
- Index: mythplugins/mythmusic/mythmusic/visualize.h
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/visualize.h (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/visualize.h (working copy)
- @@ -123,15 +123,20 @@
- virtual ~Squares();
- void resize (const QSize &newsize);
- + bool process(VisualNode *node = 0);
- bool draw(QPainter *p, const QColor &back = Qt::black);
- void handleKeyPress(const QString &action) {(void) action;}
- private:
- void drawRect(QPainter *p, QRect *rect, int i, int c, int w, int h);
- + void rotateHues ();
- +
- QSize size;
- MainVisual *pParent;
- int fake_height;
- int number_of_squares;
- + int targetHue;
- + int startHue;
- };
- #ifdef OPENGL_SUPPORT
- Index: mythplugins/mythmusic/mythmusic/maddecoder.cpp
- ===================================================================
- --- mythplugins/mythmusic/mythmusic/maddecoder.cpp (revision 13855)
- +++ mythplugins/mythmusic/mythmusic/maddecoder.cpp (working copy)
- @@ -278,6 +278,9 @@
- void MadDecoder::seek(double pos)
- {
- + if(!input()->isDirectAccess())
- + return;
- +
- seekTime = pos;
- }
advertising
Update the Post
Either update this post and resubmit it with changes, or make a new post.
You may also comment on this post.
Please note that information posted here will not expire by default. If you do not want it to expire, please set the expiry time above. If it is set to expire, web search engines will not be allowed to index it prior to it expiring. Items that are not marked to expire will be indexable by search engines. Be careful with your passwords. All illegal activities will be reported and any information will be handed over to the authorities, so be good.