]> git.sesse.net Git - vlc/blob - modules/demux/playlist/ram.c
Split RAM playlist support from m3u to its own demuxer and fix its support.
[vlc] / modules / demux / playlist / ram.c
1 /*****************************************************************************
2  * ram.c : RAM playlist format import
3  *****************************************************************************
4  * Copyright (C) 2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Srikanth Raju <srikiraju@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*
25 An example:
26 rtsp://helixserver.example.com/video1.rm?rpcontextheight=250
27 &rpcontextwidth=280&rpcontexturl="http://www.example.com/relatedinfo1.html"
28 rtsp://helixserver.example.com/video2.rm?rpurl="http://www.example.com/index.html"
29 rtsp://helixserver.example.com/sample1.smil?screensize=full
30 rtsp://helixserver.example.com/audio1.rm?start=55&end=1:25
31 rtsp://helixserver.example.com/introvid.rm?title="Introduction to Streaming Media
32 Production"&author="RealNetworks, Inc."&copyright="&#169;2001, RealNetworks, Inc."
33 rtsp://helixserver.example.com/song1.rm?clipinfo="title=Artist of the Year|artist name=Your Name
34 Here|album name=My Debut|genre=Rock|copyright=2001|year=2001|comments=This one really
35 knows how to rock!"
36
37 See also:
38 http://service.real.com/help/library/guides/realone/IntroGuide/HTML/htmfiles/ramsum.htm
39 http://service.real.com/help/library/guides/realone/IntroGuide/HTML/htmfiles/ramfile.htm
40 */
41
42
43 /*****************************************************************************
44  * Preamble
45  *****************************************************************************/
46 #ifdef HAVE_CONFIG_H
47 # include "config.h"
48 #endif
49
50 #include <vlc_common.h>
51 #include <vlc_demux.h>
52 #include <vlc_charset.h>
53
54 #include "playlist.h"
55
56 struct demux_sys_t
57 {
58     char *psz_prefix;
59 };
60
61 /*****************************************************************************
62  * Local prototypes
63  *****************************************************************************/
64 static int Demux( demux_t *p_demux);
65 static int Control( demux_t *p_demux, int i_query, va_list args );
66 static bool ContainsURL( demux_t *p_demux );
67 static void ParseClipInfo( char * psz_clipinfo, char **ppsz_artist, char **ppsz_title,
68                            char **ppsz_album, char **ppsz_genre, char **ppsz_year,
69                            char **ppsz_cdnum, char **ppsz_comments );
70
71 /**
72  * Import_RAM: main import function
73  * @param p_this: this demux object
74  * @return VLC_SUCCESS if everything is okay
75  */
76 int Import_RAM( vlc_object_t *p_this )
77 {
78     demux_t *p_demux = (demux_t *)p_this;
79     const uint8_t *p_peek;
80     CHECK_PEEK( p_peek, 8 );
81     if(! demux_IsPathExtension( p_demux, ".ram" ) )
82         return VLC_EGENERIC;
83
84     STANDARD_DEMUX_INIT_MSG( "found valid RAM playlist" );
85     p_demux->p_sys->psz_prefix = FindPrefix( p_demux );
86
87     return VLC_SUCCESS;
88 }
89
90 /**
91  * Frees up memory on module close
92  * @param p_this: this demux object
93  */
94 void Close_RAM( vlc_object_t *p_this )
95 {
96     demux_t *p_demux = (demux_t *)p_this;
97     free( p_demux->p_sys->psz_prefix );
98     free( p_demux->p_sys );
99 }
100
101 /**
102  * Returns a UTF8 encoded version of the string
103  * @param str: input string
104  * @return pointer to UTF8 string
105  */
106 static inline char *MaybeFromLocaleDup (const char *str)
107 {
108     if (str == NULL)
109         return NULL;
110
111     return IsUTF8 (str) ? strdup (str) : FromLocaleDup (str);
112 }
113
114 /**
115  * Converts a string to UTF8 encoding
116  * @param str: input string
117  */
118 static inline void MaybeFromLocaleRep (char **str)
119 {
120     char *const orig_str = *str;
121
122     if ((orig_str != NULL) && !IsUTF8 (orig_str))
123     {
124         *str = FromLocaleDup (orig_str);
125         free (orig_str);
126     }
127 }
128
129 /**
130  * Skips blanks in a given buffer
131  * @param s: input string
132  * @param i_strlen: length of the buffer
133  */
134 static char *SkipBlanks(char *s, size_t i_strlen )
135 {
136     while( i_strlen > 0 ) {
137         switch( *s )
138         {
139             case ' ':
140             case '\t':
141             case '\r':
142             case '\n':
143                 --i_strlen;
144                 ++s;
145                 break;
146             default:
147                 i_strlen = 0;
148         }
149     }
150     return s;
151 }
152
153 /**
154  * Converts a time of format hour:minutes:sec.fraction to seconds
155  * @param s: input string
156  * @param i_strlen: length of the buffer
157  * @return time in seconds
158  */
159 static int ParseTime(char *s, size_t i_strlen)
160 {
161     // need to parse hour:minutes:sec.fraction string
162     int result = 0;
163     int val;
164     const char *end = s + i_strlen;
165     // skip leading spaces if any
166     s = SkipBlanks(s, i_strlen);
167
168     val = 0;
169     while( (s < end) && isdigit(*s) )
170     {
171         int newval = val*10 + (*s - '0');
172         if( newval < val )
173         {
174             // overflow
175             val = 0;
176             break;
177         }
178         val = newval;
179         ++s;
180     }
181     result = val;
182     s = SkipBlanks(s, end-s);
183     if( *s == ':' )
184     {
185         ++s;
186         s = SkipBlanks(s, end-s);
187         result = result * 60;
188         val = 0;
189         while( (s < end) && isdigit(*s) )
190         {
191             int newval = val*10 + (*s - '0');
192             if( newval < val )
193             {
194                 // overflow
195                 val = 0;
196                 break;
197             }
198             val = newval;
199             ++s;
200         }
201         result += val;
202         s = SkipBlanks(s, end-s);
203         if( *s == ':' )
204         {
205             ++s;
206             s = SkipBlanks(s, end-s);
207             result = result * 60;
208             val = 0;
209             while( (s < end) && isdigit(*s) )
210             {
211                 int newval = val*10 + (*s - '0');
212                 if( newval < val )
213                 {
214                     // overflow
215                     val = 0;
216                     break;
217                 }
218                 val = newval;
219                 ++s;
220             }
221             result += val;
222             // TODO: one day, we may need to parse fraction for sub-second resolution
223         }
224     }
225     return result;
226 }
227
228 /**
229  * Main demux callback function
230  * @param p_demux: this demux object
231  */
232 static int Demux( demux_t *p_demux )
233 {
234     char       *psz_line;
235     char       *psz_name = NULL;
236     char       *psz_artist = NULL, *psz_album = NULL, *psz_genre = NULL, *psz_year = NULL;
237     char       *psz_author = NULL, *psz_title = NULL, *psz_copyright = NULL, *psz_cdnum = NULL, *psz_comments = NULL;
238     int        i_parsed_duration = 0;
239     mtime_t    i_duration = -1;
240     const char **ppsz_options = NULL;
241     int        i_options = 0, i_start = 0, i_stop = 0;
242     bool b_cleanup = false;
243     input_item_t *p_input;
244
245     INIT_PLAYLIST_STUFF;
246
247     psz_line = stream_ReadLine( p_demux->s );
248     while( psz_line )
249     {
250         char *psz_parse = psz_line;
251
252         /* Skip leading tabs and spaces */
253         while( *psz_parse == ' ' || *psz_parse == '\t' ||
254                *psz_parse == '\n' || *psz_parse == '\r' ) psz_parse++;
255
256         if( *psz_parse == '#' )
257         {
258             /* Ignore comments */
259         }
260         else if( *psz_parse )
261         {
262             char *psz_mrl, *psz_option_start, *psz_option_next, *psz_temp_mrl, *psz_option;
263             char *psz_param, *psz_value;
264             if( !psz_name || !*psz_name )
265             {
266                 /* Default filename as name for relative entries
267                    TODO: Currently not used. Either remove or use */
268                 psz_name = MaybeFromLocaleDup( psz_parse );
269             }
270
271             /* Get the MRL from the file. Note that this might contain parameters of form ?param1=value1&param2=value2 in a RAM file */
272             psz_mrl = ProcessMRL( psz_parse, p_demux->p_sys->psz_prefix );
273             MaybeFromLocaleRep( &psz_mrl );
274
275             b_cleanup = true;
276             if ( !psz_mrl ) goto error;
277
278             /* We have the MRL, now we have to check for options and parse them from MRL */
279             psz_temp_mrl = strdup( psz_mrl );
280             psz_option_start = strchr( psz_temp_mrl, '?' ); /* Look for start of options */
281             if( psz_option_start )
282             {
283                 psz_option_start++;
284                 psz_option_next = psz_option_start;
285                 while( 1 ) /* Process each option */
286                 {
287                     /* Look for end of first option which maybe a & or \0 */
288                     psz_option_start = psz_option_next;
289                     psz_option_next = strchr( psz_option_start, '&' );
290                     if( psz_option_next )
291                     {
292                         *psz_option_next = '\0';
293                         psz_option_next++;
294                     }
295                     else
296                         psz_option_next = strchr( psz_option_start, '\0' );
297                     /* Quit if options are over */
298                     if( psz_option_next == psz_option_start )
299                         break;
300                     psz_option = MaybeFromLocaleDup( psz_option_start );
301                     /* If this option is screwed up, try the next one */
302                     if( !psz_option )
303                         continue;
304
305                     /* Parse out param and value */
306                     psz_param = psz_option;
307                     if( strchr( psz_option, '=' ) )
308                     {
309                         psz_value = strchr( psz_option, '=' ) + 1;
310                         *(strchr( psz_option, '=' )) = '\0';
311                     }
312                     else
313                         break;
314
315                     /* Take action based on parameter value in the below if else structure */
316                     /* TODO: Remove any quotes surrounding values if required */
317                     if( !strcmp( psz_param, "clipinfo" ) )
318                     {
319                         ParseClipInfo( psz_value, &psz_artist, &psz_title,
320                            &psz_album, &psz_genre, &psz_year,
321                            &psz_cdnum, &psz_comments ); /* clipinfo has various sub parameters, which is parsed by this function */
322                     }
323                     else if( !strcmp( psz_param, "author" ) )
324                         psz_author = strdup(psz_value);
325                     else if( !strcmp( psz_param, "start" ) )
326                     {
327                         i_start = ParseTime( strdup( psz_value ),strlen( psz_value ) );
328                         char * temp = NULL;
329                         if( i_start )
330                         {
331                             if( asprintf( &temp, ":start-time=%d", i_start ) == -1 )
332                                 *(temp) = NULL;
333                             if( temp && *temp )
334                                 INSERT_ELEM( ppsz_options, i_options, i_options, temp );
335                         }
336                     }
337                     else if( !strcmp( psz_param, "end" ) )
338                     {
339                         i_stop = ParseTime( strdup( psz_value ), strlen( psz_value ) );
340                         char * temp = NULL;
341                         if( i_stop )
342                         {
343                             if( asprintf( &temp, ":stop-time=%d", i_stop ) == -1 )
344                                 *(temp) = NULL;
345                             if( temp && *temp )
346                                 INSERT_ELEM( ppsz_options, i_options, i_options, temp );
347                         }
348                     }
349                     else if( !strcmp( psz_param, "title" ) )
350                         psz_title = strdup(psz_value);
351                     else if( !strcmp( psz_param, "copyright" ) )
352                         psz_copyright = strdup(psz_value);
353                     else
354                     {   /* TODO: insert option anyway? Currently ignores*/
355                         /* INSERT_ELEM( ppsz_options, i_options, i_options, psz_option ); */
356                     }
357
358                     free( psz_option );
359                 }
360
361                 *(strchr( psz_mrl, '?' )) = '\0'; /* Remove options from MRL because VLC can't get the file otherwise */
362             }
363
364             free( psz_temp_mrl );
365
366             /* Create the input item and pump in all the options into playlist item */
367             p_input = input_item_NewExt( p_demux, psz_mrl, psz_title, i_options, ppsz_options, 0, i_duration );
368
369             if( psz_artist && *psz_artist ) input_item_SetArtist( p_input, psz_artist );
370             if( psz_author && *psz_author ) input_item_SetPublisher( p_input, psz_author );
371             if( psz_title && *psz_title ) input_item_SetTitle( p_input, psz_title );
372             if( psz_copyright && *psz_copyright ) input_item_SetCopyright( p_input, psz_copyright );
373             if( psz_album && *psz_album ) input_item_SetAlbum( p_input, psz_album );
374             if( psz_genre && *psz_genre ) input_item_SetGenre( p_input, psz_genre );
375             if( psz_year && *psz_year ) input_item_SetDate( p_input, psz_copyright );
376             if( psz_cdnum && *psz_cdnum ) input_item_SetTrackNum( p_input, psz_cdnum );
377             if( psz_comments && *psz_comments ) input_item_SetDescription( p_input, psz_comments );
378
379             input_item_AddSubItem( p_current_input, p_input );
380             vlc_gc_decref( p_input );
381             free( psz_mrl );
382         }
383
384  error:
385         /* Fetch another line */
386         free( psz_line );
387         psz_line = stream_ReadLine( p_demux->s );
388         if( !psz_line ) b_cleanup = true;
389
390         if( b_cleanup )
391         {
392             /* Cleanup state */
393             while( i_options-- ) free( (char*)ppsz_options[i_options] );
394             FREENULL( ppsz_options );
395             FREENULL( psz_name );
396             FREENULL( psz_artist );
397             FREENULL( psz_title );
398             FREENULL( psz_author );
399             FREENULL( psz_copyright );
400             FREENULL( psz_album );
401             FREENULL( psz_genre );
402             FREENULL( psz_year );
403             FREENULL( psz_cdnum );
404             FREENULL( psz_comments );
405             i_options = 0;
406             i_parsed_duration = 0;
407             i_duration = -1;
408             i_start = 0;
409             i_stop = 0;
410             b_cleanup = false;
411         }
412     }
413     HANDLE_PLAY_AND_RELEASE;
414     var_Destroy( p_demux, "m3u-extvlcopt" );
415     return 0; /* Needed for correct operation of go back */
416 }
417
418 /**
419  * @param p_demux: This object
420  * @param i_query:
421  * @param args: List of arguments
422  */
423 static int Control( demux_t *p_demux, int i_query, va_list args )
424 {
425     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
426     return VLC_EGENERIC;
427 }
428
429 /**
430  * Parses clipinfo parameter
431  * @param psz_clipinfo: string containing the clipinfo parameter along with quotes
432  * @param ppsz_artist: Buffer to store artist name
433  * @param ppsz_title: Buffer to store title
434  * @param ppsz_album: Buffer to store album
435  * @param ppsz_genre: Buffer to store genre
436  * @param ppsz_year: Buffer to store year
437  * @param ppsz_cdnum: Buffer to store cdnum
438  * @param ppsz_comments: Buffer to store comments
439  */
440 static void ParseClipInfo( char *psz_clipinfo, char **ppsz_artist, char **ppsz_title,
441                            char **ppsz_album, char **ppsz_genre, char **ppsz_year,
442                            char **ppsz_cdnum, char **ppsz_comments )
443 {
444     char *psz_option_next, *psz_option_start, *psz_param, *psz_value, *psz_suboption;
445     char *psz_temp_clipinfo = strdup( psz_clipinfo );
446     psz_option_start = psz_clipinfo;
447     psz_option_start = strchr( psz_temp_clipinfo, '"' );
448     if( !psz_option_start )
449         return;
450
451     psz_option_start++;
452     psz_option_next = psz_option_start;
453     while( 1 ) /* Process each sub option */
454     {
455         /* Get the sub option */
456         psz_option_start = psz_option_next;
457         psz_option_next = strchr( psz_option_start, '|' );
458         if( psz_option_next )
459             *psz_option_next = '\0';
460         else
461             psz_option_next = strchr( psz_option_start, '"' );
462         if( psz_option_next )
463             *psz_option_next = '\0';
464         else
465             psz_option_next = strchr( psz_option_start, '\0' );
466         if( psz_option_next == psz_option_start )
467             break;
468
469         psz_suboption = MaybeFromLocaleDup( psz_option_start );
470         if( !psz_suboption )
471             break;
472
473         /* Parse out param and value */
474         psz_param = psz_suboption;
475         if( strchr( psz_suboption, '=' ) )
476         {
477             psz_value = strchr( psz_suboption, '=' ) + 1;
478             *( strchr( psz_suboption, '=' ) ) = '\0';
479         }
480         else
481             break;
482         /* Put into args */
483         if( !strcmp( psz_param, "artist name" ) )
484             *ppsz_artist = strdup( psz_value );
485         else if( !strcmp( psz_param, "title" ) )
486             *ppsz_title = strdup( psz_value );
487         else if( !strcmp( psz_param, "album name" ) )
488             *ppsz_album = strdup( psz_value );
489         else if( !strcmp( psz_param, "genre" ) )
490             *ppsz_genre = strdup( psz_value );
491         else if( !strcmp( psz_param, "year" ) )
492             *ppsz_year = strdup( psz_value );
493         else if( !strcmp( psz_param, "cdnum" ) )
494             *ppsz_cdnum = strdup( psz_value );
495         else if( !strcmp( psz_param, "comments" ) )
496             *ppsz_comments = strdup( psz_value );
497
498         free( psz_suboption );
499         psz_option_next++;
500     }
501
502     free( psz_temp_clipinfo );
503 }