]> git.sesse.net Git - vlc/blob - modules/codec/spudec/subtitler.c
* modules/codec/spudec/*: modified the spu decoder to handle text subtitles.
[vlc] / modules / codec / spudec / subtitler.c
1 /*****************************************************************************
2  * subtitler.c : subtitler font routines
3  *****************************************************************************
4  * Copyright (C) 1999, 2000 VideoLAN
5  *
6  * Authors: Andrew Flintham <amf@cus.org.uk>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  * 
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
21  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26 #include <stdlib.h>                                      /* malloc(), free() */
27 #include <string.h>                                    /* memcpy(), memset() */
28 #include <errno.h>                                                  /* errno */
29 #include <fcntl.h>                                                 /* open() */
30 #include <ctype.h>                                              /* toascii() */
31
32 #include <vlc/vlc.h>
33 #include <vlc/vout.h>
34 #include <vlc/decoder.h>
35
36 #ifdef HAVE_UNISTD_H
37 #   include <unistd.h>                                    /* read(), close() */
38 #endif
39
40 #include <sys/types.h>
41 #include <sys/stat.h>
42
43 #include "spudec.h"
44
45 /*****************************************************************************
46  * subtitler_line : internal structure for an individual line in a subtitle
47  *****************************************************************************/
48 typedef struct subtitler_line_s
49 {
50    struct subtitler_line_s *   p_next;
51    char *                      p_text;
52 } subtitler_line_t;
53
54 /*****************************************************************************
55  * Local prototypes
56  *****************************************************************************/
57 static uint16_t *PlotSubtitleLine( char *, subtitler_font_t *, int,
58                                    uint16_t * );
59 static void      DestroySPU      ( subpicture_t * );
60
61 /*****************************************************************************
62  * subtitler_LoadFont: load a run-length encoded font file into memory
63  *****************************************************************************
64  * RLE font files have the following format:
65  *
66  *     2 bytes : magic number: 0x36 0x05
67  *     1 byte  : font height in rows
68  *
69  *     then, per character:
70  *     1 byte  : character 
71  *     1 byte  : character width in pixels
72  *
73  *         then, per row:
74  *         1 byte  : length of row, in entries
75  *         
76  *             then, per entry
77  *             1 byte : colour
78  *             1 byte : number of pixels of that colour
79  *
80  *     to end:
81  *     1 byte : 0xff
82  *****************************************************************************/
83 subtitler_font_t* subtitler_LoadFont( vout_thread_t * p_vout,
84                                       const char * psz_name )
85 {
86     subtitler_font_t * p_font;
87
88     int                i;
89     int                i_file;
90     int                i_char;
91     int                i_length;
92     int                i_line;
93     int                i_total_length;
94
95     byte_t             pi_buffer[512];                        /* file buffer */
96
97     msg_Dbg( p_vout, "loading font '%s'", psz_name );
98
99     i_file = open( psz_name, O_RDONLY );
100
101     if( i_file == -1 )
102     {
103         msg_Err( p_vout, "can't open font file '%s' (%s)", psz_name,
104                          strerror(errno) );
105         return( NULL );
106     }
107
108     /* Read magick number */
109     if( read( i_file, pi_buffer, 2 ) != 2 )
110     {
111         msg_Err( p_vout, "unexpected end of font file '%s'", psz_name );
112         close( i_file );
113         return( NULL );
114     }
115     if( pi_buffer[0] != 0x36 || pi_buffer[1] != 0x05 )
116     {
117         msg_Err( p_vout, "file '%s' is not a font file", psz_name );
118         close( i_file );
119         return( NULL );
120     }
121
122     p_font = malloc( sizeof( subtitler_font_t ) );
123
124     if( p_font == NULL )
125     {
126         msg_Err( p_vout, "out of memory" );
127         close( i_file );
128         return NULL;
129     }
130
131     /* Read font height */
132     if( read( i_file, pi_buffer, 1 ) != 1 )
133     {
134         msg_Err( p_vout, "unexpected end of font file '%s'", psz_name );
135         free( p_font ); 
136         close( i_file );
137         return( NULL );
138     }
139     p_font->i_height = pi_buffer[0];
140
141     /* Initialise font character data */
142     for( i = 0; i < 256; i++ )
143     {
144         p_font->i_width[i] = 0;
145         p_font->i_memory[i] = 0;
146         p_font->p_offset[i] = NULL;
147         p_font->p_length[i] = NULL;
148     }
149
150     while(1)
151     {
152         /* Read character number */
153         if( read( i_file, pi_buffer, 1 ) != 1)
154         {
155             msg_Err( p_vout, "unexpected end of font file '%s'", psz_name );
156             close( i_file );
157             subtitler_UnloadFont( p_vout, p_font );
158             return( NULL );
159         }
160         i_char = pi_buffer[0];
161
162         /* Character 255 signals the end of the font file */
163         if(i_char == 255)
164         {
165             break;
166         }
167
168         /* Read character width */
169         if( read( i_file, pi_buffer, 1 ) != 1 )
170         {
171             msg_Err( p_vout, "unexpected end of font file '%s'", psz_name );
172             close( i_file );
173             subtitler_UnloadFont( p_vout, p_font );
174             return( NULL );
175         }
176         p_font->i_width[ i_char ] = pi_buffer[0];
177
178         p_font->p_length[ i_char ] = (int *) malloc(
179                                      sizeof(int) * p_font->i_height );
180         p_font->p_offset[ i_char ] = (uint16_t **) malloc(
181                                      sizeof(uint16_t *) * p_font->i_height);
182
183         if( p_font->p_length[ i_char] == NULL ||
184             p_font->p_offset[ i_char ] == NULL )
185         {
186             msg_Err( p_vout, "out of memory" );
187             close( i_file );
188             subtitler_UnloadFont( p_vout, p_font );
189             return NULL;
190         }
191         for( i_line=0; i_line < p_font->i_height; i_line ++ )
192         {
193             p_font->p_offset[ i_char ][ i_line ] = NULL;
194         }
195
196         i_total_length=0;
197         for( i_line = 0; i_line < p_font->i_height; i_line ++ )
198         {
199             /* Read line length */
200             if( read( i_file, pi_buffer, 1 ) != 1)
201             {
202                 msg_Err( p_vout, "unexpected end of font file '%s'", psz_name);
203                 subtitler_UnloadFont( p_vout, p_font );
204                 close( i_file );
205                 return( NULL );
206             }
207             i_length = pi_buffer[0];
208             p_font->p_length[ i_char ][ i_line ] = i_length;
209
210             i_total_length += i_length;
211
212             /* Read line RLE data */
213             if( read( i_file, pi_buffer, i_length*2 ) != i_length*2)
214             {
215                 msg_Err( p_vout, "unexpected end of font file '%s'", psz_name);
216                 subtitler_UnloadFont( p_vout, p_font );
217                 close( i_file );
218                 return( NULL );
219             }
220             p_font->p_offset[ i_char ][ i_line ] =
221                 (uint16_t *) malloc( sizeof( uint16_t ) * i_length );
222             if( p_font->p_offset[ i_char ][ i_line ] == NULL )
223             {
224                 msg_Err( p_vout, "out of memory" );
225                 close( i_file );
226                 subtitler_UnloadFont( p_vout, p_font );
227                 return NULL;
228             }
229             for( i = 0; i < i_length; i++ )
230             {
231                 *( p_font->p_offset[ i_char ][ i_line ] + i ) =
232                 (uint16_t) ( pi_buffer[ i * 2 ] +
233                              ( pi_buffer[ i * 2 + 1 ] << 2 ) );
234             }
235
236         }
237
238        /* Set total memory size of character */
239        p_font->i_memory[ i_char ] = i_total_length;
240         
241     }
242
243     close(i_file);
244
245     return p_font;
246 }
247
248 /*****************************************************************************
249  * subtitler_UnloadFont: unload a run-length encoded font file from memory
250  *****************************************************************************/
251 void subtitler_UnloadFont( vout_thread_t * p_vout, subtitler_font_t * p_font )
252 {
253     int i_char;
254     int i_line;
255
256     msg_Dbg( p_vout, "unloading font" );
257
258     if( p_font == NULL )
259     {
260         return;
261     }
262
263     for( i_char = 0; i_char < 256; i_char ++ )
264     {
265         if( p_font->p_offset[ i_char ] != NULL )
266         {
267             for( i_line = 0; i_line < p_font->i_height; i_line++ )
268             {
269                 if( p_font->p_offset[ i_char ][ i_line ] != NULL )
270                 {
271                     free( p_font->p_offset[ i_char ][ i_line ] );
272                 }
273             }
274             free( p_font->p_offset[ i_char ] );
275         }
276         if( p_font->p_length[ i_char ] != NULL )
277         {
278             free( p_font->p_length[ i_char ] );
279         }
280     }
281
282     free( p_font );
283 }
284
285 /*****************************************************************************
286  * subtitler_PlotSubtitle: create a subpicture containing the subtitle
287  *****************************************************************************/
288 void subtitler_PlotSubtitle ( vout_thread_t *p_vout , char *psz_subtitle,
289                               subtitler_font_t *p_font, mtime_t i_start,
290                               mtime_t i_stop )
291 {
292     subpicture_t     * p_spu;
293
294     int                i_x;
295     int                i_width;
296     int                i_lines;
297     int                i_longest_width;
298     int                i_total_length;
299     int                i_char;
300
301     uint16_t         * p_data;
302
303     char             * p_line_start;
304     char             * p_word_start;
305     char             * p_char;
306
307     subtitler_line_t * p_first_line;
308     subtitler_line_t * p_previous_line;
309     subtitler_line_t * p_line;
310
311     if( p_font == NULL )
312     {
313         msg_Err( p_vout, "attempt to use NULL font in subtitle" );
314         return;
315     }
316
317     p_first_line = NULL;
318     p_previous_line = NULL;
319
320     p_line_start = psz_subtitle;
321
322     while( *p_line_start != 0 )
323     {
324         i_width = 0;
325         p_word_start = p_line_start;
326         p_char = p_line_start;
327
328         while( *p_char != '\n' && *p_char != 0 )
329         {
330             i_width += p_font->i_width[ toascii( *p_char ) ]; 
331
332             if( i_width > p_vout->output.i_width )
333             {
334                 /* If the line has more than one word, break at the end of
335                    the previous one. If the line is one very long word,
336                    display as much as we can of it */
337                 if( p_word_start != p_line_start )
338                 {
339                     p_char=p_word_start;
340                 }
341                 break;
342             }
343
344             if( *p_char == ' ' )
345             {
346                 p_word_start = p_char+1;
347             }
348
349             p_char++;
350         }
351
352         p_line = malloc(sizeof(subtitler_line_t));
353
354         if( p_line == NULL )
355         {
356             msg_Err( p_vout, "out of memory" );
357             return;
358         }
359
360         if( p_first_line == NULL )
361         {
362             p_first_line = p_line;
363         }
364
365         if( p_previous_line != NULL )
366         {
367             p_previous_line->p_next = p_line;
368         }
369
370         p_previous_line = p_line;
371
372         p_line->p_next = NULL;
373
374         p_line->p_text = malloc(( p_char - p_line_start ) +1 );
375
376         if( p_line == NULL )
377         {
378             msg_Err( p_vout, "out of memory" );
379             return;
380         }
381
382         /* Copy only the part of the text that is in this line */
383         strncpy( p_line->p_text , p_line_start , p_char - p_line_start );
384         *( p_line->p_text + ( p_char - p_line_start )) = 0;
385
386         /* If we had to break a line because it was too long, ensure that
387            no characters are lost */
388         if( *p_char != '\n' && *p_char != 0 )
389         {
390             p_char--; 
391         }
392
393         p_line_start = p_char;
394         if( *p_line_start != 0 )
395         {
396             p_line_start ++;
397         }
398     }
399
400     i_lines = 0;
401     i_longest_width = 0;
402     i_total_length = 0;
403     p_line = p_first_line;
404
405     /* Find the width of the longest line, count the total number of lines,
406        and calculate the amount of memory we need to allocate for the RLE
407        data */
408     while( p_line != NULL )
409     {
410         i_lines++;
411         i_width = 0;
412         for( i_x = 0; i_x < strlen( p_line->p_text ); i_x++ )
413         { 
414             i_char = toascii(*(( p_line->p_text )+ i_x ));
415             i_width += p_font->i_width[ i_char ];
416             i_total_length += p_font->i_memory[ i_char ];
417         }
418         if(i_width > i_longest_width)
419         {
420             i_longest_width = i_width;
421         }
422         p_line = p_line->p_next;
423     }
424
425     /* Allow space for the padding bytes at either edge */
426     i_total_length += p_font->i_height * 2 * i_lines;
427
428     /* Allocate the subpicture internal data. */
429     p_spu = vout_CreateSubPicture( p_vout, MEMORY_SUBPICTURE );
430     if( p_spu == NULL )
431     {
432         return;
433     }
434
435     /* Rationale for the "p_spudec->i_rle_size * 4": we are going to
436      * expand the RLE stuff so that we won't need to read nibbles later
437      * on. This will speed things up a lot. Plus, we'll only need to do
438      * this stupid interlacing stuff once. */
439     p_spu->p_sys = malloc( sizeof( subpicture_sys_t )
440                             + i_total_length * sizeof(uint16_t) );
441     if( p_spu->p_sys == NULL )
442     {
443         vout_DestroySubPicture( p_vout, p_spu );
444         return;
445     }
446
447     /* Fill the p_spu structure */
448     p_spu->pf_render = E_(RenderSPU);
449     p_spu->pf_destroy = DestroySPU;
450     p_spu->p_sys->p_data = (uint8_t *)p_spu->p_sys + sizeof(subpicture_sys_t);
451
452     p_spu->i_start = i_start;
453     p_spu->i_stop = i_stop;
454  
455     p_spu->b_ephemer = i_stop ? VLC_FALSE : VLC_TRUE;
456
457     /* FIXME: Do we need these two? */
458     p_spu->p_sys->pi_offset[0] = 0;
459     p_spu->p_sys->pi_offset[1] = 0;
460
461     p_spu->p_sys->b_palette = 1;
462
463     /* Colour 0 is transparent */
464     p_spu->p_sys->pi_yuv[0][0] = 0xff;
465     p_spu->p_sys->pi_yuv[0][1] = 0x80;
466     p_spu->p_sys->pi_yuv[0][2] = 0x80;
467     p_spu->p_sys->pi_yuv[0][3] = 0x80;
468     p_spu->p_sys->pi_alpha[0] = 0x0;
469
470     /* Colour 1 is grey */
471     p_spu->p_sys->pi_yuv[1][0] = 0x80;
472     p_spu->p_sys->pi_yuv[1][1] = 0x80;
473     p_spu->p_sys->pi_yuv[1][2] = 0x80;
474     p_spu->p_sys->pi_yuv[1][3] = 0x80;
475     p_spu->p_sys->pi_alpha[1] = 0xf;
476
477     /* Colour 2 is white */
478     p_spu->p_sys->pi_yuv[2][0] = 0xff;
479     p_spu->p_sys->pi_yuv[2][1] = 0xff;
480     p_spu->p_sys->pi_yuv[2][2] = 0xff;
481     p_spu->p_sys->pi_yuv[2][3] = 0xff;
482     p_spu->p_sys->pi_alpha[2] = 0xf;
483
484     /* Colour 3 is black */
485     p_spu->p_sys->pi_yuv[3][0] = 0x00;
486     p_spu->p_sys->pi_yuv[3][1] = 0x00;
487     p_spu->p_sys->pi_yuv[3][2] = 0x00;
488     p_spu->p_sys->pi_yuv[3][3] = 0x00;
489     p_spu->p_sys->pi_alpha[3] = 0xf;
490
491     p_spu->p_sys->b_crop = VLC_FALSE;
492
493     p_spu->i_x = (p_vout->output.i_width - i_longest_width) / 2;
494     p_spu->i_y = p_vout->output.i_height - (p_font->i_height * i_lines); 
495     p_spu->i_width = i_longest_width;
496     p_spu->i_height = p_font->i_height*i_lines;
497
498     p_data = (uint16_t *)(p_spu->p_sys->p_data);
499
500     p_line = p_first_line;
501     while( p_line != NULL )
502     {
503         p_data = PlotSubtitleLine( p_line->p_text,
504                                    p_font, i_longest_width, p_data );
505         p_previous_line = p_line;
506         p_line = p_line->p_next;
507         free( p_previous_line->p_text );
508         free( p_previous_line );
509     }
510
511     /* SPU is finished - we can ask the video output to display it */
512     vout_DisplaySubPicture( p_vout, p_spu );
513
514 }
515
516 /*****************************************************************************
517  * PlotSubtitleLine: plot a single line of a subtitle
518  *****************************************************************************/
519 static uint16_t * PlotSubtitleLine ( char *psz_line, subtitler_font_t *p_font,
520                                      int i_total_width, uint16_t *p_data )
521 {
522     int   i_x;
523     int   i_y;
524     int   i_length;
525     int   i_line_width;
526     int   i_char;
527
528     uint16_t * p_rle;
529
530     i_line_width = 0;
531     for( i_x = 0; i_x< strlen( psz_line ); i_x++ )
532     {
533         i_line_width += p_font->i_width[ toascii( *( psz_line+i_x ) ) ]; }
534
535     for( i_y = 0; i_y < p_font->i_height; i_y++ )
536     {
537         /* Pad line to fit box */
538         if( i_line_width < i_total_width )
539         {
540             *p_data++ = ((( i_total_width - i_line_width)/2) << 2 );
541         }
542
543         for(i_x = 0; i_x < strlen(psz_line); i_x ++)
544         {
545             i_char = toascii( *(psz_line + i_x) );
546
547             if( p_font->i_width[ i_char ] != 0 )
548             {
549                 p_rle = p_font->p_offset[ i_char ][ i_y ];
550                 i_length = p_font->p_length[ i_char ][ i_y ];
551
552                 if(p_rle != NULL )
553                 {
554                     memcpy(p_data, p_rle, i_length * sizeof(uint16_t) ); 
555                     p_data+=i_length;
556                 }
557             }
558         }
559     
560         /* Pad line to fit box */
561         if( i_line_width < i_total_width )
562         {
563             *p_data++ = ((( i_total_width - i_line_width)
564                         - (( i_total_width - i_line_width)/2)) << 2 );
565         }
566     }
567
568     return p_data;
569 }
570
571 /*****************************************************************************
572  * DestroySPU: subpicture destructor
573  *****************************************************************************/
574 static void DestroySPU( subpicture_t *p_spu )
575 {
576     free( p_spu->p_sys );
577 }