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