1 /*****************************************************************************
2  * svg.c : Put SVG on the video
3  *****************************************************************************
4  * Copyright (C) 2002, 2003 VLC authors and VideoLAN
5  * $Id: b1247af0ca26090f1e067065bf0adf3ca1086b05 $
6  *
7  * Authors: Olivier Aubert <oaubert@lisi.univ-lyon1.fr>
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  * Preamble
26  *****************************************************************************/
27 
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31 
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_fs.h>
35 #include <vlc_filter.h>
36 #include <vlc_subpicture.h>
37 #include <vlc_strings.h>
38 
39 #include <sys/types.h>
40 #include <unistd.h>
41 
42 #include <glib.h>
43 #include <glib/gstdio.h>
44 #include <glib-object.h>                                  /* g_object_unref( ) */
45 #include <librsvg/rsvg.h>
46 #include <cairo/cairo.h>
47 
48 /*****************************************************************************
49  * Local prototypes
50  *****************************************************************************/
51 static int  Create    ( vlc_object_t * );
52 static void Destroy   ( vlc_object_t * );
53 static int  RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
54                         subpicture_region_t *p_region_in,
55                         const vlc_fourcc_t * );
56 
57 struct filter_sys_t
58 {
59     char *psz_file_template;
60     const char *psz_token;
61 };
62 
63 /*****************************************************************************
64  * Module descriptor
65  *****************************************************************************/
66 
67 #define SVG_TEMPLATE_BODY_TOKEN   "<!--$SVGBODY$-->"
68 #define SVG_TEMPLATE_BODY_TOKEN_L 16
69 
70 #define TEMPLATE_TEXT N_( "SVG template file" )
71 #define TEMPLATE_LONGTEXT N_( "Location of a file holding a SVG template "\
72         "for automatic string conversion" )
73 
74 vlc_module_begin ()
75     set_category( CAT_INPUT )
76     set_subcategory( SUBCAT_INPUT_SCODEC )
77     set_capability( "text renderer", 99 )
78     add_shortcut( "svg" )
79     add_string( "svg-template-file", "", TEMPLATE_TEXT, TEMPLATE_LONGTEXT, true )
80     set_callbacks( Create, Destroy )
81 vlc_module_end ()
82 
83 static void svg_RescaletoFit  ( filter_t *, int *width, int *height, float * );
84 static picture_t * svg_RenderPicture ( filter_t *p_filter, const char * );
85 
svg_LoadTemplate(filter_t * p_filter)86 static void svg_LoadTemplate( filter_t *p_filter )
87 {
88     filter_sys_t *p_sys = p_filter->p_sys;
89     char *psz_template = NULL;
90     char *psz_filename = var_InheritString( p_filter, "svg-template-file" );
91     if( psz_filename && psz_filename[0] )
92     {
93         /* Read the template */
94         FILE *file = vlc_fopen( psz_filename, "rt" );
95         if( !file )
96         {
97             msg_Warn( p_filter, "SVG template file %s does not exist.",
98                                          psz_filename );
99         }
100         else
101         {
102             struct stat s;
103             if( fstat( fileno( file ), &s ) || ((signed)s.st_size) < 0 )
104             {
105                 msg_Err( p_filter, "SVG template invalid" );
106             }
107             else
108             {
109                 msg_Dbg( p_filter, "reading %ld bytes from template %s",
110                          (unsigned long)s.st_size, psz_filename );
111 
112                 psz_template = malloc( s.st_size + 1 );
113                 if( psz_template )
114                 {
115                     psz_template[ s.st_size ] = 0;
116                     ssize_t i_read = fread( psz_template, s.st_size, 1, file );
117                     if( i_read != 1 )
118                     {
119                         free( psz_template );
120                         psz_template = NULL;
121                     }
122                 }
123             }
124             fclose( file );
125         }
126     }
127     free( psz_filename );
128 
129     if( psz_template )
130     {
131         p_sys->psz_token = strstr( psz_template, SVG_TEMPLATE_BODY_TOKEN );
132         if( !p_sys->psz_token )
133         {
134             msg_Err( p_filter, "'%s' not found in SVG template", SVG_TEMPLATE_BODY_TOKEN );
135             free( psz_template );
136         }
137         else *((char*)p_sys->psz_token) = 0;
138     }
139 
140     p_sys->psz_file_template = psz_template;
141 }
142 
svg_GetDocument(filter_t * p_filter,int i_width,int i_height,const char * psz_body)143 static char *svg_GetDocument( filter_t *p_filter, int i_width, int i_height, const char *psz_body )
144 {
145     filter_sys_t *p_sys = p_filter->p_sys;
146     char *psz_result;
147     VLC_UNUSED(i_width);VLC_UNUSED(i_height);
148 
149     if( p_sys->psz_file_template )
150     {
151         if( asprintf( &psz_result, "%s%s%s",
152                       p_sys->psz_file_template,
153                       psz_body,
154                       &p_sys->psz_token[SVG_TEMPLATE_BODY_TOKEN_L] ) < 0 )
155             psz_result = NULL;
156     }
157     else
158     {
159         /* Either there was no file, or there was an error.
160            Use the default value */
161         const char *psz_temp = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>"
162                     "<svg preserveAspectRatio='xMinYMin meet'>" // viewBox='0 0 %d %d'>"
163                     "<rect fill='none' width='100%%' height='100%%'></rect>"
164                     "<text fill='white' font-family='sans-serif' font-size='32px'>%s</text>"
165                     "</svg>";
166         if( asprintf( &psz_result, psz_temp, /*i_width, i_height,*/ psz_body ) < 0 )
167             psz_result = NULL;
168     }
169 
170     return psz_result;
171 }
172 
173 /*****************************************************************************
174  * Create: allocates svg video thread output method
175  *****************************************************************************
176  * This function allocates and initializes a  vout method.
177  *****************************************************************************/
178 
Create(vlc_object_t * p_this)179 static int Create( vlc_object_t *p_this )
180 {
181     filter_t *p_filter = ( filter_t * )p_this;
182 
183     p_filter->p_sys = calloc( 1, sizeof(*p_filter->p_sys) );
184     if( !p_filter->p_sys )
185         return VLC_ENOMEM;
186 
187     p_filter->pf_render = RenderText;
188     svg_LoadTemplate( p_filter );
189 
190 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
191     g_type_init( );
192 #endif
193 
194     return VLC_SUCCESS;
195 }
196 
197 /*****************************************************************************
198  * Destroy: destroy Clone video thread output method
199  *****************************************************************************
200  * Clean up all data and library connections
201  *****************************************************************************/
Destroy(vlc_object_t * p_this)202 static void Destroy( vlc_object_t *p_this )
203 {
204     filter_t *p_filter = ( filter_t * )p_this;
205 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
206     rsvg_term();
207 #endif
208     free( p_filter->p_sys->psz_file_template );
209     free( p_filter->p_sys );
210 }
211 
svg_RescaletoFit(filter_t * p_filter,int * width,int * height,float * scale)212 static void svg_RescaletoFit( filter_t *p_filter, int *width, int *height, float *scale )
213 {
214     *scale = 1.0;
215 
216     if( *width > 0 && *height > 0 )
217     {
218         if( (unsigned)*width > p_filter->fmt_out.video.i_visible_width )
219             *scale = (1.0 * p_filter->fmt_out.video.i_visible_width / *width);
220 
221         if( (unsigned)*height > p_filter->fmt_out.video.i_visible_height )
222         {
223             float y_scale = (1.0 * p_filter->fmt_out.video.i_visible_height / *height);
224             if( y_scale < *scale )
225                 *scale = y_scale;
226         }
227 
228         *width *= *scale;
229         *height *= *scale;
230     }
231 }
232 
svg_RenderPicture(filter_t * p_filter,const char * psz_svgdata)233 static picture_t * svg_RenderPicture( filter_t *p_filter,
234                                       const char *psz_svgdata )
235 {
236     RsvgHandle *p_handle;
237     GError *error = NULL;
238 
239     p_handle = rsvg_handle_new_from_data( (const guint8 *)psz_svgdata,
240                                           strlen( psz_svgdata ), &error );
241     if( !p_handle )
242     {
243         msg_Err( p_filter, "error while rendering SVG: %s", error->message );
244         return NULL;
245     }
246 
247     RsvgDimensionData dim;
248     rsvg_handle_get_dimensions( p_handle, &dim );
249     float scale;
250     svg_RescaletoFit( p_filter, &dim.width, &dim.height, &scale );
251 
252     /* Create a new subpicture region */
253     video_format_t fmt;
254     video_format_Init( &fmt, VLC_CODEC_BGRA ); /* CAIRO_FORMAT_ARGB32 == VLC_CODEC_BGRA, go figure */
255     fmt.i_bits_per_pixel = 32;
256     fmt.i_chroma = VLC_CODEC_BGRA;
257     fmt.i_width = fmt.i_visible_width = dim.width;
258     fmt.i_height = fmt.i_visible_height = dim.height;
259 
260     picture_t *p_picture = picture_NewFromFormat( &fmt );
261     if( !p_picture )
262     {
263         video_format_Clean( &fmt );
264         g_object_unref( G_OBJECT( p_handle ) );
265         return NULL;
266     }
267     memset( p_picture->p[0].p_pixels, 0x00, p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
268 
269     cairo_surface_t* surface = cairo_image_surface_create_for_data( p_picture->p->p_pixels,
270                                                                     CAIRO_FORMAT_ARGB32,
271                                                                     fmt.i_width, fmt.i_height,
272                                                                     p_picture->p[0].i_pitch );
273     if( !surface )
274     {
275         g_object_unref( G_OBJECT( p_handle ) );
276         picture_Release( p_picture );
277         return NULL;
278     }
279 
280     cairo_t *cr = cairo_create( surface );
281     if( !cr )
282     {
283         msg_Err( p_filter, "error while creating cairo surface" );
284         cairo_surface_destroy( surface );
285         g_object_unref( G_OBJECT( p_handle ) );
286         picture_Release( p_picture );
287         return NULL;
288     }
289 
290     if( ! rsvg_handle_render_cairo( p_handle, cr ) )
291     {
292         msg_Err( p_filter, "error while rendering SVG" );
293         cairo_destroy( cr );
294         cairo_surface_destroy( surface );
295         g_object_unref( G_OBJECT( p_handle ) );
296         picture_Release( p_picture );
297         return NULL;
298     }
299 
300     cairo_destroy( cr );
301     cairo_surface_destroy( surface );
302     g_object_unref( G_OBJECT( p_handle ) );
303 
304     return p_picture;
305 }
306 
SegmentsToSVG(text_segment_t * p_segment,int i_height,int * pi_total_size)307 static char * SegmentsToSVG( text_segment_t *p_segment, int i_height, int *pi_total_size )
308 {
309     char *psz_result = NULL;
310 
311     i_height = 6 * i_height / 100;
312     *pi_total_size = 0;
313 
314     for( ; p_segment; p_segment = p_segment->p_next )
315     {
316         char *psz_prev = psz_result;
317         char *psz_encoded = vlc_xml_encode( p_segment->psz_text );
318         if( asprintf( &psz_result, "%s<tspan x='0' dy='%upx'>%s</tspan>\n",
319                                    (psz_prev) ? psz_prev : "",
320                                     i_height,
321                                     psz_encoded ) < 0 )
322             psz_result = NULL;
323         free( psz_prev );
324         free( psz_encoded );
325 
326         *pi_total_size += i_height;
327     }
328 
329     return psz_result;
330 }
331 
RenderText(filter_t * p_filter,subpicture_region_t * p_region_out,subpicture_region_t * p_region_in,const vlc_fourcc_t * p_chroma_list)332 static int RenderText( filter_t *p_filter, subpicture_region_t *p_region_out,
333                        subpicture_region_t *p_region_in,
334                        const vlc_fourcc_t *p_chroma_list )
335 {
336     /* Sanity check */
337     if( !p_region_in || !p_region_out || !p_region_in->p_text )
338         return VLC_EGENERIC;
339 
340     for( size_t i=0; p_chroma_list[i]; i++ )
341     {
342         if( p_chroma_list[i] == VLC_CODEC_BGRA )
343             break;
344         if( p_chroma_list[i] == 0 )
345             return VLC_EGENERIC;
346     }
347 
348     p_region_out->i_x = p_region_in->i_x;
349     p_region_out->i_y = p_region_in->i_y;
350 
351     unsigned i_width = p_filter->fmt_out.video.i_visible_width;
352     if( (unsigned) p_region_out->i_x <= i_width )
353         i_width -= p_region_out->i_x;
354 
355     unsigned i_height = p_filter->fmt_out.video.i_visible_height;
356     if( (unsigned) p_region_out->i_y <= i_height )
357         i_height -= p_region_out->i_y;
358 
359     if( i_height == 0 || i_width == 0 )
360         return VLC_EGENERIC;
361 
362     char *psz_svg;
363     /* Check if the data is SVG or pure text. In the latter case,
364        convert the text to SVG. FIXME: find a better test */
365     if( p_region_in->p_text && strstr( p_region_in->p_text->psz_text, "<svg" ) )
366     {
367         psz_svg = strdup( p_region_in->p_text->psz_text );
368     }
369     else
370     {
371         /* Data is text. Convert to SVG */
372         int i_total;
373         psz_svg = SegmentsToSVG( p_region_in->p_text, i_height, &i_total );
374         if( psz_svg )
375         {
376             char *psz_doc = svg_GetDocument( p_filter, i_width, i_total, psz_svg );
377             free( psz_svg );
378             psz_svg = psz_doc;
379         }
380     }
381 
382     if( !psz_svg )
383         return VLC_EGENERIC;
384 
385     picture_t *p_picture = svg_RenderPicture( p_filter, psz_svg );
386 
387     free( psz_svg );
388 
389     if (p_picture)
390     {
391         p_region_out->p_picture = p_picture;
392         video_format_Clean( &p_region_out->fmt );
393         video_format_Copy( &p_region_out->fmt, &p_picture->format );
394         return VLC_SUCCESS;
395     }
396     return VLC_EGENERIC;
397 }
398