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 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_demux.h>
38 #include <vlc_charset.h>
51 /*****************************************************************************
53 *****************************************************************************/
54 static int Demux( demux_t
*p_demux
);
55 static int Control( demux_t
*p_demux
, int i_query
, va_list args
);
57 static int StoreString( demux_t
*p_demux
, char **ppsz_string
,
58 const char *psz_source_start
,
59 const char *psz_source_end
)
61 demux_sys_t
*p_sys
= p_demux
->p_sys
;
62 unsigned len
= psz_source_end
- psz_source_start
;
66 char *buf
= *ppsz_string
= malloc ((len
* (1 + !p_sys
->b_utf8
)) + 1);
72 memcpy (buf
, psz_source_start
, len
);
73 (*ppsz_string
)[len
] = '\0';
74 EnsureUTF8 (*ppsz_string
);
78 /* Latin-1 -> UTF-8 */
79 for (unsigned i
= 0; i
< len
; i
++)
81 unsigned char c
= psz_source_start
[i
];
84 *buf
++ = 0xc0 | (c
>> 6);
85 *buf
++ = 0x80 | (c
& 0x3f);
92 buf
= realloc (*ppsz_string
, buf
- *ppsz_string
);
99 static char *SkipBlanks(char *s
, size_t i_strlen
)
101 while( i_strlen
> 0 ) {
118 static int ParseTime(char *s
, size_t i_strlen
)
120 // need to parse hour:minutes:sec.fraction string
123 const char *end
= s
+ i_strlen
;
124 // skip leading spaces if any
125 s
= SkipBlanks(s
, i_strlen
);
128 while( (s
< end
) && isdigit(*s
) )
130 int newval
= val
*10 + (*s
- '0');
141 s
= SkipBlanks(s
, end
-s
);
145 s
= SkipBlanks(s
, end
-s
);
146 result
= result
* 60;
148 while( (s
< end
) && isdigit(*s
) )
150 int newval
= val
*10 + (*s
- '0');
161 s
= SkipBlanks(s
, end
-s
);
165 s
= SkipBlanks(s
, end
-s
);
166 result
= result
* 60;
168 while( (s
< end
) && isdigit(*s
) )
170 int newval
= val
*10 + (*s
- '0');
181 // TODO: one day, we may need to parse fraction for sub-second resolution
187 /*****************************************************************************
188 * Import_ASX: main import function
189 *****************************************************************************/
190 int Import_ASX( vlc_object_t
*p_this
)
192 demux_t
*p_demux
= (demux_t
*)p_this
;
193 const uint8_t *p_peek
;
194 CHECK_PEEK( p_peek
, 10 );
196 // skip over possible leading empty lines and empty spaces
197 p_peek
= (uint8_t *)SkipBlanks((char *)p_peek
, 6);
199 if( POKE( p_peek
, "<asx", 4 ) || demux_IsPathExtension( p_demux
, ".asx" ) ||
200 demux_IsPathExtension( p_demux
, ".wax" ) || demux_IsPathExtension( p_demux
, ".wvx" ) ||
201 demux_IsForced( p_demux
, "asx-open" ) )
208 STANDARD_DEMUX_INIT_MSG( "found valid ASX playlist" );
209 p_demux
->p_sys
->psz_prefix
= FindPrefix( p_demux
);
210 p_demux
->p_sys
->psz_data
= NULL
;
211 p_demux
->p_sys
->i_data_len
= -1;
212 p_demux
->p_sys
->b_utf8
= false;
213 p_demux
->p_sys
->b_skip_ads
=
214 var_InheritInteger( p_demux
, "playlist-skip-ads" );
219 /*****************************************************************************
220 * Deactivate: frees unused data
221 *****************************************************************************/
222 void Close_ASX( vlc_object_t
*p_this
)
224 demux_t
*p_demux
= (demux_t
*)p_this
;
225 demux_sys_t
*p_sys
= p_demux
->p_sys
;
227 free( p_sys
->psz_prefix
);
228 free( p_sys
->psz_data
);
232 static int Demux( demux_t
*p_demux
)
234 demux_sys_t
*p_sys
= p_demux
->p_sys
;
235 char *psz_parse
= NULL
;
236 char *psz_backup
= NULL
;
237 bool b_entry
= false;
238 input_item_t
*p_current_input
= GetCurrentItem(p_demux
);
241 if( p_sys
->i_data_len
< 0 )
244 p_sys
->i_data_len
= stream_Size( p_demux
->s
) + 1; /* This is a cheat to prevent unnecessary realloc */
245 if( p_sys
->i_data_len
<= 0 || p_sys
->i_data_len
> 16384 ) p_sys
->i_data_len
= 1024;
246 p_sys
->psz_data
= xmalloc( p_sys
->i_data_len
+1);
248 /* load the complete file */
251 int i_read
= stream_Read( p_demux
->s
, &p_sys
->psz_data
[i_pos
], p_sys
->i_data_len
- i_pos
);
252 p_sys
->psz_data
[i_pos
+ i_read
] = '\0';
254 if( i_read
< p_sys
->i_data_len
- i_pos
) break; /* Done */
257 p_sys
->i_data_len
<<= 1 ;
258 p_sys
->psz_data
= xrealloc( p_sys
->psz_data
,
259 p_sys
->i_data_len
* sizeof( char * ) + 1 );
261 if( p_sys
->i_data_len
<= 0 ) return -1;
264 input_item_node_t
*p_subitems
= input_item_node_Create( p_current_input
);
266 psz_parse
= p_sys
->psz_data
;
267 /* Find first element */
268 if( ( psz_parse
= strcasestr( psz_parse
, "<ASX" ) ) )
271 char *psz_string
= NULL
;
274 char *psz_base_asx
= NULL
;
275 char *psz_title_asx
= NULL
;
276 char *psz_artist_asx
= NULL
;
277 char *psz_copyright_asx
= NULL
;
278 char *psz_moreinfo_asx
= NULL
;
279 char *psz_abstract_asx
= NULL
;
281 char *psz_base_entry
= NULL
;
282 char *psz_title_entry
= NULL
;
283 char *psz_artist_entry
= NULL
;
284 char *psz_copyright_entry
= NULL
;
285 char *psz_moreinfo_entry
= NULL
;
286 char *psz_abstract_entry
= NULL
;
287 int i_entry_count
= 0;
288 bool b_skip_entry
= false;
290 char *psz_href
= NULL
;
294 psz_parse
= strcasestr( psz_parse
, ">" );
296 /* counter for single ad item */
297 input_item_t
*uniq_entry_ad_backup
= NULL
;
298 int i_inserted_entries
= 0;
300 while( psz_parse
&& ( psz_parse
= strcasestr( psz_parse
, "<" ) ) )
302 if( !strncasecmp( psz_parse
, "<!--", 4 ) )
304 /* this is a comment */
305 if( ( psz_parse
= strcasestr( psz_parse
, "-->" ) ) )
309 else if( !strncasecmp( psz_parse
, "<PARAM ", 7 ) )
311 bool b_encoding_flag
= false;
312 psz_parse
= SkipBlanks(psz_parse
+7, (unsigned)-1);
313 if( !strncasecmp( psz_parse
, "name", 4 ) )
315 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
317 psz_backup
= ++psz_parse
;
318 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
320 i_strlen
= psz_parse
-psz_backup
;
321 if( i_strlen
< 1 ) continue;
322 msg_Dbg( p_demux
, "param name strlen: %d", i_strlen
);
323 psz_string
= xmalloc( i_strlen
+ 1);
324 memcpy( psz_string
, psz_backup
, i_strlen
);
325 psz_string
[i_strlen
] = '\0';
326 msg_Dbg( p_demux
, "param name: %s", psz_string
);
327 b_encoding_flag
= !strcasecmp( psz_string
, "encoding" );
335 if( !strncasecmp( psz_parse
, "value", 5 ) )
337 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
339 psz_backup
= ++psz_parse
;
340 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
342 i_strlen
= psz_parse
-psz_backup
;
343 if( i_strlen
< 1 ) continue;
344 msg_Dbg( p_demux
, "param value strlen: %d", i_strlen
);
345 psz_string
= xmalloc( i_strlen
+1);
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
= xmalloc( i_strlen
+1);
471 memcpy( psz_string
, psz_backup
, i_strlen
);
472 psz_string
[i_strlen
] = '\0';
473 input_item_t
*p_input
;
474 p_input
= input_item_New( p_demux
, psz_string
, psz_title_asx
);
475 input_item_CopyOptions( p_current_input
, p_input
);
476 input_item_AddSubItem( p_current_input
, p_input
);
477 input_item_node_AppendItem( p_subitems
, 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?" );
510 /* An skip entry is an ad only if other entries exist without skip */
511 if( p_sys
->b_skip_ads
&& b_skip_entry
&& i_inserted_entries
!= 0 )
513 char *psz_current_input_name
= input_item_GetName( p_current_input
);
514 msg_Dbg( p_demux
, "skipped entry %d %s (%s)",
516 ( psz_title_entry
? psz_title_entry
: psz_current_input_name
), psz_href
);
517 free( psz_current_input_name
);
521 if( i_starttime
|| i_duration
)
525 if( asprintf(ppsz_options
+i_options
, ":start-time=%d", i_starttime
) == -1 )
526 *(ppsz_options
+i_options
) = NULL
;
532 if( asprintf(ppsz_options
+i_options
, ":stop-time=%d", i_starttime
+ i_duration
) == -1 )
533 *(ppsz_options
+i_options
) = NULL
;
539 /* create the new entry */
540 char *psz_current_input_name
= input_item_GetName( p_current_input
);
541 if( asprintf( &psz_name
, "%d %s", i_entry_count
, ( psz_title_entry
? psz_title_entry
: psz_current_input_name
) ) != -1 )
543 char *psz_mrl
= ProcessMRL( psz_href
, p_demux
->p_sys
->psz_prefix
);
544 p_entry
= input_item_NewExt( p_demux
, psz_mrl
, psz_name
,
545 i_options
, (const char * const *)ppsz_options
, VLC_INPUT_OPTION_TRUSTED
, -1 );
548 input_item_CopyOptions( p_current_input
, p_entry
);
551 psz_name
= ppsz_options
[--i_options
];
556 if( psz_title_entry
) input_item_SetTitle( p_entry
, psz_title_entry
);
557 if( psz_artist_entry
) input_item_SetArtist( p_entry
, psz_artist_entry
);
558 if( psz_copyright_entry
) input_item_SetCopyright( p_entry
, psz_copyright_entry
);
559 if( psz_moreinfo_entry
) input_item_SetURL( p_entry
, psz_moreinfo_entry
);
560 if( psz_abstract_entry
) input_item_SetDescription( p_entry
, psz_abstract_entry
);
562 i_inserted_entries
++;
563 if( p_sys
->b_skip_ads
&& b_skip_entry
)
565 // We put the entry as a backup for unique ad case
566 uniq_entry_ad_backup
= p_entry
;
570 if( uniq_entry_ad_backup
!= NULL
)
572 uniq_entry_ad_backup
= NULL
;
573 vlc_gc_decref( uniq_entry_ad_backup
);
575 input_item_AddSubItem( p_current_input
, p_entry
);
576 input_item_node_AppendItem( p_subitems
, p_entry
);
577 vlc_gc_decref( p_entry
);
580 free( psz_current_input_name
);
584 FREENULL( psz_href
);
585 FREENULL( psz_title_entry
);
586 FREENULL( psz_base_entry
);
587 FREENULL( psz_artist_entry
);
588 FREENULL( psz_copyright_entry
);
589 FREENULL( psz_moreinfo_entry
);
590 FREENULL( psz_abstract_entry
);
593 else if( !strncasecmp( psz_parse
, "<Entry", 6 ) )
595 char *psz_clientskip
;
599 msg_Err( p_demux
, "We already are in an entry section" );
604 psz_clientskip
= strcasestr( psz_parse
, "clientskip=\"no\"" );
605 psz_parse
= strcasestr( psz_parse
, ">" );
607 /* If clientskip was enabled ... this is an ad */
608 b_skip_entry
= (NULL
!= psz_clientskip
) && (psz_clientskip
< psz_parse
);
610 // init entry details
615 else if( !strncasecmp( psz_parse
, "<Ref ", 5 ) )
617 psz_parse
= SkipBlanks(psz_parse
+5, (unsigned)-1);
620 msg_Err( p_demux
, "A ref outside an entry section" );
624 if( !strncasecmp( psz_parse
, "HREF", 4 ) )
626 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
628 psz_backup
= ++psz_parse
;
629 psz_backup
= SkipBlanks(psz_backup
, (unsigned)-1);
630 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
633 i_strlen
= psz_parse
-psz_backup
;
634 if( i_strlen
< 1 ) continue;
638 /* we have allready one href in this entry, lets make new input from it and
639 continue with new href, don't free meta/options*/
640 input_item_t
*p_entry
= NULL
;
641 char *psz_name
= input_item_GetName( p_current_input
);
643 char *psz_mrl
= ProcessMRL( psz_href
, p_demux
->p_sys
->psz_prefix
);
644 p_entry
= input_item_NewExt( p_demux
, psz_mrl
, psz_name
,
645 0, NULL
, VLC_INPUT_OPTION_TRUSTED
, -1 );
647 input_item_CopyOptions( p_current_input
, p_entry
);
648 if( psz_title_entry
) input_item_SetTitle( p_entry
, psz_title_entry
);
649 if( psz_artist_entry
) input_item_SetArtist( p_entry
, psz_artist_entry
);
650 if( psz_copyright_entry
) input_item_SetCopyright( p_entry
, psz_copyright_entry
);
651 if( psz_moreinfo_entry
) input_item_SetURL( p_entry
, psz_moreinfo_entry
);
652 if( psz_abstract_entry
) input_item_SetDescription( p_entry
, psz_abstract_entry
);
653 input_item_AddSubItem( p_current_input
, p_entry
);
654 input_item_node_AppendItem( p_subitems
, p_entry
);
655 vlc_gc_decref( p_entry
);
659 psz_href
= xmalloc( i_strlen
+1);
660 memcpy( psz_href
, psz_backup
, i_strlen
);
661 psz_href
[i_strlen
] = '\0';
662 psz_tmp
= psz_href
+ (i_strlen
-1);
663 while( psz_tmp
>= psz_href
&&
664 ( *psz_tmp
== '\r' || *psz_tmp
== '\n' ) )
674 if( ( psz_parse
= strcasestr( psz_parse
, ">" ) ) )
678 else if( !strncasecmp( psz_parse
, "<starttime ", 11 ) )
680 psz_parse
= SkipBlanks(psz_parse
+11, (unsigned)-1);
683 msg_Err( p_demux
, "starttime outside an entry section" );
687 if( !strncasecmp( psz_parse
, "value", 5 ) )
689 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
691 psz_backup
= ++psz_parse
;
692 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
694 i_strlen
= psz_parse
-psz_backup
;
695 if( i_strlen
< 1 ) continue;
697 i_starttime
= ParseTime(psz_backup
, i_strlen
);
703 if( ( psz_parse
= strcasestr( psz_parse
, ">" ) ) )
707 else if( !strncasecmp( psz_parse
, "<duration ", 11 ) )
709 psz_parse
= SkipBlanks(psz_parse
+5, (unsigned)-1);
712 msg_Err( p_demux
, "duration outside an entry section" );
716 if( !strncasecmp( psz_parse
, "value", 5 ) )
718 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
720 psz_backup
= ++psz_parse
;
721 if( ( psz_parse
= strcasestr( psz_parse
, "\"" ) ) )
723 i_strlen
= psz_parse
-psz_backup
;
724 if( i_strlen
< 1 ) continue;
726 i_duration
= ParseTime(psz_backup
, i_strlen
);
732 if( ( psz_parse
= strcasestr( psz_parse
, ">" ) ) )
736 else if( !strncasecmp( psz_parse
, "</ASX", 5 ) )
738 if( psz_title_asx
) input_item_SetTitle( p_current_input
, psz_title_asx
);
739 if( psz_artist_asx
) input_item_SetArtist( p_current_input
, psz_artist_asx
);
740 if( psz_copyright_asx
) input_item_SetCopyright( p_current_input
, psz_copyright_asx
);
741 if( psz_moreinfo_asx
) input_item_SetURL( p_current_input
, psz_moreinfo_asx
);
742 if( psz_abstract_asx
) input_item_SetDescription( p_current_input
, psz_abstract_asx
);
743 FREENULL( psz_base_asx
);
744 FREENULL( psz_title_asx
);
745 FREENULL( psz_artist_asx
);
746 FREENULL( psz_copyright_asx
);
747 FREENULL( psz_moreinfo_asx
);
748 FREENULL( psz_abstract_asx
);
753 if ( uniq_entry_ad_backup
!= NULL
)
755 msg_Dbg( p_demux
, "added unique entry even if ad");
756 /* If ASX contains a unique entry, we add it, it is probably not an ad */
757 input_item_AddSubItem( p_current_input
, uniq_entry_ad_backup
);
758 input_item_node_AppendItem( p_subitems
, uniq_entry_ad_backup
);
759 vlc_gc_decref( uniq_entry_ad_backup
);
762 /* FIXME Unsupported elements */
771 input_item_AddSubItemTree( p_subitems
);
772 input_item_node_Delete( p_subitems
);
774 vlc_gc_decref(p_current_input
);
775 return 0; /* Needed for correct operation of go back */
778 static int Control( demux_t
*p_demux
, int i_query
, va_list args
)
780 VLC_UNUSED(p_demux
); VLC_UNUSED(i_query
); VLC_UNUSED(args
);