]> git.sesse.net Git - vlc/blob - modules/demux/m3u.c
the m3u demux now reads the pls files shoutcast uses.
[vlc] / modules / demux / m3u.c
1 /*****************************************************************************
2  * m3u.c: a meta demux to parse pls, m3u and asx playlists
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: m3u.c,v 1.15 2003/03/06 15:30:42 sigmunau Exp $
6  *
7  * Authors: Sigmund Augdal <sigmunau@idi.ntnu.no>
8  *          Gildas Bazin <gbazin@netcourrier.com>
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <string.h>                                              /* strdup() */
30 #include <errno.h>
31
32 #include <vlc/vlc.h>
33 #include <vlc/input.h>
34 #include <vlc_playlist.h>
35
36 #include <sys/types.h>
37
38 /*****************************************************************************
39  * Constants and structures
40  *****************************************************************************/
41 #define MAX_LINE 1024
42
43 #define TYPE_UNKNOWN 0
44 #define TYPE_M3U 1
45 #define TYPE_ASX 2
46 #define TYPE_HTML 3
47 #define TYPE_PLS 4
48
49 struct demux_sys_t
50 {
51     int i_type;                                   /* playlist type (m3u/asx) */
52 };
53
54 /*****************************************************************************
55  * Local prototypes
56  *****************************************************************************/
57 static int  Activate  ( vlc_object_t * );
58 static void Deactivate( vlc_object_t * );
59 static int  Demux ( input_thread_t * );
60
61 /*****************************************************************************
62  * Module descriptor
63  *****************************************************************************/
64 vlc_module_begin();
65     set_description( _("playlist metademux") );
66     set_capability( "demux", 10 );
67     set_callbacks( Activate, Deactivate );
68     add_shortcut( "m3u" );
69     add_shortcut( "asx" );
70     add_shortcut( "html" );
71     add_shortcut( "pls" );
72 vlc_module_end();
73
74 /*****************************************************************************
75  * Activate: initializes m3u demux structures
76  *****************************************************************************/
77 static int Activate( vlc_object_t * p_this )
78 {
79     input_thread_t *p_input = (input_thread_t *)p_this;
80     char           *psz_ext;
81     demux_sys_t    *p_m3u;
82     int            i_type = 0;
83
84     /* Initialize access plug-in structures. */
85     if( p_input->i_mtu == 0 )
86     {
87         /* Improve speed. */
88         p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
89     }
90
91     p_input->pf_demux = Demux;
92     p_input->pf_rewind = NULL;
93
94     /* Check for m3u/asx file extension */
95     psz_ext = strrchr ( p_input->psz_name, '.' );
96     if( psz_ext )
97     {
98         if( !strcasecmp( psz_ext, ".m3u") ||
99             ( p_input->psz_demux && !strncmp(p_input->psz_demux, "m3u", 3) ) )
100         {
101             i_type = TYPE_M3U;
102         }
103         else if( !strcasecmp( psz_ext, ".asx") ||
104                  ( p_input->psz_demux && !strncmp(p_input->psz_demux, "asx", 3) ) )
105         {
106             i_type = TYPE_ASX;
107         }
108         else if( !strcasecmp( psz_ext, ".html") ||
109                  ( p_input->psz_demux && !strncmp(p_input->psz_demux, "html", 4) ) )
110         {
111             i_type = TYPE_HTML;
112         }
113         else if( !strcasecmp( psz_ext, ".pls") ||
114                  ( p_input->psz_demux && !strncmp(p_input->psz_demux, "pls", 3) ) )
115         {
116             i_type = TYPE_PLS;
117         }
118     }
119
120     /* we had no luck looking at the file extention, so we have a look
121      * at the content. This is useful for .asp, .php and similar files
122      * that are actually html. Also useful for som asx files that have
123      * another extention */
124     if( !i_type )
125     {
126         byte_t *p_peek;
127         int i_size = input_Peek( p_input, &p_peek, MAX_LINE );
128         i_size -= sizeof("[playlist]") - 1;
129         if ( i_size > 0 ) {
130             while ( i_size
131                     && strncasecmp( p_peek, "[playlist]", sizeof("[playlist]") - 1 )
132                     && strncasecmp( p_peek, "<html>", sizeof("<html>") - 1 )
133                     && strncasecmp( p_peek, "<asx", sizeof("<asx") - 1 ) )
134             {
135                 p_peek++;
136                 i_size--;
137             }
138             if ( !i_size )
139             {
140                 return -1;
141             }
142             else if ( !strncasecmp( p_peek, "[playlist]", sizeof("[playlist]") -1 ) )
143             {
144                 i_type = TYPE_PLS;
145             }
146             else if ( !strncasecmp( p_peek, "<html>", sizeof("<html>") -1 ) )
147             {
148                 i_type = TYPE_HTML;
149             }
150             else if ( !strncasecmp( p_peek, "<asx", sizeof("<asx") -1 ) )
151             {
152                 i_type = TYPE_ASX;
153             }
154         }
155     }
156     /* Allocate p_m3u */
157     if( !( p_m3u = malloc( sizeof( demux_sys_t ) ) ) )
158     {
159         msg_Err( p_input, "out of memory" );
160         return -1;
161     }
162     p_input->p_demux_data = p_m3u;
163
164     p_m3u->i_type = i_type;
165
166     return 0;
167 }
168
169 /*****************************************************************************
170  * Deactivate: frees unused data
171  *****************************************************************************/
172 static void Deactivate( vlc_object_t *p_this )
173 {
174     input_thread_t *p_input = (input_thread_t *)p_this;
175     demux_sys_t *p_m3u = (demux_sys_t *)p_input->p_demux_data  ; 
176
177     free( p_m3u );
178 }
179
180 /*****************************************************************************
181  * ProcessLine: read a "line" from the file and add any entries found
182  * to the playlist. Return number of items added ( 0 or 1 )
183  *****************************************************************************/
184 static int ProcessLine ( input_thread_t *p_input , demux_sys_t *p_m3u
185         , playlist_t *p_playlist , char psz_line[MAX_LINE], int i_position )
186 {
187     char          *psz_bol, *psz_name;
188     psz_bol = psz_line;
189
190     /* Remove unnecessary tabs or spaces at the beginning of line */
191     while( *psz_bol == ' ' || *psz_bol == '\t' ||
192            *psz_bol == '\n' || *psz_bol == '\r' )
193         psz_bol++;
194
195     if( p_m3u->i_type == TYPE_M3U )
196     {
197         /* Check for comment line */
198         if( *psz_bol == '#' )
199             /*line is comment or extended info, ignored for now */
200             return 0;
201     }
202     else if ( p_m3u->i_type == TYPE_PLS )
203     {
204         /* We are dealing with .pls files from shoutcast
205          * We are looking for lines like "File1=http://..." */
206         if( !strncasecmp( psz_bol, "File", sizeof("File") - 1 ) )
207         {
208             psz_bol += sizeof("File") - 1;
209             psz_bol = strchr( psz_bol, '=' );
210             if ( !psz_bol ) return 0;
211             psz_bol++;
212         }
213         else
214         {
215             return 0;
216         }
217     }
218     else if ( p_m3u->i_type == TYPE_ASX )
219     {
220         /* We are dealing with ASX files.
221          * We are looking for "<ref href=" xml markups that
222          * begins with "mms://", "http://" or "file://" */
223         char *psz_eol;
224
225         while( *psz_bol &&
226                strncasecmp( psz_bol, "ref", sizeof("ref") - 1 ) )
227             psz_bol++;
228
229         if( !*psz_bol ) return 0;
230
231         while( *psz_bol &&
232                strncasecmp( psz_bol, "href", sizeof("href") - 1 ) )
233             psz_bol++;
234
235         if( !*psz_bol ) return 0;
236
237         while( *psz_bol &&
238                strncasecmp( psz_bol, "mms://",
239                             sizeof("mms://") - 1 ) &&
240                strncasecmp( psz_bol, "http://",
241                             sizeof("http://") - 1 ) &&
242                strncasecmp( psz_bol, "file://",
243                             sizeof("file://") - 1 ) )
244             psz_bol++;
245
246         if( !*psz_bol ) return 0;
247
248         psz_eol = strchr( psz_bol, '"');
249         if( !psz_eol )
250           return 0;
251
252         *psz_eol = '\0';
253     }
254     else if ( p_m3u->i_type == TYPE_HTML )
255     {
256         /* We are dealing with a html file with embedded
257          * video.  We are looking for "<param name="filename"
258          * value=" html markups that begin with "http://" */
259         char *psz_eol;
260
261         while( *psz_bol &&
262                strncasecmp( psz_bol, "param", sizeof("param") - 1 ) )
263             psz_bol++;
264
265         if( !*psz_bol ) return 0;
266
267         while( *psz_bol &&
268                strncasecmp( psz_bol, "filename", sizeof("filename") - 1 ) )
269             psz_bol++;
270
271         if( !*psz_bol ) return 0;
272
273         while( *psz_bol &&
274                strncasecmp( psz_bol, "http://",
275                             sizeof("http://") - 1 ) )
276             psz_bol++;
277
278         if( !*psz_bol ) return 0;
279
280         psz_eol = strchr( psz_bol, '"');
281         if( !psz_eol )
282           return 0;
283
284         *psz_eol = '\0';
285
286     }
287     else
288     {
289         msg_Warn( p_input, "unknown file type" );
290         return 0;
291     }
292
293     /* empty line */
294     if ( !*psz_bol ) return 0;
295
296     /*
297      * From now on, we know we've got a meaningful line
298      */
299
300     /* check for a protocol name */
301     /* for URL, we should look for "://"
302      * for MRL (Media Resource Locator) ([[<access>][/<demux>]:][<source>]),
303      * we should look for ":"
304      * so we end up looking simply for ":"*/
305     /* PB: on some file systems, ':' are valid characters though*/
306     psz_name = psz_bol;
307     while( *psz_name && *psz_name!=':' )
308     {
309         psz_name++;
310     }
311 #ifdef WIN32
312     if ( *psz_name && ( psz_name == psz_bol + 1 ) )
313     {
314         /* if it is not an URL,
315          * as it is unlikely to be an MRL (PB: if it is ?)
316          * it should be an absolute file name with the drive letter */
317         if ( *(psz_name+1) == '/' )/* "*:/" */
318         {
319             if ( *(psz_name+2) != '/' )/* not "*://" */
320                 while ( *psz_name ) *psz_name++;/* so now (*psz_name==0) */
321         }
322         else while ( *psz_name ) *psz_name++;/* "*:*"*/
323     }
324 #endif
325
326     /* if the line doesn't specify a protocol name,
327      * check if the line has an absolute or relative path */
328 #ifndef WIN32
329     if( !*psz_name && *psz_bol != '/' )
330          /* If this line doesn't begin with a '/' */
331 #else
332     if( !*psz_name
333             && *psz_bol!='/'
334             && *psz_bol!='\\'
335             && *(psz_bol+1)!=':' )
336          /* if this line doesn't begin with
337           *  "/" or "\" or "*:" or "*:\" or "*:/" or "\\" */
338 #endif
339     {
340         /* assume the path is relative to the path of the m3u file. */
341         char *psz_path = strdup( p_input->psz_name );
342
343 #ifndef WIN32
344         psz_name = strrchr( psz_path, '/' );
345 #else
346         psz_name = strrchr( psz_path, '\\' );
347         if ( ! psz_name ) psz_name = strrchr( psz_path, '/' );
348 #endif
349         if( psz_name ) *psz_name = '\0';
350         else *psz_path = '\0';
351 #ifndef WIN32
352         psz_name = malloc( strlen(psz_path) + strlen(psz_bol) + 2 );
353         sprintf( psz_name, "%s/%s", psz_path, psz_bol );
354 #else
355         if ( *psz_path != '\0' )
356         {
357             psz_name = malloc( strlen(psz_path) + strlen(psz_bol) + 2 );
358             sprintf( psz_name, "%s\\%s", psz_path, psz_bol );
359         }
360         else psz_name = strdup( psz_bol );
361 #endif
362         free( psz_path );
363     }
364     else
365     {
366         psz_name = strdup( psz_bol );
367     }
368
369     playlist_Add( p_playlist, psz_name,
370                   PLAYLIST_INSERT, i_position );
371
372     free( psz_name );
373     return 1;
374 }
375
376 /*****************************************************************************
377  * Demux: reads and demuxes data packets
378  *****************************************************************************
379  * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
380  *****************************************************************************/
381 static int Demux ( input_thread_t *p_input )
382 {
383     data_packet_t *p_data;
384     char          *p_buf, psz_line[MAX_LINE], eol_tok;
385     int           i_size, i_bufpos, i_linepos = 0;
386     playlist_t    *p_playlist;
387     int           i_position;
388     vlc_bool_t    b_discard = VLC_FALSE;
389
390     demux_sys_t   *p_m3u = (demux_sys_t *)p_input->p_demux_data;
391
392     p_playlist = (playlist_t *) vlc_object_find( p_input, VLC_OBJECT_PLAYLIST,
393                                                  FIND_ANYWHERE );
394     if( !p_playlist )
395     {
396         msg_Err( p_input, "can't find playlist" );
397         return -1;
398     }
399
400     p_playlist->pp_items[p_playlist->i_index]->b_autodeletion = VLC_TRUE;
401     i_position = p_playlist->i_index + 1;
402
403     /* Depending on wether we are dealing with an m3u/asf file, the end of
404      * line token will be different */
405     if( p_m3u->i_type == TYPE_ASX || p_m3u->i_type == TYPE_HTML )
406         eol_tok = '>';
407     else
408         eol_tok = '\n';
409
410     while( ( i_size = input_SplitBuffer( p_input, &p_data, MAX_LINE ) ) > 0 )
411     {
412         i_bufpos = 0; p_buf = p_data->p_payload_start;
413
414         while( i_size )
415         {
416             /* Build a line < MAX_LINE */
417             while( p_buf[i_bufpos] != eol_tok && i_size )
418             {
419                 if( i_linepos == MAX_LINE || b_discard == VLC_TRUE )
420                 {
421                     /* line is bigger than MAX_LINE, discard it */
422                     i_linepos = 0;
423                     b_discard = VLC_TRUE;
424                 }
425                 else
426                 {
427                     if ( eol_tok != '\n' || p_buf[i_bufpos] != '\r' )
428                     {
429                         psz_line[i_linepos] = p_buf[i_bufpos];
430                         i_linepos++;
431                     }
432                 }
433
434                 i_size--; i_bufpos++;
435             }
436
437             /* Check if we need more data */
438             if( !i_size ) continue;
439
440             i_size--; i_bufpos++;
441             b_discard = VLC_FALSE;
442
443             /* Check for empty line */
444             if( !i_linepos ) continue;
445
446             psz_line[i_linepos] = '\0';
447             i_linepos = 0;
448             i_position += ProcessLine ( p_input, p_m3u , p_playlist ,
449                                         psz_line, i_position );
450         }
451
452         input_DeletePacket( p_input->p_method_data, p_data );
453     }
454
455     if ( i_linepos && b_discard != VLC_TRUE && eol_tok == '\n' )
456     {
457         psz_line[i_linepos] = '\0';
458         i_linepos = 0;
459         i_position += ProcessLine ( p_input, p_m3u , p_playlist , psz_line,
460                                     i_position );
461     }
462
463     vlc_object_release( p_playlist );
464
465     return 0;
466 }