]> git.sesse.net Git - vlc/blob - modules/misc/playlist/xspf.c
Merge author/artist
[vlc] / modules / misc / playlist / xspf.c
1 /******************************************************************************
2  * xspf.c : XSPF playlist export functions
3  ******************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Daniel Stränger <vlc at schmaller dot de>
8  *          Yoann Peronneau <yoann@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *******************************************************************************/
24
25 /**
26  * \file modules/misc/playlist/xspf.c
27  * \brief XSPF playlist export functions
28  */
29 #include <stdio.h>
30 #include <vlc/vlc.h>
31 #include <vlc/intf.h>
32
33 #include "vlc_meta.h"
34 #include "vlc_strings.h"
35 #include "charset.h"
36 #include "xspf.h"
37
38 /**
39  * \brief Prints the XSPF header to file, writes each item by xspf_export_item()
40  * and closes the open xml elements
41  * \param p_this the VLC playlist object
42  * \return VLC_SUCCESS if some memory is available, otherwise VLC_ENONMEM
43  */
44 int E_(xspf_export_playlist)( vlc_object_t *p_this )
45 {
46     const playlist_t *p_playlist = (playlist_t *)p_this;
47     const playlist_export_t *p_export =
48         (playlist_export_t *)p_playlist->p_private;
49     int               i, i_count;
50     char             *psz_temp;
51     playlist_item_t  *p_node = p_export->p_root;
52
53     /* write XSPF XML header */
54     fprintf( p_export->p_file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
55     fprintf( p_export->p_file,
56              "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n" );
57
58     if( !p_node ) return VLC_SUCCESS;
59
60     /* save name of the playlist node */
61     psz_temp = convert_xml_special_chars( p_node->p_input->psz_name );
62     if( *psz_temp )
63     {
64         fprintf(  p_export->p_file, "\t<title>%s</title>\n", psz_temp );
65     }
66     free( psz_temp );
67
68     /* save location of the playlist node */
69     psz_temp = assertUTF8URI( p_export->psz_filename );
70     if( psz_temp && *psz_temp )
71     {
72         fprintf( p_export->p_file, "\t<location>%s</location>\n", psz_temp );
73         free( psz_temp );
74     }
75
76     /* export all items in a flat format */
77     fprintf( p_export->p_file, "\t<trackList>\n" );
78     i_count = 0;
79     for( i = 0; i < p_node->i_children; i++ )
80     {
81         xspf_export_item( p_node->pp_children[i], p_export->p_file,
82                           &i_count );
83     }
84     fprintf( p_export->p_file, "\t</trackList>\n" );
85
86     /* export the tree structure in <extension> */
87     fprintf( p_export->p_file, "\t<extension>\n" );
88     i_count = 0;
89     for( i = 0; i < p_node->i_children; i++ )
90     {
91         xspf_extension_item( p_node->pp_children[i], p_export->p_file,
92                              &i_count );
93     }
94     fprintf( p_export->p_file, "\t</extension>\n" );
95
96     /* close the header elements */
97     fprintf( p_export->p_file, "</playlist>\n" );
98
99     return VLC_SUCCESS;
100 }
101
102 /**
103  * \brief exports one item to file or traverse if item is a node
104  * \param p_item playlist item to export
105  * \param p_file file to write xml-converted item to
106  * \param p_i_count counter for track identifiers
107  */
108 static void xspf_export_item( playlist_item_t *p_item, FILE *p_file,
109                               int *p_i_count )
110 {
111     char *psz;
112     char *psz_temp;
113
114     if( !p_item ) return;
115
116     /* if we get a node here, we must traverse it */
117     if( p_item->i_children > 0 )
118     {
119         int i;
120         for( i = 0; i < p_item->i_children; i++ )
121         {
122             xspf_export_item( p_item->pp_children[i], p_file, p_i_count );
123         }
124         return;
125     }
126
127     /* don't write empty nodes */
128     if( p_item->i_children == 0 )
129     {
130         return;
131     }
132
133     /* leaves can be written directly */
134     fprintf( p_file, "\t\t<track>\n" );
135
136     /* print identifier and increase the counter */
137     fprintf( p_file, "\t\t\t<identifier>%i</identifier>\n", *p_i_count );
138     ( *p_i_count )++;
139
140     /* -> the location */
141     if( p_item->p_input->psz_uri && *p_item->p_input->psz_uri )
142     {
143         psz = assertUTF8URI( p_item->p_input->psz_uri );
144         fprintf( p_file, "\t\t\t<location>%s</location>\n", psz );
145         free( psz );
146     }
147
148     /* -> the name/title (only if different from uri)*/
149     if( p_item->p_input->psz_name &&
150         p_item->p_input->psz_uri &&
151         strcmp( p_item->p_input->psz_uri, p_item->p_input->psz_name ) )
152     {
153         psz_temp = convert_xml_special_chars( p_item->p_input->psz_name );
154         if( *psz_temp )
155             fprintf( p_file, "\t\t\t<title>%s</title>\n", psz_temp );
156         free( psz_temp );
157     }
158
159     if( p_item->p_input->p_meta == NULL )
160     {
161         goto xspfexportitem_end;
162     }
163
164     /* -> the artist/creator */
165     psz = p_item->p_input->p_meta->psz_artist ?
166                         strdup( p_item->p_input->p_meta->psz_artist ):
167                         strdup( "" );
168     if( psz && !*psz )
169     {
170         free( psz );
171         psz = NULL;
172     }
173     if( !psz )
174     {
175         psz = p_item->p_input->p_meta->psz_artist ?
176                         strdup( p_item->p_input->p_meta->psz_artist ):
177                         strdup( "" );
178     }
179     psz_temp = convert_xml_special_chars( psz );
180     if( psz ) free( psz );
181     if( *psz_temp )
182     {
183         fprintf( p_file, "\t\t\t<creator>%s</creator>\n", psz_temp );
184     }
185     free( psz_temp );
186
187     /* -> the album */
188     psz = p_item->p_input->p_meta->psz_album ?
189                         strdup( p_item->p_input->p_meta->psz_album ):
190                         strdup( "" );
191     psz_temp = convert_xml_special_chars( psz );
192     if( psz ) free( psz );
193     if( *psz_temp )
194     {
195         fprintf( p_file, "\t\t\t<album>%s</album>\n", psz_temp );
196     }
197     free( psz_temp );
198
199     /* -> the track number */
200     psz = p_item->p_input->p_meta->psz_tracknum ?
201                         strdup( p_item->p_input->p_meta->psz_tracknum ):
202                         strdup( "" );
203     if( psz )
204     {
205         if( *psz )
206         {
207             fprintf( p_file, "\t\t\t<trackNum>%i</trackNum>\n", atoi( psz ) );
208         }
209         free( psz );
210     }
211
212 xspfexportitem_end:
213     /* -> the duration */
214     if( p_item->p_input->i_duration > 0 )
215     {
216         fprintf( p_file, "\t\t\t<duration>%ld</duration>\n",
217                  (long)(p_item->p_input->i_duration / 1000) );
218     }
219
220     fprintf( p_file, "\t\t</track>\n" );
221
222     return;
223 }
224
225 /**
226  * \brief exports one item in extension to file and traverse if item is a node
227  * \param p_item playlist item to export
228  * \param p_file file to write xml-converted item to
229  * \param p_i_count counter for track identifiers
230  */
231 static void xspf_extension_item( playlist_item_t *p_item, FILE *p_file,
232                                  int *p_i_count )
233 {
234     if( !p_item ) return;
235
236     /* if we get a node here, we must traverse it */
237     if( p_item->i_children >= 0 )
238     {
239         int i;
240         char *psz_temp;
241         psz_temp = convert_xml_special_chars( p_item->p_input->psz_name );
242         fprintf( p_file, "\t\t<node title=\"%s\">\n",
243                  *psz_temp ? p_item->p_input->psz_name : "" );
244         free( psz_temp );
245
246         for( i = 0; i < p_item->i_children; i++ )
247         {
248             xspf_extension_item( p_item->pp_children[i], p_file, p_i_count );
249         }
250
251         fprintf( p_file, "\t\t</node>\n" );
252         return;
253     }
254
255
256     /* print leaf and increase the counter */
257     fprintf( p_file, "\t\t\t<item href=\"%i\" />\n", *p_i_count );
258     ( *p_i_count )++;
259
260     return;
261 }
262
263 /**
264  * \param psz_name the location of the media ressource (e.g. local file,
265  *        device, network stream, etc.)
266  * \return a new char buffer which asserts that the location is valid UTF-8
267  *         and a valid URI
268  * \note the returned buffer must be freed, when it isn't used anymore
269  */
270 static char *assertUTF8URI( char *psz_name )
271 {
272     char *psz_ret = NULL;              /**< the new result buffer to return */
273     char *psz_s = NULL, *psz_d = NULL; /**< src & dest pointers for URI conversion */
274     vlc_bool_t b_name_is_uri = VLC_FALSE;
275
276     if( !psz_name || !*psz_name )
277         return NULL;
278
279     /* check that string is valid UTF-8 */
280     /* XXX: Why do we even need to do that ? (all strings in core are UTF-8 encoded */
281     if( !( psz_s = EnsureUTF8( psz_name ) ) )
282         return NULL;
283
284     /* max. 3x for URI conversion (percent escaping) and
285        8 bytes for "file://" and NULL-termination */
286     psz_ret = (char *)malloc( sizeof(char)*strlen(psz_name)*6*3+8 );
287     if( !psz_ret )
288         return NULL;
289
290     /** \todo check for a valid scheme part preceding the colon */
291     if( strchr( psz_s, ':' ) )
292     {
293         psz_d = psz_ret;
294         b_name_is_uri = VLC_TRUE;
295     }
296     /* assume "file" scheme if no scheme-part is included */
297     else
298     {
299         strcpy( psz_ret, "file://" );
300         psz_d = psz_ret + 7;
301     }
302
303     while( *psz_s )
304     {
305         /* percent-encode all non-ASCII and the XML special characters and the percent sign itself */
306         if( *psz_s & B10000000 ||
307             *psz_s == '<' ||
308             *psz_s == '>' ||
309             *psz_s == '&' ||
310             *psz_s == ' ' ||
311             ( *psz_s == '%' && !b_name_is_uri ) )
312         {
313             *psz_d++ = '%';
314             *psz_d++ = hexchars[(*psz_s >> 4) & B00001111];
315             *psz_d++ = hexchars[*psz_s & B00001111];
316         }
317         else
318         {
319             *psz_d++ = *psz_s;
320         }
321
322         psz_s++;
323     }
324     *psz_d = '\0';
325
326     return (char *)realloc( psz_ret, sizeof(char)*strlen( psz_ret ) + 1 );
327 }