7432cac73c880d6a405b9117abb9471942a4797e
[vlc/vlc-3.0.git] / modules / demux / playlist / shoutcast.c
1 /*****************************************************************************
2  * shoutcast.c: Winamp >=5.2 shoutcast demuxer
3  *****************************************************************************
4  * Copyright (C) 2006 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -@t- videolan -Dot- org>
8  *          based on b4s.c by Sigmund Augdal Helberg <dnumgis@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_access.h>
35
36 #include "playlist.h"
37 #include <vlc_xml.h>
38 #include <vlc_strings.h>
39
40 /* duplicate from modules/services_discovery/shout.c */
41 #define SHOUTCAST_BASE_URL "http://www.shoutcast.com/sbin/newxml.phtml"
42 #define SHOUTCAST_TUNEIN_BASE_URL "http://www.shoutcast.com"
43 #define SHOUTCAST_TV_TUNEIN_URL "http://www.shoutcast.com/sbin/tunein-tvstation.pls?id="
44
45 /*****************************************************************************
46  * Local prototypes
47  *****************************************************************************/
48 static int Demux( stream_t *, input_item_node_t * );
49
50 static int DemuxGenre( stream_t *p_demux, xml_reader_t *p_xml_reader,
51                        input_item_node_t *p_input_node );
52 static int DemuxStation( stream_t *p_demux, xml_reader_t *p_xml_reader,
53                          input_item_node_t *p_input_node, bool b_adult );
54
55 /*****************************************************************************
56  * Import_Shoutcast: main import function
57  *****************************************************************************/
58 int Import_Shoutcast( vlc_object_t *p_this )
59 {
60     stream_t *p_demux = (stream_t *)p_this;
61
62     CHECK_FILE(p_demux);
63
64     p_demux->pf_readdir = Demux;
65     p_demux->pf_control = access_vaDirectoryControlHelper;
66     msg_Dbg( p_demux, "using shoutcast playlist reader" );
67
68     return VLC_SUCCESS;
69 }
70
71 static int Demux( stream_t *p_demux, input_item_node_t *p_input_node )
72 {
73     xml_reader_t *p_xml_reader = NULL;
74     const char *node;
75     int i_ret = VLC_EGENERIC;
76
77     p_xml_reader = xml_ReaderCreate( p_demux, p_demux->p_source );
78     if( !p_xml_reader )
79         goto error;
80
81     /* check root node */
82     if( xml_ReaderNextNode( p_xml_reader, &node ) != XML_READER_STARTELEM )
83     {
84         msg_Err( p_demux, "invalid file (no root node)" );
85         goto error;
86     }
87
88     if( strcmp( node, "genrelist" ) && strcmp( node, "stationlist" ) )
89     {
90         msg_Err( p_demux, "invalid root node <%s>", node );
91         goto error;
92     }
93
94     if( !strcmp( node, "genrelist" ) )
95     {
96         /* we're reading a genre list */
97         if( DemuxGenre( p_demux, p_xml_reader, p_input_node ) )
98             goto error;
99     }
100     else
101     {
102         /* we're reading a station list */
103         if( DemuxStation( p_demux, p_xml_reader, p_input_node,
104                 var_InheritBool( p_demux, "shoutcast-show-adult" ) ) )
105             goto error;
106     }
107
108     i_ret = VLC_SUCCESS;
109
110 error:
111     if( p_xml_reader )
112         xml_ReaderDelete( p_xml_reader );
113     if( p_input_node ) input_item_node_Delete( p_input_node );
114     return i_ret;
115 }
116
117 /* <genrelist>
118  *   <genre name="the name"></genre>
119  *   ...
120  * </genrelist>
121  **/
122 static int DemuxGenre( stream_t *p_demux, xml_reader_t *p_xml_reader,
123                        input_item_node_t *p_input_node )
124 {
125     const char *node;
126     char *psz_name = NULL; /* genre name */
127     int type;
128
129     while( (type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
130     {
131         switch( type )
132         {
133             case XML_READER_STARTELEM:
134             {
135                 if( !strcmp( node, "genre" ) )
136                 {
137                     // Read the attributes
138                     const char *name, *value;
139                     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) )
140                     {
141                         if( !strcmp( name, "name" ) )
142                         {
143                             free(psz_name);
144                             psz_name = strdup( value );
145                         }
146                         else
147                             msg_Warn( p_demux,
148                                       "unexpected attribute %s in <%s>",
149                                       name, node );
150                     }
151                 }
152                 break;
153             }
154
155             case XML_READER_ENDELEM:
156                 if( !strcmp( node, "genre" ) && psz_name != NULL )
157                 {
158                     char* psz_mrl;
159
160                     if( asprintf( &psz_mrl, SHOUTCAST_BASE_URL "?genre=%s",
161                                   psz_name ) != -1 )
162                     {
163                         input_item_t *p_input;
164                         vlc_xml_decode( psz_mrl );
165                         p_input = input_item_New( psz_mrl, psz_name );
166                         input_item_CopyOptions( p_input, p_input_node->p_item );
167                         input_item_AddOption( p_input, "stream-filter=shout-winamp", VLC_INPUT_OPTION_TRUSTED );
168                         free( psz_mrl );
169                         input_item_node_AppendItem( p_input_node, p_input );
170                         input_item_Release( p_input );
171                     }
172                     FREENULL( psz_name );
173                 }
174                 break;
175         }
176     }
177
178     free( psz_name );
179     return 0;
180 }
181
182 /* radio stations:
183  * <stationlist>
184  *   <tunein base="/sbin/tunein-station.pls"></tunein>
185  *   <station name="the name"
186  *            mt="mime type"
187  *            id="the id"
188  *            br="bit rate"
189  *            genre="A big genre string"
190  *            ct="current track name/author/..."
191  *            lc="listener count"></station>
192  * </stationlist>
193  *
194  * TV stations:
195  * <stationlist>
196  *   <tunein base="/sbin/tunein-station.pls"></tunein>
197  *   <station name="the name"
198  *            id="the id"
199  *            br="bit rate"
200  *            rt="rating"
201  *            load="server load ?"
202  *            ct="current track name/author/..."
203  *            genre="A big genre string"
204  *            lc="listener count"></station>
205  * </stationlist>
206  **/
207 static int DemuxStation( stream_t *p_demux, xml_reader_t *p_xml_reader,
208                          input_item_node_t *p_input_node, bool b_adult )
209 {
210     char *psz_base = NULL; /* */
211
212     char *psz_name = NULL; /* genre name */
213     char *psz_mt = NULL; /* mime type */
214     char *psz_id = NULL; /* id */
215     char *psz_br = NULL; /* bit rate */
216     char *psz_genre = NULL; /* genre */
217     char *psz_ct = NULL; /* current track */
218     char *psz_lc = NULL; /* listener count */
219
220     /* If these are set then it's *not* a radio but a TV */
221     char *psz_rt = NULL; /* rating for shoutcast TV */
222     char *psz_load = NULL; /* load for shoutcast TV */
223
224     const char *node; /* tag name */
225     int i_type;
226
227     while( (i_type = xml_ReaderNextNode( p_xml_reader, &node )) > 0 )
228     {
229         switch( i_type )
230         {
231             case XML_READER_STARTELEM:
232                 // Read the attributes
233                 if( !strcmp( node, "tunein" ) )
234                 {
235                     const char *name, *value;
236                     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) )
237                     {
238                         if( !strcmp( name, "base" ) )
239                         {
240                             free( psz_base );
241                             psz_base = strdup( value );
242                         }
243                         else
244                             msg_Warn( p_demux,
245                                       "unexpected attribute %s in <%s>",
246                                       name, node );
247                     }
248                 }
249                 else if( !strcmp( node, "station" ) )
250                 {
251                     const char *name, *value;
252                     while( (name = xml_ReaderNextAttr( p_xml_reader, &value )) )
253                     {
254                         char **p = NULL;
255                         if( !strcmp( name, "name" ) )
256                             p = &psz_name;
257                         else if ( !strcmp( name, "mt" ) )
258                             p = &psz_mt;
259                         else if ( !strcmp( name, "id" ) )
260                             p = &psz_id;
261                         else if ( !strcmp( name, "br" ) )
262                             p = &psz_br;
263                         else if ( !strcmp( name, "genre" ) )
264                             p = &psz_genre;
265                         else if ( !strcmp( name, "ct" ) )
266                             p = &psz_ct;
267                         else if ( !strcmp( name, "lc" ) )
268                             p = &psz_lc;
269                         else if ( !strcmp( name, "rt" ) )
270                             p = &psz_rt;
271                         else if ( !strcmp( name, "load" ) )
272                             p = &psz_load;
273                         if( p != NULL )
274                         {
275                             free( *p );
276                             *p = strdup( value );
277                         }
278                         else
279                             msg_Warn( p_demux,
280                                       "unexpected attribute %s in <%s>",
281                                       name, node );
282                     }
283                 }
284                 break;
285
286             // End element
287             case XML_READER_ENDELEM:
288                 if( !strcmp( node, "station" ) &&
289                     ( psz_base || ( psz_rt && psz_load &&
290                     ( b_adult || strcmp( psz_rt, "NC17" ) ) ) ) )
291                 {
292                     char *psz_mrl = NULL;
293                     if( psz_rt || psz_load )
294                     {
295                         /* tv */
296                         if( asprintf( &psz_mrl, SHOUTCAST_TV_TUNEIN_URL "%s",
297                                  psz_id ) == -1)
298                             psz_mrl = NULL;
299                     }
300                     else
301                     {
302                         /* radio */
303                         if( asprintf( &psz_mrl, SHOUTCAST_TUNEIN_BASE_URL "%s?id=%s",
304                              psz_base, psz_id ) == -1 )
305                             psz_mrl = NULL;
306                     }
307
308                     /* Create the item */
309                     input_item_t *p_input = NULL;
310
311                     if( likely(psz_mrl != NULL) )
312                     {
313                         vlc_xml_decode( psz_mrl );
314                         p_input = input_item_New( psz_mrl, psz_name );
315                         free( psz_mrl );
316                     }
317
318                     if( likely(p_input != NULL) )
319                     {
320                         input_item_CopyOptions( p_input, p_input_node->p_item );
321
322 #define SADD_INFO( type, field ) \
323                     if( field ) \
324                         input_item_AddInfo( p_input, _("Shoutcast"), \
325                                             vlc_gettext(type), "%s", field )
326                         SADD_INFO( N_("Mime"), psz_mt );
327                         SADD_INFO( N_("Bitrate"), psz_br );
328                         SADD_INFO( N_("Listeners"), psz_lc );
329                         SADD_INFO( N_("Load"), psz_load );
330                         if( psz_genre )
331                             input_item_SetGenre( p_input, psz_genre );
332                         if( psz_ct )
333                             input_item_SetNowPlaying( p_input, psz_ct );
334                         if( psz_rt )
335                             input_item_SetRating( p_input, psz_rt );
336                         input_item_node_AppendItem( p_input_node, p_input );
337                         input_item_Release( p_input );
338                     }
339                     FREENULL( psz_base );
340                     FREENULL( psz_name );
341                     FREENULL( psz_mt );
342                     FREENULL( psz_id );
343                     FREENULL( psz_br );
344                     FREENULL( psz_genre );
345                     FREENULL( psz_ct );
346                     FREENULL( psz_lc );
347                     FREENULL( psz_rt );
348                     FREENULL( psz_load );
349                 }
350                 break;
351         }
352     }
353     /* Free all strings anyway, in case of missing end element */
354     FREENULL( psz_base );
355     FREENULL( psz_name );
356     FREENULL( psz_mt );
357     FREENULL( psz_id );
358     FREENULL( psz_br );
359     FREENULL( psz_genre );
360     FREENULL( psz_ct );
361     FREENULL( psz_lc );
362     FREENULL( psz_rt );
363     FREENULL( psz_load );
364     return 0;
365 }