Update to MPlayer SVN rev 34180.
[vaapi:kinkis-mplayer.git] / .svn / text-base / playtreeparser.c.svn-base
1 /*
2  * This file is part of MPlayer.
3  *
4  * MPlayer is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * MPlayer is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with MPlayer; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 /// \file
20 /// \ingroup PlaytreeParser
21
22 #include "config.h"
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <limits.h>
34 #include "asxparser.h"
35 #include "m_config.h"
36 #include "playtree.h"
37 #include "playtreeparser.h"
38 #include "stream/stream.h"
39 #include "libmpdemux/demuxer.h"
40 #include "mp_msg.h"
41
42
43 #define BUF_STEP 1024
44
45 #define WHITES " \n\r\t"
46
47 static void
48 strstrip(char* str) {
49   char* i;
50
51   if (str==NULL)
52     return;
53   for(i = str ; i[0] != '\0' && strchr(WHITES,i[0]) != NULL; i++)
54     /* NOTHING */;
55   if(i[0] != '\0') {
56     memmove(str,i,strlen(i) + 1);
57     for(i = str + strlen(str) - 1 ; strchr(WHITES,i[0]) != NULL; i--)
58       /* NOTHING */;
59     i[1] = '\0';
60   } else
61     str[0] = '\0';
62 }
63
64 static char*
65 play_tree_parser_get_line(play_tree_parser_t* p) {
66   char *end,*line_end;
67   int r,resize = 0;
68
69   if(p->buffer == NULL) {
70     p->buffer = malloc(BUF_STEP);
71     p->buffer_size = BUF_STEP;
72     p->buffer[0] = 0;
73     p->iter = p->buffer;
74   }
75
76   if(p->stream->eof && (p->buffer_end == 0 || p->iter[0] == '\0'))
77     return NULL;
78
79   assert(p->buffer_end < p->buffer_size);
80   assert(!p->buffer[p->buffer_end]);
81   while(1) {
82
83     if(resize) {
84       char *tmp;
85       r = p->iter - p->buffer;
86       end = p->buffer + p->buffer_end;
87       if (p->buffer_size > INT_MAX - BUF_STEP)
88         break;
89       tmp = realloc(p->buffer, p->buffer_size + BUF_STEP);
90       if (!tmp)
91         break;
92       p->buffer = tmp;
93       p->iter = p->buffer + r;
94       p->buffer_size += BUF_STEP;
95       resize = 0;
96     }
97
98     if(p->buffer_size - p->buffer_end > 1 && ! p->stream->eof) {
99       r = stream_read(p->stream,p->buffer + p->buffer_end,p->buffer_size - p->buffer_end - 1);
100       if(r > 0) {
101         p->buffer_end += r;
102         assert(p->buffer_end < p->buffer_size);
103         p->buffer[p->buffer_end] = '\0';
104         while(strlen(p->buffer + p->buffer_end - r) != r)
105           p->buffer[p->buffer_end - r + strlen(p->buffer + p->buffer_end - r)] = '\n';
106       }
107       assert(!p->buffer[p->buffer_end]);
108     }
109
110     end = strchr(p->iter,'\n');
111     if(!end) {
112       if(p->stream->eof) {
113         end = p->buffer + p->buffer_end;
114         break;
115       }
116       resize = 1;
117       continue;
118     }
119     break;
120   }
121
122   line_end = (end > p->iter && *(end-1) == '\r') ? end-1 : end;
123   if(line_end - p->iter >= 0)
124     p->line = realloc(p->line, line_end - p->iter + 1);
125   else
126     return NULL;
127   if(line_end - p->iter > 0)
128     strncpy(p->line,p->iter,line_end - p->iter);
129   p->line[line_end - p->iter] = '\0';
130   if(end[0] != '\0')
131     end++;
132
133   if(!p->keep) {
134     if(end[0] != '\0') {
135       p->buffer_end -= end-p->iter;
136       memmove(p->buffer,end,p->buffer_end);
137     } else
138       p->buffer_end = 0;
139     p->buffer[p->buffer_end] = '\0';
140     p->iter = p->buffer;
141   } else
142     p->iter = end;
143
144   return p->line;
145 }
146
147 static void
148 play_tree_parser_reset(play_tree_parser_t* p) {
149   p->iter = p->buffer;
150 }
151
152 static void
153 play_tree_parser_stop_keeping(play_tree_parser_t* p) {
154   p->keep = 0;
155   if(p->iter && p->iter != p->buffer) {
156     p->buffer_end -= p->iter -p->buffer;
157     if(p->buffer_end)
158       memmove(p->buffer,p->iter,p->buffer_end);
159     p->buffer[p->buffer_end] = 0;
160     p->iter = p->buffer;
161   }
162 }
163
164
165 static play_tree_t*
166 parse_asx(play_tree_parser_t* p) {
167   int comments = 0,get_line = 1;
168   char* line = NULL;
169
170   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying asx...\n");
171
172   while(1) {
173     if(get_line) {
174       line = play_tree_parser_get_line(p);
175       if(!line)
176         return NULL;
177       strstrip(line);
178       if(line[0] == '\0')
179         continue;
180     }
181     if(!comments) {
182       if(line[0] != '<') {
183         mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"First char isn't '<' but '%c'\n",line[0]);
184         mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Buffer = [%s]\n",p->buffer);
185         return NULL;
186       } else if(strncmp(line,"<!--",4) == 0) { // Comments
187         comments = 1;
188         line += 4;
189         if(line[0] != '\0' && strlen(line) > 0)
190           get_line = 0;
191       } else if(strncasecmp(line,"<ASX",4) == 0) // We got an asx element
192         break;
193       else // We don't get an asx
194         return NULL;
195     } else { // Comments
196       char* c;
197       c = strchr(line,'-');
198       if(c) {
199         if (strncmp(c,"--!>",4) == 0) { // End of comments
200           comments = 0;
201           line = c+4;
202           if(line[0] != '\0') // There is some more data on this line : keep it
203             get_line = 0;
204
205         } else {
206           line = c+1; // Jump the -
207           if(line[0] != '\0') // Some more data
208             get_line = 0;
209           else  // End of line
210             get_line = 1;
211         }
212       } else // No - on this line (or rest of line) : get next one
213         get_line = 1;
214     }
215   }
216
217   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected asx format\n");
218
219   // We have an asx : load it in memory and parse
220
221   while((line = play_tree_parser_get_line(p)) != NULL)
222     /* NOTHING */;
223
224  mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Parsing asx file: [%s]\n",p->buffer);
225  return asx_parser_build_tree(p->buffer,p->deep);
226 }
227
228 static char*
229 pls_entry_get_value(char* line) {
230   char* i;
231
232   i = strchr(line,'=');
233   if(!i || i[1] == '\0')
234     return NULL;
235   else
236     return i+1;
237 }
238
239 typedef struct pls_entry {
240   char* file;
241   char* title;
242   char* length;
243 } pls_entry_t;
244
245 static int
246 pls_read_entry(char* line,pls_entry_t** _e,int* _max_entry,char** val) {
247   int num,max_entry = (*_max_entry);
248   pls_entry_t* e = (*_e);
249   int limit = INT_MAX / sizeof(*e);
250   char* v;
251
252   v = pls_entry_get_value(line);
253   if(!v) {
254     mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
255     return -1;
256   }
257
258   num = atoi(line);
259   if(num <= 0 || num > limit) {
260     if (max_entry >= limit) {
261         mp_msg(MSGT_PLAYTREE, MSGL_WARN, "Too many index entries\n");
262         return -1;
263     }
264     num = max_entry+1;
265     mp_msg(MSGT_PLAYTREE,MSGL_WARN,"No or invalid entry index in entry %s\nAssuming %d\n",line,num);
266   }
267   if(num > max_entry) {
268     e = realloc(e, num * sizeof(pls_entry_t));
269     if (!e)
270       return -1;
271     memset(&e[max_entry],0,(num-max_entry)*sizeof(pls_entry_t));
272     max_entry = num;
273   }
274   (*_e) = e;
275   (*_max_entry) = max_entry;
276   (*val) = v;
277
278   return num;
279 }
280
281
282 static play_tree_t*
283 parse_pls(play_tree_parser_t* p) {
284   char *line,*v;
285   pls_entry_t* entries = NULL;
286   int n_entries = 0,max_entry=0,num;
287   play_tree_t *list = NULL, *entry = NULL, *last_entry = NULL;
288
289   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying Winamp playlist...\n");
290   while((line = play_tree_parser_get_line(p))) {
291     strstrip(line);
292     if(strlen(line))
293       break;
294   }
295   if (!line)
296     return NULL;
297   if(strcasecmp(line,"[playlist]"))
298     return NULL;
299   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected Winamp playlist format\n");
300   play_tree_parser_stop_keeping(p);
301   line = play_tree_parser_get_line(p);
302   if(!line)
303     return NULL;
304   strstrip(line);
305   if(strncasecmp(line,"NumberOfEntries",15) == 0) {
306     v = pls_entry_get_value(line);
307     n_entries = atoi(v);
308     if(n_entries < 0)
309       mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Invalid number of entries: very funny!!!\n");
310     else
311       mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Playlist claims to have %d entries. Let's see.\n",n_entries);
312     line = play_tree_parser_get_line(p);
313   }
314
315   while(line) {
316     strstrip(line);
317     if(line[0] == '\0') {
318       line = play_tree_parser_get_line(p);
319       continue;
320     }
321     if(strncasecmp(line,"File",4) == 0) {
322       num = pls_read_entry(line+4,&entries,&max_entry,&v);
323       if(num < 0)
324         mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
325       else
326         entries[num-1].file = strdup(v);
327     } else if(strncasecmp(line,"Title",5) == 0) {
328       num = pls_read_entry(line+5,&entries,&max_entry,&v);
329       if(num < 0)
330         mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
331       else
332         entries[num-1].title = strdup(v);
333     } else if(strncasecmp(line,"Length",6) == 0) {
334       num = pls_read_entry(line+6,&entries,&max_entry,&v);
335       if(num < 0)
336         mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
337       else {
338         char *end;
339         long val = strtol(v, &end, 10);
340         if (*end || (val <= 0 && val != -1))
341           mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Invalid length value in entry %s\n",line);
342         else if (val > 0)
343           entries[num-1].length = strdup(v);
344       }
345     } else
346       mp_msg(MSGT_PLAYTREE,MSGL_WARN,"Unknown entry type %s\n",line);
347     line = play_tree_parser_get_line(p);
348   }
349
350   for(num = 0; num < max_entry ; num++) {
351     if(entries[num].file == NULL)
352       mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Entry %d don't have a file !!!!\n",num+1);
353     else {
354       mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding entry %s\n",entries[num].file);
355       entry = play_tree_new();
356       play_tree_add_file(entry,entries[num].file);
357       if (entries[num].length)
358         play_tree_set_param(entry, "endpos", entries[num].length);
359       free(entries[num].file);
360       if(list)
361         play_tree_append_entry(last_entry,entry);
362       else
363         list = entry;
364       last_entry = entry;
365     }
366     // When we have info in playtree we add these info
367     free(entries[num].title);
368     free(entries[num].length);
369   }
370
371   free(entries);
372
373   if (!list)
374     return NULL;
375
376   entry = play_tree_new();
377   play_tree_set_child(entry,list);
378   return entry;
379 }
380
381 /*
382  Reference Ini-Format: Each entry is assumed a reference
383  */
384 static play_tree_t*
385 parse_ref_ini(play_tree_parser_t* p) {
386   char *line,*v;
387   play_tree_t *list = NULL, *entry = NULL, *last_entry = NULL;
388
389   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying reference-ini playlist...\n");
390   if (!(line = play_tree_parser_get_line(p)))
391     return NULL;
392   strstrip(line);
393   if(strcasecmp(line,"[Reference]"))
394     return NULL;
395   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected reference-ini playlist format\n");
396   play_tree_parser_stop_keeping(p);
397   line = play_tree_parser_get_line(p);
398   if(!line)
399     return NULL;
400   while(line) {
401     strstrip(line);
402     if(strncasecmp(line,"Ref",3) == 0) {
403       v = pls_entry_get_value(line+3);
404       if(!v)
405         mp_msg(MSGT_PLAYTREE,MSGL_ERR,"No value in entry %s\n",line);
406       else
407       {
408         mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding entry %s\n",v);
409         entry = play_tree_new();
410         play_tree_add_file(entry,v);
411         if(list)
412           play_tree_append_entry(last_entry,entry);
413         else
414           list = entry;
415         last_entry = entry;
416       }
417     }
418     line = play_tree_parser_get_line(p);
419   }
420
421   if(!list) return NULL;
422   entry = play_tree_new();
423   play_tree_set_child(entry,list);
424   return entry;
425 }
426
427 static play_tree_t*
428 parse_m3u(play_tree_parser_t* p) {
429   char* line;
430   play_tree_t *list = NULL, *entry = NULL, *last_entry = NULL;
431
432   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying extended m3u playlist...\n");
433   if (!(line = play_tree_parser_get_line(p)))
434     return NULL;
435   strstrip(line);
436   if(strcasecmp(line,"#EXTM3U"))
437     return NULL;
438   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected extended m3u playlist format\n");
439   play_tree_parser_stop_keeping(p);
440
441   while((line = play_tree_parser_get_line(p)) != NULL) {
442     strstrip(line);
443     if(line[0] == '\0')
444       continue;
445     /* EXTM3U files contain such lines:
446      * #EXTINF:<seconds>, <title>
447      * followed by a line with the filename
448      * for now we have no place to put that
449      * so we just skip that extra-info ::atmos
450      */
451     if(line[0] == '#') {
452 #if 0 /* code functional */
453       if(strncasecmp(line,"#EXTINF:",8) == 0) {
454         mp_msg(MSGT_PLAYTREE,MSGL_INFO,"[M3U] Duration: %dsec  Title: %s\n",
455           strtol(line+8,&line,10), line+2);
456       }
457 #endif
458       continue;
459     }
460     entry = play_tree_new();
461     play_tree_add_file(entry,line);
462     if(!list)
463       list = entry;
464     else
465       play_tree_append_entry(last_entry,entry);
466     last_entry = entry;
467   }
468
469   if(!list) return NULL;
470   entry = play_tree_new();
471   play_tree_set_child(entry,list);
472   return entry;
473 }
474
475 static play_tree_t*
476 parse_smil(play_tree_parser_t* p) {
477   int entrymode=0;
478   char* line,source[512],*pos,*s_start,*s_end,*src_line;
479   play_tree_t *list = NULL, *entry = NULL, *last_entry = NULL;
480   int is_rmsmil = 0;
481   unsigned int npkt, ttlpkt;
482
483   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying smil playlist...\n");
484
485   // Check if smil
486   while((line = play_tree_parser_get_line(p)) != NULL) {
487     strstrip(line);
488     if(line[0] == '\0') // Ignore empties
489       continue;
490     if (strncasecmp(line,"<?xml",5)==0) // smil in xml
491       continue;
492     if (strncasecmp(line,"<!DOCTYPE smil",13)==0) // smil in xml
493       continue;
494     if (strncasecmp(line,"<smil",5)==0 || strncasecmp(line,"<?wpl",5)==0 ||
495       strncasecmp(line,"(smil-document",14)==0)
496       break; // smil header found
497     else
498       return NULL; //line not smil exit
499   }
500
501   if (!line) return NULL;
502   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected smil playlist format\n");
503   play_tree_parser_stop_keeping(p);
504
505   if (strncasecmp(line,"(smil-document",14)==0) {
506     mp_msg(MSGT_PLAYTREE,MSGL_V,"Special smil-over-realrtsp playlist header\n");
507     is_rmsmil = 1;
508     if (sscanf(line, "(smil-document (ver 1.0)(npkt %u)(ttlpkt %u", &npkt, &ttlpkt) != 2) {
509       mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: header parsing failure, assuming single packet.\n");
510       npkt = ttlpkt = 1;
511     }
512     if (ttlpkt == 0 || npkt > ttlpkt) {
513       mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: bad packet counters (npkk = %u, ttlpkt = %u), assuming single packet.\n",
514         npkt, ttlpkt);
515       npkt = ttlpkt = 1;
516     }
517   }
518
519   //Get entries from smil
520   src_line = line;
521   line = NULL;
522   do {
523     strstrip(src_line);
524     free(line);
525     line = NULL;
526     /* If we're parsing smil over realrtsp and this is not the last packet and
527      * this is the last line in the packet (terminating with ") ) we must get
528      * the next line, strip the header, and concatenate it to the current line.
529      */
530     if (is_rmsmil && npkt != ttlpkt && strstr(src_line,"\")")) {
531       char *payload;
532
533       line = strdup(src_line);
534       if(!(src_line = play_tree_parser_get_line(p))) {
535         mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't get line from packet %u/%u.\n", npkt, ttlpkt);
536         break;
537       }
538       strstrip(src_line);
539       // Skip header, packet starts after "
540       if(!(payload = strchr(src_line,'\"'))) {
541         mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't find start of packet, using complete line.\n");
542         payload = src_line;
543       } else
544         payload++;
545       // Skip ") at the end of the last line from the current packet
546       line[strlen(line)-2] = 0;
547       line = realloc(line, strlen(line)+strlen(payload)+1);
548       strcat (line, payload);
549       npkt++;
550     } else
551       line = strdup(src_line);
552     /* Unescape \" to " for smil-over-rtsp */
553     if (is_rmsmil && line[0] != '\0') {
554       int i, j;
555
556       for (i = 0; i < strlen(line); i++)
557         if (line[i] == '\\' && line[i+1] == '"')
558           for (j = i; line[j]; j++)
559             line[j] = line[j+1];
560     }
561     pos = line;
562    while (pos) {
563     if (!entrymode) { // all entries filled so far
564      while ((pos=strchr(pos, '<'))) {
565       if (strncasecmp(pos,"<video",6)==0  || strncasecmp(pos,"<audio",6)==0 || strncasecmp(pos,"<media",6)==0) {
566           entrymode=1;
567           break; // Got a valid tag, exit '<' search loop
568       }
569       pos++;
570      }
571     }
572     if (entrymode) { //Entry found but not yet filled
573       pos = strstr(pos,"src=");   // Is source present on this line
574       if (pos != NULL) {
575         entrymode=0;
576         if (pos[4] != '"' && pos[4] != '\'') {
577           mp_msg(MSGT_PLAYTREE,MSGL_V,"Unknown delimiter %c in source line %s\n", pos[4], line);
578           break;
579         }
580         s_start=pos+5;
581         s_end=strchr(s_start,pos[4]);
582         if (s_end == NULL) {
583           mp_msg(MSGT_PLAYTREE,MSGL_V,"Error parsing this source line %s\n",line);
584           break;
585         }
586         if (s_end-s_start> 511) {
587           mp_msg(MSGT_PLAYTREE,MSGL_V,"Cannot store such a large source %s\n",line);
588           break;
589         }
590         strncpy(source,s_start,s_end-s_start);
591         source[(s_end-s_start)]='\0'; // Null terminate
592         entry = play_tree_new();
593         play_tree_add_file(entry,source);
594         if(!list)  //Insert new entry
595           list = entry;
596         else
597           play_tree_append_entry(last_entry,entry);
598         last_entry = entry;
599         pos = s_end;
600       }
601     }
602    }
603   } while((src_line = play_tree_parser_get_line(p)) != NULL);
604
605   free(line);
606
607   if(!list) return NULL; // Nothing found
608
609   entry = play_tree_new();
610   play_tree_set_child(entry,list);
611   return entry;
612 }
613
614 static play_tree_t*
615 embedded_playlist_parse(char *line) {
616   int f=DEMUXER_TYPE_PLAYLIST;
617   stream_t* stream;
618   play_tree_parser_t* ptp;
619   play_tree_t* entry;
620
621   // Get stream opened to link
622   stream=open_stream(line,0,&f);
623   if(!stream) {
624     mp_msg(MSGT_PLAYTREE,MSGL_WARN,"Can't open playlist %s\n",line);
625     return NULL;
626   }
627
628   //add new playtree
629   mp_msg(MSGT_PLAYTREE,MSGL_V,"Adding playlist %s to element entryref\n",line);
630
631   ptp = play_tree_parser_new(stream,1);
632   entry = play_tree_parser_get_play_tree(ptp, 1);
633   play_tree_parser_free(ptp);
634   free_stream(stream);
635
636   return entry;
637 }
638
639 static play_tree_t*
640 parse_textplain(play_tree_parser_t* p) {
641   char* line;
642   char *c;
643   int embedded;
644   play_tree_t *list = NULL, *entry = NULL, *last_entry = NULL;
645
646   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying plaintext playlist...\n");
647   play_tree_parser_stop_keeping(p);
648
649   while((line = play_tree_parser_get_line(p)) != NULL) {
650     strstrip(line);
651     if(line[0] == '\0' || line[0] == '#' || (line[0] == '/' && line[1] == '/'))
652       continue;
653
654     //Special check for embedded smil or ram reference in file
655     embedded = 0;
656     if (strlen(line) > 5)
657       for(c = line; c[0]; c++ )
658         if ( ((c[0] == '.') && //start with . and next have smil with optional ? or &
659            (tolower(c[1]) == 's') && (tolower(c[2])== 'm') &&
660            (tolower(c[3]) == 'i') && (tolower(c[4]) == 'l') &&
661            (!c[5] || c[5] == '?' || c[5] == '&')) || // or
662           ((c[0] == '.') && // start with . and next have smi or ram with optional ? or &
663           ( ((tolower(c[1]) == 's') && (tolower(c[2])== 'm') && (tolower(c[3]) == 'i')) ||
664             ((tolower(c[1]) == 'r') && (tolower(c[2])== 'a') && (tolower(c[3]) == 'm')) )
665            && (!c[4] || c[4] == '?' || c[4] == '&')) ){
666           entry=embedded_playlist_parse(line);
667           embedded = 1;
668           break;
669         }
670
671     if (!embedded) {      //regular file link
672       entry = play_tree_new();
673       play_tree_add_file(entry,line);
674     }
675
676     if (entry != NULL) {
677       if(!list)
678         list = entry;
679       else
680         play_tree_append_entry(last_entry,entry);
681       last_entry = entry;
682     }
683   }
684
685   if(!list) return NULL;
686   entry = play_tree_new();
687   play_tree_set_child(entry,list);
688   return entry;
689 }
690
691 /**
692  * \brief decode the base64 used in nsc files
693  * \param in input string, 0-terminated
694  * \param buf output buffer, must point to memory suitable for realloc,
695  *            will be NULL on failure.
696  * \return decoded length in bytes
697  */
698 static int decode_nsc_base64(char *in, char **buf) {
699   int i, j, n;
700   if (in[0] != '0' || in[1] != '2')
701     goto err_out;
702   in += 2; // skip prefix
703   if (strlen(in) < 16) // error out if nothing to decode
704     goto err_out;
705   in += 12; // skip encoded string length
706   n = strlen(in) / 4;
707   *buf = realloc(*buf, n * 3);
708   for (i = 0; i < n; i++) {
709     uint8_t c[4];
710     for (j = 0; j < 4; j++) {
711       c[j] = in[4 * i + j];
712       if (c[j] >= '0' && c[j] <= '9') c[j] += 0 - '0';
713       else if (c[j] >= 'A' && c[j] <= 'Z') c[j] += 10 - 'A';
714       else if (c[j] >= 'a' && c[j] <= 'z') c[j] += 36 - 'a';
715       else if (c[j] == '{') c[j] = 62;
716       else if (c[j] == '}') c[j] = 63;
717       else {
718         mp_msg(MSGT_PLAYTREE, MSGL_ERR, "Invalid character %c (0x%02"PRIx8")\n", c[j], c[j]);
719         goto err_out;
720       }
721     }
722     (*buf)[3 * i] = (c[0] << 2) | (c[1] >> 4);
723     (*buf)[3 * i + 1] = (c[1] << 4) | (c[2] >> 2);
724     (*buf)[3 * i + 2] = (c[2] << 6) | c[3];
725   }
726   return 3 * n;
727 err_out:
728   free(*buf);
729   *buf = NULL;
730   return 0;
731 }
732
733 /**
734  * \brief "converts" utf16 to ascii by just discarding every second byte
735  * \param buf buffer to convert
736  * \param len lenght of buffer, must be > 0
737  */
738 static void utf16_to_ascii(char *buf, int len) {
739   int i;
740   if (len <= 0) return;
741   for (i = 0; i < len / 2; i++)
742     buf[i] = buf[i * 2];
743   buf[i] = 0; // just in case
744 }
745
746 static play_tree_t *parse_nsc(play_tree_parser_t* p) {
747   char *line, *addr = NULL, *url, *unicast_url = NULL;
748   int port = 0;
749   play_tree_t *entry = NULL;
750
751   mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying nsc playlist...\n");
752   while((line = play_tree_parser_get_line(p)) != NULL) {
753     strstrip(line);
754     if(!line[0]) // Ignore empties
755       continue;
756     if (strncasecmp(line,"[Address]", 9) == 0)
757       break; // nsc header found
758     else
759       return NULL;
760   }
761   mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected nsc playlist format\n");
762   play_tree_parser_stop_keeping(p);
763   while ((line = play_tree_parser_get_line(p)) != NULL) {
764     strstrip(line);
765     if (!line[0])
766       continue;
767     if (strncasecmp(line, "Unicast URL=", 12) == 0) {
768       int len = decode_nsc_base64(&line[12], &unicast_url);
769       if (len <= 0)
770         mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported Unicast URL encoding\n");
771       else
772         utf16_to_ascii(unicast_url, len);
773     } else if (strncasecmp(line, "IP Address=", 11) == 0) {
774       int len = decode_nsc_base64(&line[11], &addr);
775       if (len <= 0)
776         mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported IP Address encoding\n");
777       else
778         utf16_to_ascii(addr, len);
779     } else if (strncasecmp(line, "IP Port=", 8) == 0) {
780       port = strtol(&line[8], NULL, 0);
781     }
782   }
783
784   if (unicast_url)
785     url = strdup(unicast_url);
786   else if (addr && port) {
787     url = malloc(strlen(addr) + 7 + 20 + 1);
788     sprintf(url, "http://%s:%i", addr, port);
789   } else
790    goto out;
791
792   entry = play_tree_new();
793   play_tree_add_file(entry, url);
794   free(url);
795 out:
796   free(addr);
797   free(unicast_url);
798   return entry;
799 }
800
801 play_tree_t*
802 parse_playtree(stream_t *stream, int forced) {
803   play_tree_parser_t* p;
804   play_tree_t* ret;
805
806 #ifdef MP_DEBUG
807   assert(stream != NULL);
808 #endif
809
810   p = play_tree_parser_new(stream,0);
811   if(!p)
812     return NULL;
813
814   ret = play_tree_parser_get_play_tree(p, forced);
815   play_tree_parser_free(p);
816
817   return ret;
818 }
819
820 static void
821 play_tree_add_basepath(play_tree_t* pt, char* bp) {
822   int i,bl = strlen(bp),fl;
823
824   if(pt->child) {
825     play_tree_t* i;
826     for(i = pt->child ; i != NULL ; i = i->next)
827       play_tree_add_basepath(i,bp);
828     return;
829   }
830
831   if(!pt->files)
832     return;
833
834   for(i = 0 ; pt->files[i] != NULL ; i++) {
835     fl = strlen(pt->files[i]);
836     // if we find a full unix path, url:// or X:\ at the beginning,
837     // don't mangle it.
838     if(fl <= 0 || strstr(pt->files[i],"://") || (strstr(pt->files[i],":\\") == pt->files[i] + 1) || (pt->files[i][0] == '/') )
839       continue;
840     // if the path begins with \ then prepend drive letter to it.
841     if (pt->files[i][0] == '\\') {
842       if (pt->files[i][1] == '\\')
843         continue;
844       pt->files[i] = realloc(pt->files[i], 2 + fl + 1);
845       memmove(pt->files[i] + 2,pt->files[i],fl+1);
846       memcpy(pt->files[i],bp,2);
847       continue;
848     }
849     pt->files[i] = realloc(pt->files[i], bl + fl + 1);
850     memmove(pt->files[i] + bl,pt->files[i],fl+1);
851     memcpy(pt->files[i],bp,bl);
852   }
853 }
854
855 // Wrapper for play_tree_add_basepath (add base path from file)
856 void play_tree_add_bpf(play_tree_t* pt, char* filename)
857 {
858   char *ls, *file;
859
860   if (pt && filename)
861   {
862     file = strdup(filename);
863     if (file)
864     {
865       ls = strrchr(file,'/');
866       if(!ls) ls = strrchr(file,'\\');
867       if(ls) {
868         ls[1] = '\0';
869         play_tree_add_basepath(pt,file);
870       }
871       free(file);
872     }
873   }
874 }
875
876 play_tree_t*
877 parse_playlist_file(char* file) {
878   stream_t *stream;
879   play_tree_t* ret;
880   int f=DEMUXER_TYPE_PLAYLIST;
881
882   stream = open_stream(file,0,&f);
883
884   if(!stream) {
885     mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Error while opening playlist file %s: %s\n",file,strerror(errno));
886     return NULL;
887   }
888
889   mp_msg(MSGT_PLAYTREE,MSGL_V,"Parsing playlist file %s...\n",file);
890
891   ret = parse_playtree(stream,1);
892   free_stream(stream);
893
894   play_tree_add_bpf(ret, file);
895
896   return ret;
897
898 }
899
900
901 play_tree_parser_t*
902 play_tree_parser_new(stream_t* stream,int deep) {
903   play_tree_parser_t* p;
904
905   p = calloc(1,sizeof(play_tree_parser_t));
906   if(!p)
907     return NULL;
908   p->stream = stream;
909   p->deep = deep;
910   p->keep = 1;
911
912   return p;
913
914 }
915
916 void
917 play_tree_parser_free(play_tree_parser_t* p) {
918
919 #ifdef MP_DEBUG
920   assert(p != NULL);
921 #endif
922
923   free(p->buffer);
924   free(p->line);
925   free(p);
926 }
927
928 play_tree_t*
929 play_tree_parser_get_play_tree(play_tree_parser_t* p, int forced) {
930   play_tree_t* tree = NULL;
931
932 #ifdef MP_DEBUG
933   assert(p != NULL);
934 #endif
935
936
937   while(play_tree_parser_get_line(p) != NULL) {
938     play_tree_parser_reset(p);
939
940     tree = parse_asx(p);
941     if(tree) break;
942     play_tree_parser_reset(p);
943
944     tree = parse_pls(p);
945     if(tree) break;
946     play_tree_parser_reset(p);
947
948     tree = parse_m3u(p);
949     if(tree) break;
950     play_tree_parser_reset(p);
951
952     tree = parse_ref_ini(p);
953     if(tree) break;
954     play_tree_parser_reset(p);
955
956     tree = parse_smil(p);
957     if(tree) break;
958     play_tree_parser_reset(p);
959
960     tree = parse_nsc(p);
961     if(tree) break;
962     play_tree_parser_reset(p);
963
964     // Here come the others formats ( textplain must stay the last one )
965     if (forced)
966     {
967       tree = parse_textplain(p);
968       if(tree) break;
969     }
970     break;
971   }
972
973   if(tree)
974     mp_msg(MSGT_PLAYTREE,MSGL_V,"Playlist successfully parsed\n");
975   else
976     mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_ERR:MSGL_V),"Error while parsing playlist\n");
977
978   if(tree)
979     tree = play_tree_cleanup(tree);
980
981   if(!tree) mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_WARN:MSGL_V),"Warning: empty playlist\n");
982
983   return tree;
984 }