playlist demuxers: return -1 in case of error, 0 in case of eof, 1 else; like any...
[vlc.git] / modules / demux / playlist / shoutcast.c
1 /*****************************************************************************
2  * shoutcast.c: Winamp >=5.2 shoutcast demuxer
3  *****************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
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
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 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 General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #include <vlc/vlc.h>
30 #include <vlc_demux.h>
31
32 #include "playlist.h"
33 #include "vlc_xml.h"
34
35 struct demux_sys_t
36 {
37     playlist_t *p_playlist;
38     input_item_t *p_current_input;
39
40     xml_t *p_xml;
41     xml_reader_t *p_xml_reader;
42
43     vlc_bool_t b_adult;
44 };
45
46 /* duplicate from modules/services_discovery/shout.c */
47 #define SHOUTCAST_BASE_URL "http/shout-winamp://www.shoutcast.com/sbin/newxml.phtml"
48 #define SHOUTCAST_TUNEIN_BASE_URL "http://www.shoutcast.com"
49 #define SHOUTCAST_TV_TUNEIN_URL "http://www.shoutcast.com/sbin/tunein-tvstation.pls?id="
50
51 /*****************************************************************************
52  * Local prototypes
53  *****************************************************************************/
54 static int Demux( demux_t *p_demux);
55 static int Control( demux_t *p_demux, int i_query, va_list args );
56
57 static int DemuxGenre( demux_t *p_demux );
58 static int DemuxStation( demux_t *p_demux );
59
60 /*****************************************************************************
61  * Import_Shoutcast: main import function
62  *****************************************************************************/
63 int E_(Import_Shoutcast)( vlc_object_t *p_this )
64 {
65     demux_t *p_demux = (demux_t *)p_this;
66
67     if( !demux2_IsForced( p_demux, "shout-winamp" ) )
68         return VLC_EGENERIC;
69
70     STANDARD_DEMUX_INIT_MSG( "using shoutcast playlist reader" );
71     p_demux->p_sys->p_playlist = NULL;
72     p_demux->p_sys->p_xml = NULL;
73     p_demux->p_sys->p_xml_reader = NULL;
74
75     /* Do we want to list adult content ? */
76     var_Create( p_demux, "shoutcast-show-adult",
77                 VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
78     p_demux->p_sys->b_adult = var_GetBool( p_demux, "shoutcast-show-adult" );
79
80     return VLC_SUCCESS;
81 }
82
83 /*****************************************************************************
84  * Deactivate: frees unused data
85  *****************************************************************************/
86 void E_(Close_Shoutcast)( vlc_object_t *p_this )
87 {
88     demux_t *p_demux = (demux_t *)p_this;
89     demux_sys_t *p_sys = p_demux->p_sys;
90
91     if( p_sys->p_playlist )
92         vlc_object_release( p_sys->p_playlist );
93     if( p_sys->p_xml_reader )
94         xml_ReaderDelete( p_sys->p_xml, p_sys->p_xml_reader );
95     if( p_sys->p_xml )
96         xml_Delete( p_sys->p_xml );
97     free( p_sys );
98 }
99
100 static int Demux( demux_t *p_demux )
101 {
102     demux_sys_t *p_sys = p_demux->p_sys;
103     xml_t *p_xml;
104     xml_reader_t *p_xml_reader;
105     char *psz_eltname = NULL;
106     INIT_PLAYLIST_STUFF;
107     p_sys->p_playlist = p_playlist;
108     p_sys->p_current_input = p_current_input;
109
110     p_xml = p_sys->p_xml = xml_Create( p_demux );
111     if( !p_xml ) return -1;
112
113     p_xml_reader = xml_ReaderCreate( p_xml, p_demux->s );
114     if( !p_xml_reader ) return -1;
115     p_sys->p_xml_reader = p_xml_reader;
116
117     /* check root node */
118     if( xml_ReaderRead( p_xml_reader ) != 1 )
119     {
120         msg_Err( p_demux, "invalid file (no root node)" );
121         return -1;
122     }
123
124     if( xml_ReaderNodeType( p_xml_reader ) != XML_READER_STARTELEM ||
125         ( psz_eltname = xml_ReaderName( p_xml_reader ) ) == NULL ||
126         ( strcmp( psz_eltname, "genrelist" )
127           && strcmp( psz_eltname, "stationlist" ) ) )
128     {
129         msg_Err( p_demux, "invalid root node %i, %s",
130                  xml_ReaderNodeType( p_xml_reader ), psz_eltname );
131         if( psz_eltname ) free( psz_eltname );
132         return -1;
133     }
134
135     if( !strcmp( psz_eltname, "genrelist" ) )
136     {
137         /* we're reading a genre list */
138         free( psz_eltname );
139         if( DemuxGenre( p_demux ) ) return -1;
140     }
141     else
142     {
143         /* we're reading a station list */
144         free( psz_eltname );
145         if( DemuxStation( p_demux ) ) return -1;
146     }
147
148     HANDLE_PLAY_AND_RELEASE;
149     p_sys->p_playlist = NULL;
150     return 0; /* Needed for correct operation of go back */
151 }
152
153 #define GET_VALUE( a ) \
154                         if( !strcmp( psz_attrname, #a ) ) \
155                         { \
156                             psz_ ## a = strdup( psz_attrvalue ); \
157                         }
158 /* <genrelist>
159  *   <genre name="the name"></genre>
160  *   ...
161  * </genrelist>
162  **/
163 static int DemuxGenre( demux_t *p_demux )
164 {
165     demux_sys_t *p_sys = p_demux->p_sys;
166     char *psz_name = NULL; /* genre name */
167     char *psz_eltname = NULL; /* tag name */
168     input_item_t *p_input;
169
170     while( xml_ReaderRead( p_sys->p_xml_reader ) == 1 )
171     {
172         int i_type;
173
174         // Get the node type
175         i_type = xml_ReaderNodeType( p_sys->p_xml_reader );
176         switch( i_type )
177         {
178             // Error
179             case -1:
180                 return -1;
181                 break;
182
183             case XML_READER_STARTELEM:
184                 // Read the element name
185                 psz_eltname = xml_ReaderName( p_sys->p_xml_reader );
186                 if( !psz_eltname ) return -1;
187
188                 if( !strcmp( psz_eltname, "genre" ) )
189                 {
190                     // Read the attributes
191                     while( xml_ReaderNextAttr( p_sys->p_xml_reader ) == VLC_SUCCESS )
192                     {
193                         char *psz_attrname = xml_ReaderName( p_sys->p_xml_reader );
194                         char *psz_attrvalue =
195                             xml_ReaderValue( p_sys->p_xml_reader );
196                         if( !psz_attrname || !psz_attrvalue )
197                         {
198                             FREENULL(psz_attrname);
199                             FREENULL(psz_attrvalue);
200                             free(psz_eltname);
201                             /*FIXME: isn't return a bit too much. what about break*/
202                             return -1;
203                         }
204
205                         GET_VALUE( name )
206                         else
207                         {
208                             msg_Warn( p_demux,
209                                       "unexpected attribure %s in element %s",
210                                       psz_attrname,psz_eltname );
211                         }
212                         free( psz_attrname );
213                         free( psz_attrvalue );
214                     }
215                 }
216                 free( psz_eltname ); psz_eltname = NULL;
217                 break;
218
219             case XML_READER_TEXT:
220                 break;
221
222             // End element
223             case XML_READER_ENDELEM:
224                 // Read the element name
225                 psz_eltname = xml_ReaderName( p_sys->p_xml_reader );
226                 if( !psz_eltname ) return -1;
227                 if( !strcmp( psz_eltname, "genre" ) )
228                 {
229                     char *psz_mrl = malloc( strlen( SHOUTCAST_BASE_URL )
230                             + strlen( "?genre=" ) + strlen( psz_name ) + 1 );
231                     sprintf( psz_mrl, SHOUTCAST_BASE_URL "?genre=%s",
232                              psz_name );
233                     p_input = input_ItemNewExt( p_sys->p_playlist, psz_mrl,
234                                                 psz_name, 0, NULL, -1 );
235                     input_ItemCopyOptions( p_sys->p_current_input,
236                                                 p_input );
237                     free( psz_mrl );
238                     input_ItemAddSubItem( p_sys->p_current_input, p_input );
239                     FREENULL( psz_name );
240                 }
241                 FREENULL( psz_eltname );
242                 break;
243         }
244     }
245     return 0;
246 }
247
248 /* radio stations:
249  * <stationlist>
250  *   <tunein base="/sbin/tunein-station.pls"></tunein>
251  *   <station name="the name"
252  *            mt="mime type"
253  *            id="the id"
254  *            br="bit rate"
255  *            genre="A big genre string"
256  *            ct="current track name/author/..."
257  *            lc="listener count"></station>
258  * </stationlist>
259  *
260  * TV stations:
261  * <stationlist>
262  *   <tunein base="/sbin/tunein-station.pls"></tunein>
263  *   <station name="the name"
264  *            id="the id"
265  *            br="bit rate"
266  *            rt="rating"
267  *            load="server load ?"
268  *            ct="current track name/author/..."
269  *            genre="A big genre string"
270  *            lc="listener count"></station>
271  * </stationlist>
272  **/
273 static int DemuxStation( demux_t *p_demux )
274 {
275     demux_sys_t *p_sys = p_demux->p_sys;
276     input_item_t *p_input;
277
278     char *psz_base = NULL; /* */
279
280     char *psz_name = NULL; /* genre name */
281     char *psz_mt = NULL; /* mime type */
282     char *psz_id = NULL; /* id */
283     char *psz_br = NULL; /* bit rate */
284     char *psz_genre = NULL; /* genre */
285     char *psz_ct = NULL; /* current track */
286     char *psz_lc = NULL; /* listener count */
287
288     /* If these are set then it's *not* a radio but a TV */
289     char *psz_rt = NULL; /* rating for shoutcast TV */
290     char *psz_load = NULL; /* load for shoutcast TV */
291
292     char *psz_eltname = NULL; /* tag name */
293
294     while( xml_ReaderRead( p_sys->p_xml_reader ) == 1 )
295     {
296         int i_type;
297
298         // Get the node type
299         i_type = xml_ReaderNodeType( p_sys->p_xml_reader );
300         switch( i_type )
301         {
302             // Error
303             case -1:
304                 return -1;
305                 break;
306
307             case XML_READER_STARTELEM:
308                 // Read the element name
309                 psz_eltname = xml_ReaderName( p_sys->p_xml_reader );
310                 if( !psz_eltname ) return -1;
311
312                 // Read the attributes
313                 if( !strcmp( psz_eltname, "tunein" ) )
314                 {
315                     while( xml_ReaderNextAttr( p_sys->p_xml_reader ) == VLC_SUCCESS )
316                     {
317                         char *psz_attrname = xml_ReaderName( p_sys->p_xml_reader );
318                         char *psz_attrvalue =
319                             xml_ReaderValue( p_sys->p_xml_reader );
320                         if( !psz_attrname || !psz_attrvalue )
321                         {
322                             free(psz_eltname);
323                             FREENULL(psz_attrname);
324                             FREENULL(psz_attrvalue);
325                             return -1;
326                         }
327
328                         GET_VALUE( base )
329                         else
330                         {
331                             msg_Warn( p_demux,
332                                       "unexpected attribure %s in element %s",
333                                       psz_attrname, psz_eltname );
334                         }
335                         free( psz_attrname );
336                         free( psz_attrvalue );
337                     }
338                 }
339                 else if( !strcmp( psz_eltname, "station" ) )
340                 {
341                     while( xml_ReaderNextAttr( p_sys->p_xml_reader ) == VLC_SUCCESS )
342                     {
343                         char *psz_attrname = xml_ReaderName( p_sys->p_xml_reader );
344                         char *psz_attrvalue =
345                             xml_ReaderValue( p_sys->p_xml_reader );
346                         if( !psz_attrname || !psz_attrvalue )
347                         {
348                             free(psz_eltname);
349                             FREENULL(psz_attrname);
350                             FREENULL(psz_attrvalue);
351                             return -1;
352                         }
353
354                         GET_VALUE( name )
355                         else GET_VALUE( mt )
356                         else GET_VALUE( id )
357                         else GET_VALUE( br )
358                         else GET_VALUE( genre )
359                         else GET_VALUE( ct )
360                         else GET_VALUE( lc )
361                         else GET_VALUE( rt )
362                         else GET_VALUE( load )
363                         else
364                         {
365                             msg_Warn( p_demux,
366                                       "unexpected attribute %s in element %s",
367                                       psz_attrname, psz_eltname );
368                         }
369                         free( psz_attrname );
370                         free( psz_attrvalue );
371                     }
372                 }
373                 free(psz_eltname);
374                 break;
375
376             case XML_READER_TEXT:
377                 break;
378
379             // End element
380             case XML_READER_ENDELEM:
381                 // Read the element name
382                 psz_eltname = xml_ReaderName( p_sys->p_xml_reader );
383                 if( !psz_eltname ) return -1;
384                 if( !strcmp( psz_eltname, "station" ) &&
385                     ( psz_base || ( psz_rt && psz_load &&
386                     ( p_sys->b_adult || strcmp( psz_rt, "NC17" ) ) ) ) )
387                 {
388                     char *psz_mrl = NULL;
389                     if( psz_rt || psz_load )
390                     {
391                         /* tv */
392                         psz_mrl = malloc( strlen( SHOUTCAST_TV_TUNEIN_URL )
393                                           + strlen( psz_id ) + 1 );
394                         sprintf( psz_mrl, SHOUTCAST_TV_TUNEIN_URL "%s",
395                                  psz_id );
396                     }
397                     else
398                     {
399                         /* radio */
400                         psz_mrl = malloc( strlen( SHOUTCAST_TUNEIN_BASE_URL )
401                             + strlen( psz_base ) + strlen( "?id=" )
402                             + strlen( psz_id ) + 1 );
403                         sprintf( psz_mrl, SHOUTCAST_TUNEIN_BASE_URL "%s?id=%s",
404                              psz_base, psz_id );
405                     }
406                     p_input = input_ItemNewExt( p_sys->p_playlist, psz_mrl,
407                                                 psz_name , 0, NULL, -1 );
408                     free( psz_mrl );
409
410                     input_ItemCopyOptions( p_sys->p_current_input,
411                                                 p_input );
412
413 #define SADD_INFO( type, field ) if( field ) { input_ItemAddInfo( \
414                     p_input, _("Shoutcast"), _(type), "%s", field ) ; }
415                     SADD_INFO( "Mime type", psz_mt );
416                     SADD_INFO( "Bitrate", psz_br );
417                     SADD_INFO( "Listeners", psz_lc );
418                     SADD_INFO( "Load", psz_load );
419                     if( psz_genre )
420                         input_item_SetGenre( p_input, psz_genre );
421                     if( psz_ct )
422                         input_item_SetNowPlaying( p_input, psz_ct );
423                     if( psz_rt )
424                         input_item_SetRating( p_input, psz_rt );
425                     input_ItemAddSubItem( p_sys->p_current_input, p_input );
426
427                     FREENULL( psz_name );
428                     FREENULL( psz_mt );
429                     FREENULL( psz_id );
430                     FREENULL( psz_br );
431                     FREENULL( psz_genre );
432                     FREENULL( psz_ct );
433                     FREENULL( psz_lc );
434                     FREENULL( psz_rt );
435                 }
436                 free( psz_eltname );
437                 break;
438         }
439     }
440     return 0;
441 }
442
443 static int Control( demux_t *p_demux, int i_query, va_list args )
444 {
445     return VLC_EGENERIC;
446 }