]> git.sesse.net Git - vlc/blob - modules/demux/playlist/ram.c
ce7cc822be212a969ab6a07ca6d9635ea80c8044
[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_url.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 void ParseClipInfo( const char * psz_clipinfo, char **ppsz_artist, char **ppsz_title,
67                            char **ppsz_album, char **ppsz_genre, char **ppsz_year,
68                            char **ppsz_cdnum, char **ppsz_comments );
69
70 /**
71  * Import_RAM: main import function
72  * @param p_this: this demux object
73  * @return VLC_SUCCESS if everything is okay
74  */
75 int Import_RAM( vlc_object_t *p_this )
76 {
77     demux_t *p_demux = (demux_t *)p_this;
78
79     if(! demux_IsPathExtension( p_demux, ".ram" ) ||
80          demux_IsPathExtension( p_demux, ".rm" ) )
81         return VLC_EGENERIC;
82
83     STANDARD_DEMUX_INIT_MSG( "found valid RAM playlist" );
84     p_demux->p_sys->psz_prefix = FindPrefix( p_demux );
85
86     return VLC_SUCCESS;
87 }
88
89 /**
90  * Frees up memory on module close
91  * @param p_this: this demux object
92  */
93 void Close_RAM( vlc_object_t *p_this )
94 {
95     demux_t *p_demux = (demux_t *)p_this;
96     free( p_demux->p_sys->psz_prefix );
97     free( p_demux->p_sys );
98 }
99
100 /**
101  * Skips blanks in a given buffer
102  * @param s: input string
103  * @param i_strlen: length of the buffer
104  */
105 static const char *SkipBlanks( const char *s, size_t i_strlen )
106 {
107     while( i_strlen > 0 ) {
108         switch( *s )
109         {
110             case ' ':
111             case '\t':
112             case '\r':
113             case '\n':
114                 --i_strlen;
115                 ++s;
116                 break;
117             default:
118                 i_strlen = 0;
119         }
120     }
121     return s;
122 }
123
124 /**
125  * Converts a time of format hour:minutes:sec.fraction to seconds
126  * @param s: input string
127  * @param i_strlen: length of the buffer
128  * @return time in seconds
129  */
130 static int ParseTime( const char *s, size_t i_strlen)
131 {
132     // need to parse hour:minutes:sec.fraction string
133     int result = 0;
134     int val;
135     const char *end = s + i_strlen;
136     // skip leading spaces if any
137     s = SkipBlanks(s, i_strlen);
138
139     val = 0;
140     while( (s < end) && isdigit(*s) )
141     {
142         int newval = val*10 + (*s - '0');
143         if( newval < val )
144         {
145             // overflow
146             val = 0;
147             break;
148         }
149         val = newval;
150         ++s;
151     }
152     result = val;
153     s = SkipBlanks(s, end-s);
154     if( *s == ':' )
155     {
156         ++s;
157         s = SkipBlanks(s, end-s);
158         result = result * 60;
159         val = 0;
160         while( (s < end) && isdigit(*s) )
161         {
162             int newval = val*10 + (*s - '0');
163             if( newval < val )
164             {
165                 // overflow
166                 val = 0;
167                 break;
168             }
169             val = newval;
170             ++s;
171         }
172         result += val;
173         s = SkipBlanks(s, end-s);
174         if( *s == ':' )
175         {
176             ++s;
177             s = SkipBlanks(s, end-s);
178             result = result * 60;
179             val = 0;
180             while( (s < end) && isdigit(*s) )
181             {
182                 int newval = val*10 + (*s - '0');
183                 if( newval < val )
184                 {
185                     // overflow
186                     val = 0;
187                     break;
188                 }
189                 val = newval;
190                 ++s;
191             }
192             result += val;
193             // TODO: one day, we may need to parse fraction for sub-second resolution
194         }
195     }
196     return result;
197 }
198
199 /**
200  * Main demux callback function
201  * @param p_demux: this demux object
202  */
203 static int Demux( demux_t *p_demux )
204 {
205     char       *psz_line;
206     char       *psz_artist = NULL, *psz_album = NULL, *psz_genre = NULL, *psz_year = NULL;
207     char       *psz_author = NULL, *psz_title = NULL, *psz_copyright = NULL, *psz_cdnum = NULL, *psz_comments = NULL;
208     int        i_parsed_duration = 0;
209     mtime_t    i_duration = -1;
210     const char **ppsz_options = NULL;
211     int        i_options = 0, i_start = 0, i_stop = 0;
212     bool b_cleanup = false;
213     input_item_t *p_input;
214
215     input_item_t *p_current_input = GetCurrentItem(p_demux);
216
217     input_item_node_t *p_subitems = input_item_node_Create( p_current_input );
218
219     psz_line = stream_ReadLine( p_demux->s );
220     while( psz_line )
221     {
222         char *psz_parse = psz_line;
223
224         /* Skip leading tabs and spaces */
225         while( *psz_parse == ' ' || *psz_parse == '\t' ||
226                *psz_parse == '\n' || *psz_parse == '\r' ) psz_parse++;
227
228         if( *psz_parse == '#' )
229         {
230             /* Ignore comments */
231         }
232         else if( *psz_parse )
233         {
234             char *psz_mrl, *psz_option_next, *psz_option;
235             char *psz_param, *psz_value;
236
237             /* Get the MRL from the file. Note that this might contain parameters of form ?param1=value1&param2=value2 in a RAM file */
238             psz_mrl = ProcessMRL( psz_parse, p_demux->p_sys->psz_prefix );
239
240             b_cleanup = true;
241             if ( !psz_mrl ) goto error;
242
243             /* We have the MRL, now we have to check for options and parse them from MRL */
244             psz_option = strchr( psz_mrl, '?' ); /* Look for start of options */
245             if( psz_option )
246             {
247                 /* Remove options from MRL
248                    because VLC can't get the file otherwise */
249                 *psz_option = '\0';
250                 psz_option++;
251                 psz_option_next = psz_option;
252                 while( 1 ) /* Process each option */
253                 {
254                     /* Look for end of first option which maybe a & or \0 */
255                     psz_option = psz_option_next;
256                     psz_option_next = strchr( psz_option, '&' );
257                     if( psz_option_next )
258                     {
259                         *psz_option_next = '\0';
260                         psz_option_next++;
261                     }
262                     else
263                         psz_option_next = strchr( psz_option, '\0' );
264                     /* Quit if options are over */
265                     if( psz_option_next == psz_option )
266                         break;
267
268                     /* Parse out param and value */
269                     psz_param = psz_option;
270                     psz_value = strchr( psz_option, '=' );
271                     if( psz_value == NULL )
272                         break;
273                     *psz_value = '\0';
274                     psz_value++;
275
276                     /* Take action based on parameter value in the below if else structure */
277                     /* TODO: Remove any quotes surrounding values if required */
278                     if( !strcmp( psz_param, "clipinfo" ) )
279                     {
280                         ParseClipInfo( psz_value, &psz_artist, &psz_title,
281                            &psz_album, &psz_genre, &psz_year,
282                            &psz_cdnum, &psz_comments ); /* clipinfo has various sub parameters, which is parsed by this function */
283                     }
284                     else if( !strcmp( psz_param, "author" ) )
285                         psz_author = decode_URI_duplicate(psz_value);
286                     else if( !strcmp( psz_param, "start" ) )
287                     {
288                         i_start = ParseTime( psz_value, strlen( psz_value ) );
289                         char *temp;
290                         if( i_start )
291                         {
292                             if( asprintf( &temp, ":start-time=%d", i_start ) != -1 )
293                                 INSERT_ELEM( ppsz_options, i_options, i_options, temp );
294                         }
295                     }
296                     else if( !strcmp( psz_param, "end" ) )
297                     {
298                         i_stop = ParseTime( psz_value, strlen( psz_value ) );
299                         char *temp;
300                         if( i_stop )
301                         {
302                             if( asprintf( &temp, ":stop-time=%d", i_stop ) != -1 )
303                                 INSERT_ELEM( ppsz_options, i_options, i_options, temp );
304                         }
305                     }
306                     else if( !strcmp( psz_param, "title" ) )
307                         psz_title = decode_URI_duplicate(psz_value);
308                     else if( !strcmp( psz_param, "copyright" ) )
309                         psz_copyright = decode_URI_duplicate(psz_value);
310                     else
311                     {   /* TODO: insert option anyway? Currently ignores*/
312                         /* INSERT_ELEM( ppsz_options, i_options, i_options, psz_option ); */
313                     }
314                 }
315             }
316
317             /* Create the input item and pump in all the options into playlist item */
318             p_input = input_item_NewExt( p_demux, psz_mrl, psz_title, i_options, ppsz_options, 0, i_duration );
319
320             if( !EMPTY_STR( psz_artist ) ) input_item_SetArtist( p_input, psz_artist );
321             if( !EMPTY_STR( psz_author ) ) input_item_SetPublisher( p_input, psz_author );
322             if( !EMPTY_STR( psz_title ) ) input_item_SetTitle( p_input, psz_title );
323             if( !EMPTY_STR( psz_copyright ) ) input_item_SetCopyright( p_input, psz_copyright );
324             if( !EMPTY_STR( psz_album ) ) input_item_SetAlbum( p_input, psz_album );
325             if( !EMPTY_STR( psz_genre ) ) input_item_SetGenre( p_input, psz_genre );
326             if( !EMPTY_STR( psz_year ) ) input_item_SetDate( p_input, psz_copyright );
327             if( !EMPTY_STR( psz_cdnum ) ) input_item_SetTrackNum( p_input, psz_cdnum );
328             if( !EMPTY_STR( psz_comments ) ) input_item_SetDescription( p_input, psz_comments );
329
330             input_item_AddSubItem( p_current_input, p_input );
331             input_item_node_AppendItem( p_subitems, p_input );
332             vlc_gc_decref( p_input );
333             free( psz_mrl );
334         }
335
336  error:
337         /* Fetch another line */
338         free( psz_line );
339         psz_line = stream_ReadLine( p_demux->s );
340         if( !psz_line ) b_cleanup = true;
341
342         if( b_cleanup )
343         {
344             /* Cleanup state */
345             while( i_options-- ) free( (char*)ppsz_options[i_options] );
346             FREENULL( ppsz_options );
347             FREENULL( psz_artist );
348             FREENULL( psz_title );
349             FREENULL( psz_author );
350             FREENULL( psz_copyright );
351             FREENULL( psz_album );
352             FREENULL( psz_genre );
353             FREENULL( psz_year );
354             FREENULL( psz_cdnum );
355             FREENULL( psz_comments );
356             i_options = 0;
357             i_parsed_duration = 0;
358             i_duration = -1;
359             i_start = 0;
360             i_stop = 0;
361             b_cleanup = false;
362         }
363     }
364     input_item_AddSubItemTree( p_subitems );
365     input_item_node_Delete( p_subitems );
366     vlc_gc_decref(p_current_input);
367     var_Destroy( p_demux, "m3u-extvlcopt" );
368     return 0; /* Needed for correct operation of go back */
369 }
370
371 /**
372  * @param p_demux: This object
373  * @param i_query:
374  * @param args: List of arguments
375  */
376 static int Control( demux_t *p_demux, int i_query, va_list args )
377 {
378     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
379     return VLC_EGENERIC;
380 }
381
382 /**
383  * Parses clipinfo parameter
384  * @param psz_clipinfo: string containing the clipinfo parameter along with quotes
385  * @param ppsz_artist: Buffer to store artist name
386  * @param ppsz_title: Buffer to store title
387  * @param ppsz_album: Buffer to store album
388  * @param ppsz_genre: Buffer to store genre
389  * @param ppsz_year: Buffer to store year
390  * @param ppsz_cdnum: Buffer to store cdnum
391  * @param ppsz_comments: Buffer to store comments
392  */
393 static void ParseClipInfo( const char *psz_clipinfo, char **ppsz_artist, char **ppsz_title,
394                            char **ppsz_album, char **ppsz_genre, char **ppsz_year,
395                            char **ppsz_cdnum, char **ppsz_comments )
396 {
397     char *psz_option_next, *psz_option_start, *psz_param, *psz_value, *psz_suboption;
398     char *psz_temp_clipinfo = strdup( psz_clipinfo );
399     psz_option_start = strchr( psz_temp_clipinfo, '"' );
400     if( !psz_option_start )
401     {
402         free( psz_temp_clipinfo );
403         return;
404     }
405
406     psz_option_start++;
407     psz_option_next = psz_option_start;
408     while( 1 ) /* Process each sub option */
409     {
410         /* Get the sub option */
411         psz_option_start = psz_option_next;
412         psz_option_next = strchr( psz_option_start, '|' );
413         if( psz_option_next )
414             *psz_option_next = '\0';
415         else
416             psz_option_next = strchr( psz_option_start, '"' );
417         if( psz_option_next )
418             *psz_option_next = '\0';
419         else
420             psz_option_next = strchr( psz_option_start, '\0' );
421         if( psz_option_next == psz_option_start )
422             break;
423
424         psz_suboption = strdup( psz_option_start );
425         if( !psz_suboption )
426             break;
427
428         /* Parse out param and value */
429         psz_param = psz_suboption;
430         if( strchr( psz_suboption, '=' ) )
431         {
432             psz_value = strchr( psz_suboption, '=' ) + 1;
433             *( strchr( psz_suboption, '=' ) ) = '\0';
434         }
435         else
436             break;
437         /* Put into args */
438         if( !strcmp( psz_param, "artist name" ) )
439             *ppsz_artist = decode_URI_duplicate( psz_value );
440         else if( !strcmp( psz_param, "title" ) )
441             *ppsz_title = decode_URI_duplicate( psz_value );
442         else if( !strcmp( psz_param, "album name" ) )
443             *ppsz_album = decode_URI_duplicate( psz_value );
444         else if( !strcmp( psz_param, "genre" ) )
445             *ppsz_genre = decode_URI_duplicate( psz_value );
446         else if( !strcmp( psz_param, "year" ) )
447             *ppsz_year = decode_URI_duplicate( psz_value );
448         else if( !strcmp( psz_param, "cdnum" ) )
449             *ppsz_cdnum = decode_URI_duplicate( psz_value );
450         else if( !strcmp( psz_param, "comments" ) )
451             *ppsz_comments = decode_URI_duplicate( psz_value );
452
453         free( psz_suboption );
454         psz_option_next++;
455     }
456
457     free( psz_temp_clipinfo );
458 }