1 /*****************************************************************************
2 * asx.c : ASX playlist format import
3 *****************************************************************************
4 * Copyright (C) 2005-2006 the VideoLAN team
7 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
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.
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.
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 *****************************************************************************/
24 /* See also: http://msdn.microsoft.com/library/en-us/wmplay10/mmp_sdk/windowsmediametafilereference.asp
27 /*****************************************************************************
29 *****************************************************************************/
36 #include <vlc_common.h>
37 #include <vlc_demux.h>
40 #include <vlc_charset.h>
43 #include <vlc_memory.h>
54 /*****************************************************************************
56 *****************************************************************************/
57 static int Demux( demux_t *p_demux);
58 static int Control( demux_t *p_demux, int i_query, va_list args );
60 static int StoreString( demux_t *p_demux, char **ppsz_string,
61 const char *psz_source_start,
62 const char *psz_source_end )
64 demux_sys_t *p_sys = p_demux->p_sys;
65 unsigned len = psz_source_end - psz_source_start;
69 char *buf = *ppsz_string = malloc ((len * (1 + !p_sys->b_utf8)) + 1);
75 memcpy (buf, psz_source_start, len);
76 (*ppsz_string)[len] = '\0';
77 EnsureUTF8 (*ppsz_string);
81 /* Latin-1 -> UTF-8 */
82 for (unsigned i = 0; i < len; i++)
84 unsigned char c = psz_source_start[i];
87 *buf++ = 0xc0 | (c >> 6);
88 *buf++ = 0x80 | (c & 0x3f);
95 buf = realloc (*ppsz_string, buf - *ppsz_string);
102 static char *SkipBlanks(char *s, size_t i_strlen )
104 while( i_strlen > 0 ) {
121 static int ParseTime(char *s, size_t i_strlen)
123 // need to parse hour:minutes:sec.fraction string
126 const char *end = s + i_strlen;
127 // skip leading spaces if any
128 s = SkipBlanks(s, i_strlen);
131 while( (s < end) && isdigit(*s) )
133 int newval = val*10 + (*s - '0');
144 s = SkipBlanks(s, end-s);
148 s = SkipBlanks(s, end-s);
149 result = result * 60;
151 while( (s < end) && isdigit(*s) )
153 int newval = val*10 + (*s - '0');
164 s = SkipBlanks(s, end-s);
168 s = SkipBlanks(s, end-s);
169 result = result * 60;
171 while( (s < end) && isdigit(*s) )
173 int newval = val*10 + (*s - '0');
184 // TODO: one day, we may need to parse fraction for sub-second resolution
190 /*****************************************************************************
191 * Import_ASX: main import function
192 *****************************************************************************/
193 int Import_ASX( vlc_object_t *p_this )
195 demux_t *p_demux = (demux_t *)p_this;
196 const uint8_t *p_peek;
197 CHECK_PEEK( p_peek, 10 );
199 // skip over possible leading empty lines and empty spaces
200 p_peek = (uint8_t *)SkipBlanks((char *)p_peek, 6);
202 if( POKE( p_peek, "<asx", 4 ) || demux_IsPathExtension( p_demux, ".asx" ) ||
203 demux_IsPathExtension( p_demux, ".wax" ) || demux_IsPathExtension( p_demux, ".wvx" ) ||
204 demux_IsForced( p_demux, "asx-open" ) )
211 STANDARD_DEMUX_INIT_MSG( "found valid ASX playlist" );
212 p_demux->p_sys->psz_prefix = FindPrefix( p_demux );
213 p_demux->p_sys->psz_data = NULL;
214 p_demux->p_sys->i_data_len = -1;
215 p_demux->p_sys->b_utf8 = false;
216 p_demux->p_sys->b_skip_ads = config_GetInt( p_demux, "playlist-skip-ads" );
221 /*****************************************************************************
222 * Deactivate: frees unused data
223 *****************************************************************************/
224 void Close_ASX( vlc_object_t *p_this )
226 demux_t *p_demux = (demux_t *)p_this;
227 demux_sys_t *p_sys = p_demux->p_sys;
229 free( p_sys->psz_prefix );
230 free( p_sys->psz_data );
234 static int Demux( demux_t *p_demux )
236 demux_sys_t *p_sys = p_demux->p_sys;
237 char *psz_parse = NULL;
238 char *psz_backup = NULL;
239 bool b_entry = false;
240 input_item_t *p_current_input = GetCurrentItem(p_demux);
243 if( p_sys->i_data_len < 0 )
246 p_sys->i_data_len = stream_Size( p_demux->s ) + 1; /* This is a cheat to prevent unnecessary realloc */
247 if( p_sys->i_data_len <= 0 || p_sys->i_data_len > 16384 ) p_sys->i_data_len = 1024;
248 p_sys->psz_data = malloc( p_sys->i_data_len +1);
249 assert( p_sys->psz_data );
251 /* load the complete file */
254 int i_read = stream_Read( p_demux->s, &p_sys->psz_data[i_pos], p_sys->i_data_len - i_pos );
255 p_sys->psz_data[i_pos + i_read] = '\0';
257 if( i_read < p_sys->i_data_len - i_pos ) break; /* Done */
260 p_sys->i_data_len <<= 1 ;
261 p_sys->psz_data = realloc_or_free( p_sys->psz_data,
262 p_sys->i_data_len * sizeof( char * ) + 1 );
263 assert( p_sys->psz_data );
265 if( p_sys->i_data_len <= 0 ) return -1;
268 psz_parse = p_sys->psz_data;
269 /* Find first element */
270 if( ( psz_parse = strcasestr( psz_parse, "<ASX" ) ) )
273 char *psz_string = NULL;
276 char *psz_base_asx = NULL;
277 char *psz_title_asx = NULL;
278 char *psz_artist_asx = NULL;
279 char *psz_copyright_asx = NULL;
280 char *psz_moreinfo_asx = NULL;
281 char *psz_abstract_asx = NULL;
283 char *psz_base_entry = NULL;
284 char *psz_title_entry = NULL;
285 char *psz_artist_entry = NULL;
286 char *psz_copyright_entry = NULL;
287 char *psz_moreinfo_entry = NULL;
288 char *psz_abstract_entry = NULL;
289 int i_entry_count = 0;
290 bool b_skip_entry = false;
292 char *psz_href = NULL;
296 psz_parse = strcasestr( psz_parse, ">" );
298 while( psz_parse && ( psz_parse = strcasestr( psz_parse, "<" ) ) )
300 if( !strncasecmp( psz_parse, "<!--", 4 ) )
302 /* this is a comment */
303 if( ( psz_parse = strcasestr( psz_parse, "-->" ) ) )
307 else if( !strncasecmp( psz_parse, "<PARAM ", 7 ) )
309 bool b_encoding_flag = false;
310 psz_parse = SkipBlanks(psz_parse+7, (unsigned)-1);
311 if( !strncasecmp( psz_parse, "name", 4 ) )
313 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
315 psz_backup = ++psz_parse;
316 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
318 i_strlen = psz_parse-psz_backup;
319 if( i_strlen < 1 ) continue;
320 msg_Dbg( p_demux, "param name strlen: %d", i_strlen);
321 psz_string = malloc( i_strlen + 1);
322 assert( psz_string );
323 memcpy( psz_string, psz_backup, i_strlen );
324 psz_string[i_strlen] = '\0';
325 msg_Dbg( p_demux, "param name: %s", psz_string);
326 b_encoding_flag = !strcasecmp( psz_string, "encoding" );
334 if( !strncasecmp( psz_parse, "value", 5 ) )
336 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
338 psz_backup = ++psz_parse;
339 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
341 i_strlen = psz_parse-psz_backup;
342 if( i_strlen < 1 ) continue;
343 msg_Dbg( p_demux, "param value strlen: %d", i_strlen);
344 psz_string = malloc( i_strlen +1);
345 assert( psz_string );
346 memcpy( psz_string, psz_backup, i_strlen );
347 psz_string[i_strlen] = '\0';
348 msg_Dbg( p_demux, "param value: %s", psz_string);
349 if( b_encoding_flag && !strcasecmp( psz_string, "utf-8" ) ) p_sys->b_utf8 = true;
356 if( ( psz_parse = strcasestr( psz_parse, "/>" ) ) )
360 else if( !strncasecmp( psz_parse, "<BANNER", 7 ) )
362 /* We skip this element */
363 if( ( psz_parse = strcasestr( psz_parse, "</BANNER>" ) ) )
367 else if( !strncasecmp( psz_parse, "<PREVIEWDURATION", 16 ) ||
368 !strncasecmp( psz_parse, "<LOGURL", 7 ) ||
369 !strncasecmp( psz_parse, "<Skin", 5 ) )
371 /* We skip this element */
372 if( ( psz_parse = strcasestr( psz_parse, "/>" ) ) )
376 else if( !strncasecmp( psz_parse, "<BASE ", 6 ) )
378 psz_parse = SkipBlanks(psz_parse+6, (unsigned)-1);
379 if( !strncasecmp( psz_parse, "HREF", 4 ) )
381 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
383 psz_backup = ++psz_parse;
384 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
386 StoreString( p_demux, (b_entry ? &psz_base_entry : &psz_base_asx), psz_backup, psz_parse );
392 if( ( psz_parse = strcasestr( psz_parse, "/>" ) ) )
396 else if( !strncasecmp( psz_parse, "<TITLE>", 7 ) )
398 psz_backup = psz_parse+=7;
399 if( ( psz_parse = strcasestr( psz_parse, "</TITLE>" ) ) )
401 StoreString( p_demux, (b_entry ? &psz_title_entry : &psz_title_asx), psz_backup, psz_parse );
406 else if( !strncasecmp( psz_parse, "<Author>", 8 ) )
408 psz_backup = psz_parse+=8;
409 if( ( psz_parse = strcasestr( psz_parse, "</Author>" ) ) )
411 StoreString( p_demux, (b_entry ? &psz_artist_entry : &psz_artist_asx), psz_backup, psz_parse );
416 else if( !strncasecmp( psz_parse, "<Copyright", 10 ) )
418 psz_backup = psz_parse+=11;
419 if( ( psz_parse = strcasestr( psz_parse, "</Copyright>" ) ) )
421 StoreString( p_demux, (b_entry ? &psz_copyright_entry : &psz_copyright_asx), psz_backup, psz_parse );
426 else if( !strncasecmp( psz_parse, "<MoreInfo ", 10 ) )
428 psz_parse = SkipBlanks(psz_parse+10, (unsigned)-1);
429 if( !strncasecmp( psz_parse, "HREF", 4 ) )
431 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
433 psz_backup = ++psz_parse;
434 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
436 StoreString( p_demux, (b_entry ? &psz_moreinfo_entry : &psz_moreinfo_asx), psz_backup, psz_parse );
442 if( ( psz_parse = strcasestr( psz_parse, "/>" ) ) )
444 else if( ( psz_parse = strcasestr( psz_parse, "</MoreInfo>") ) )
448 else if( !strncasecmp( psz_parse, "<ABSTRACT>", 10 ) )
450 psz_backup = psz_parse+=10;
451 if( ( psz_parse = strcasestr( psz_parse, "</ABSTRACT>" ) ) )
453 StoreString( p_demux, (b_entry ? &psz_abstract_entry : &psz_abstract_asx), psz_backup, psz_parse );
458 else if( !strncasecmp( psz_parse, "<EntryRef ", 10 ) )
460 psz_parse = SkipBlanks(psz_parse+10, (unsigned)-1);
461 if( !strncasecmp( psz_parse, "HREF", 4 ) )
463 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
465 psz_backup = ++psz_parse;
466 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
468 i_strlen = psz_parse-psz_backup;
469 if( i_strlen < 1 ) continue;
470 psz_string = malloc( i_strlen +1);
471 assert( psz_string );
472 memcpy( psz_string, psz_backup, i_strlen );
473 psz_string[i_strlen] = '\0';
474 input_item_t *p_input;
475 p_input = input_item_New( p_demux, psz_string, psz_title_asx );
476 input_item_CopyOptions( p_current_input, p_input );
477 input_item_AddSubItem( p_current_input, p_input );
478 vlc_gc_decref( p_input );
485 if( ( psz_parse = strcasestr( psz_parse, "/>" ) ) )
489 else if( !strncasecmp( psz_parse, "</Entry>", 8 ) )
491 input_item_t *p_entry = NULL;
492 char *psz_name = NULL;
494 char * ppsz_options[2];
497 /* add a new entry */
501 msg_Err( p_demux, "end of entry without start?" );
507 msg_Err( p_demux, "entry without href?" );
511 if( p_sys->b_skip_ads && b_skip_entry )
513 char *psz_current_input_name = input_item_GetName( p_current_input );
515 msg_Dbg( p_demux, "skipped entry %d %s (%s)",
517 ( psz_title_entry ? psz_title_entry : psz_current_input_name ), psz_href );
518 free( psz_current_input_name );
522 if( i_starttime || i_duration )
526 if( asprintf(ppsz_options+i_options, ":start-time=%d", i_starttime) == -1 )
527 *(ppsz_options+i_options) = NULL;
533 if( asprintf(ppsz_options+i_options, ":stop-time=%d", i_starttime + i_duration) == -1 )
534 *(ppsz_options+i_options) = NULL;
540 /* create the new entry */
541 char *psz_current_input_name = input_item_GetName( p_current_input );
542 if( asprintf( &psz_name, "%d %s", i_entry_count, ( psz_title_entry ? psz_title_entry : psz_current_input_name ) ) != -1 )
544 char *psz_mrl = ProcessMRL( psz_href, p_demux->p_sys->psz_prefix );
545 p_entry = input_item_NewExt( p_demux, psz_mrl, psz_name,
546 i_options, (const char * const *)ppsz_options, VLC_INPUT_OPTION_TRUSTED, -1 );
549 input_item_CopyOptions( p_current_input, p_entry );
552 psz_name = ppsz_options[--i_options];
557 if( psz_title_entry ) input_item_SetTitle( p_entry, psz_title_entry );
558 if( psz_artist_entry ) input_item_SetArtist( p_entry, psz_artist_entry );
559 if( psz_copyright_entry ) input_item_SetCopyright( p_entry, psz_copyright_entry );
560 if( psz_moreinfo_entry ) input_item_SetURL( p_entry, psz_moreinfo_entry );
561 if( psz_abstract_entry ) input_item_SetDescription( p_entry, psz_abstract_entry );
562 input_item_AddSubItem( p_current_input, p_entry );
563 vlc_gc_decref( p_entry );
565 free( psz_current_input_name );
569 FREENULL( psz_href );
570 FREENULL( psz_title_entry );
571 FREENULL( psz_base_entry );
572 FREENULL( psz_artist_entry );
573 FREENULL( psz_copyright_entry );
574 FREENULL( psz_moreinfo_entry );
575 FREENULL( psz_abstract_entry );
578 else if( !strncasecmp( psz_parse, "<Entry", 6 ) )
580 char *psz_clientskip;
584 msg_Err( p_demux, "We already are in an entry section" );
589 psz_clientskip = strcasestr( psz_parse, "clientskip=\"no\"" );
590 psz_parse = strcasestr( psz_parse, ">" );
592 /* If clientskip was enabled ... this is an ad */
593 b_skip_entry = (NULL != psz_clientskip) && (psz_clientskip < psz_parse);
595 // init entry details
600 else if( !strncasecmp( psz_parse, "<Ref ", 5 ) )
602 psz_parse = SkipBlanks(psz_parse+5, (unsigned)-1);
605 msg_Err( p_demux, "A ref outside an entry section" );
609 if( !strncasecmp( psz_parse, "HREF", 4 ) )
611 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
613 psz_backup = ++psz_parse;
614 psz_backup = SkipBlanks(psz_backup, (unsigned)-1);
615 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
618 i_strlen = psz_parse-psz_backup;
619 if( i_strlen < 1 ) continue;
623 /* we have allready one href in this entry, lets make new input from it and
624 continue with new href, don't free meta/options*/
625 input_item_t *p_entry = NULL;
626 char *psz_name = input_item_GetName( p_current_input );
628 char *psz_mrl = ProcessMRL( psz_href, p_demux->p_sys->psz_prefix );
629 p_entry = input_item_NewExt( p_demux, psz_mrl, psz_name,
630 0, NULL, VLC_INPUT_OPTION_TRUSTED, -1 );
632 input_item_CopyOptions( p_current_input, p_entry );
633 if( psz_title_entry ) input_item_SetTitle( p_entry, psz_title_entry );
634 if( psz_artist_entry ) input_item_SetArtist( p_entry, psz_artist_entry );
635 if( psz_copyright_entry ) input_item_SetCopyright( p_entry, psz_copyright_entry );
636 if( psz_moreinfo_entry ) input_item_SetURL( p_entry, psz_moreinfo_entry );
637 if( psz_abstract_entry ) input_item_SetDescription( p_entry, psz_abstract_entry );
638 input_item_AddSubItem( p_current_input, p_entry );
639 vlc_gc_decref( p_entry );
643 psz_href = malloc( i_strlen +1);
644 assert( psz_string );
645 memcpy( psz_href, psz_backup, i_strlen );
646 psz_href[i_strlen] = '\0';
647 psz_tmp = psz_href + (i_strlen-1);
648 while( psz_tmp >= psz_href &&
649 ( *psz_tmp == '\r' || *psz_tmp == '\n' ) )
659 if( ( psz_parse = strcasestr( psz_parse, ">" ) ) )
663 else if( !strncasecmp( psz_parse, "<starttime ", 11 ) )
665 psz_parse = SkipBlanks(psz_parse+11, (unsigned)-1);
668 msg_Err( p_demux, "starttime outside an entry section" );
672 if( !strncasecmp( psz_parse, "value", 5 ) )
674 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
676 psz_backup = ++psz_parse;
677 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
679 i_strlen = psz_parse-psz_backup;
680 if( i_strlen < 1 ) continue;
682 i_starttime = ParseTime(psz_backup, i_strlen);
688 if( ( psz_parse = strcasestr( psz_parse, ">" ) ) )
692 else if( !strncasecmp( psz_parse, "<duration ", 11 ) )
694 psz_parse = SkipBlanks(psz_parse+5, (unsigned)-1);
697 msg_Err( p_demux, "duration outside an entry section" );
701 if( !strncasecmp( psz_parse, "value", 5 ) )
703 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
705 psz_backup = ++psz_parse;
706 if( ( psz_parse = strcasestr( psz_parse, "\"" ) ) )
708 i_strlen = psz_parse-psz_backup;
709 if( i_strlen < 1 ) continue;
711 i_duration = ParseTime(psz_backup, i_strlen);
717 if( ( psz_parse = strcasestr( psz_parse, ">" ) ) )
721 else if( !strncasecmp( psz_parse, "</ASX", 5 ) )
723 if( psz_title_asx ) input_item_SetTitle( p_current_input, psz_title_asx );
724 if( psz_artist_asx ) input_item_SetArtist( p_current_input, psz_artist_asx );
725 if( psz_copyright_asx ) input_item_SetCopyright( p_current_input, psz_copyright_asx );
726 if( psz_moreinfo_asx ) input_item_SetURL( p_current_input, psz_moreinfo_asx );
727 if( psz_abstract_asx ) input_item_SetDescription( p_current_input, psz_abstract_asx );
728 FREENULL( psz_base_asx );
729 FREENULL( psz_title_asx );
730 FREENULL( psz_artist_asx );
731 FREENULL( psz_copyright_asx );
732 FREENULL( psz_moreinfo_asx );
733 FREENULL( psz_abstract_asx );
739 /* FIXME Unsupported elements */
747 vlc_gc_decref(p_current_input);
748 return 0; /* Needed for correct operation of go back */
751 static int Control( demux_t *p_demux, int i_query, va_list args )
753 VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);